diff --git a/CHANGELOG.md b/CHANGELOG.md index ba48690..b5c2758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,4 +13,8 @@ - Unsafe enumeration functions are now marked obsolete ## Version 1.0.0 -- DLL configuration on platforms other than Windows is done via app.config now, as per http://www.mono-project.com/docs/advanced/pinvoke/dllmap/ \ No newline at end of file +- DLL configuration on platforms other than Windows is done via app.config now, as per http://www.mono-project.com/docs/advanced/pinvoke/dllmap/ +- Fixed a bug in which trying to open a nonexistant bug would create an invalid handle without throwing exceptions +- Fixed a bug which would ignore the offset parameter in stream readings +- Added automated testing +- Various deprecated methods are now private only, they have been substituted with safer counterparts \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index f2825d8..f7b00b3 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Francesco Bertolaccini +# Copyright (c) 2017 Francesco Bertolaccini Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, diff --git a/README.md b/README.md index 4b1d10c..821ff2c 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,8 @@ You should also include compiled shared library of physfs alongside your binary ````c# using(var pfs = new PhysFS("")) // This ensures correct initialization and deinitialization -using(var stream = pfs.OpenRead("/helloworld.txt")) +using(var reader = new StreamReader(pfs.OpenRead("/helloworld.txt"))) { - var reader = new StreamReader(stream); var contents = reader.ReadToEnd(); } -```` - -## Support on Beerpay -Hey dude! Help me out for a couple of :beers:! - -[![Beerpay](https://beerpay.io/frabert/SharpPhysFS/badge.svg?style=beer-square)](https://beerpay.io/frabert/SharpPhysFS) [![Beerpay](https://beerpay.io/frabert/SharpPhysFS/make-wish.svg?style=flat-square)](https://beerpay.io/frabert/SharpPhysFS?focus=wish) \ No newline at end of file +```` \ No newline at end of file diff --git a/SharpPhysFS.sln b/SharpPhysFS.sln index aa284c9..1c5e18e 100644 --- a/SharpPhysFS.sln +++ b/SharpPhysFS.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26403.3 +VisualStudioVersion = 15.0.26430.6 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpPhysFS", "SharpPhysFS\SharpPhysFS.csproj", "{AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}" EndProject @@ -15,20 +15,54 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{E1D09413-14F4-4C29-BD06-B6E7D38B80CD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{F1848CF4-B858-4F6D-A1CE-032633BE3C28}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Debug|x64.ActiveCfg = Debug|x64 + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Debug|x64.Build.0 = Debug|x64 + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Debug|x86.ActiveCfg = Debug|x86 + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Debug|x86.Build.0 = Debug|x86 {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Release|Any CPU.Build.0 = Release|Any CPU + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Release|x64.ActiveCfg = Release|x64 + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Release|x64.Build.0 = Release|x64 + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Release|x86.ActiveCfg = Release|x86 + {AD6AA182-8C7F-4F3A-AAEF-7BD993D1D262}.Release|x86.Build.0 = Release|x86 {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Debug|x64.ActiveCfg = Debug|x64 + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Debug|x64.Build.0 = Debug|x64 + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Debug|x86.ActiveCfg = Debug|x86 + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Debug|x86.Build.0 = Debug|x86 {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Release|Any CPU.Build.0 = Release|Any CPU + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Release|x64.ActiveCfg = Release|x64 + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Release|x64.Build.0 = Release|x64 + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Release|x86.ActiveCfg = Release|x86 + {E1D09413-14F4-4C29-BD06-B6E7D38B80CD}.Release|x86.Build.0 = Release|x86 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Debug|x64.ActiveCfg = Debug|x64 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Debug|x64.Build.0 = Debug|x64 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Debug|x86.ActiveCfg = Debug|x86 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Debug|x86.Build.0 = Debug|x86 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Release|Any CPU.Build.0 = Release|Any CPU + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Release|x64.ActiveCfg = Release|x64 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Release|x64.Build.0 = Release|x64 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Release|x86.ActiveCfg = Release|x86 + {F1848CF4-B858-4F6D-A1CE-032633BE3C28}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SharpPhysFS/Interop.cs b/SharpPhysFS/Interop.cs index d4dbfbb..76818c0 100644 --- a/SharpPhysFS/Interop.cs +++ b/SharpPhysFS/Interop.cs @@ -44,7 +44,7 @@ namespace SharpPhysFS } [StructLayout(LayoutKind.Sequential)] - public class Allocator + public struct Allocator { [MarshalAs(UnmanagedType.FunctionPtr)] public InitDelegate Init; diff --git a/SharpPhysFS/PhysFS.LowLevel.cs b/SharpPhysFS/PhysFS.LowLevel.cs index 37924ab..e9954a7 100644 --- a/SharpPhysFS/PhysFS.LowLevel.cs +++ b/SharpPhysFS/PhysFS.LowLevel.cs @@ -1,8 +1,9 @@ using System; +using System.Runtime.InteropServices; namespace SharpPhysFS { - public partial class PhysFS + public sealed partial class PhysFS { internal static class LowLevel { @@ -36,15 +37,18 @@ namespace SharpPhysFS physFS.ThrowException(err); } - public static long Read(IntPtr file, byte[] buffer, uint objSize, uint objCount) + public static long Read(IntPtr file, byte[] buffer, uint objSize, uint objCount, int offset) { - unsafe + if (buffer.Length < (objSize * objCount) + offset) { - fixed (void* ptr = buffer) - { - return Interop.PHYSFS_read(file, (IntPtr)ptr, objSize, objCount); - } + throw new InvalidOperationException("Buffer too small"); } + + var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + long ret = Interop.PHYSFS_read(file, IntPtr.Add(handle.AddrOfPinnedObject(), offset), 1, objCount); + handle.Free(); + + return ret; } public static long Write(IntPtr file, byte[] buffer, uint objSize, uint objCount) diff --git a/SharpPhysFS/PhysFS.cs b/SharpPhysFS/PhysFS.cs index ed93253..f3fca75 100644 --- a/SharpPhysFS/PhysFS.cs +++ b/SharpPhysFS/PhysFS.cs @@ -14,7 +14,7 @@ namespace SharpPhysFS /// /// Main class for SharpPhysFS /// - public partial class PhysFS + public sealed partial class PhysFS : IDisposable { public class PhysFSException : Exception @@ -29,11 +29,6 @@ namespace SharpPhysFS Init(argv0); } - public PhysFS(string argv0, string libname) - { - Init(argv0); - } - static T FromPtr(IntPtr ptr) { return (T)Marshal.PtrToStructure(ptr, typeof(T)); @@ -709,39 +704,49 @@ namespace SharpPhysFS public PhysFSStream OpenAppend(string file) { var handle = LowLevel.OpenAppend(file, this); + if (handle == IntPtr.Zero) throw new PhysFSException(this); return new PhysFSStream(this, handle, false); } public PhysFSStream OpenRead(string file) { var handle = LowLevel.OpenRead(file, this); + if (handle == IntPtr.Zero) throw new PhysFSException(this); return new PhysFSStream(this, handle, true); } public PhysFSStream OpenWrite(string file) { var handle = LowLevel.OpenWrite(file, this); + if (handle == IntPtr.Zero) throw new PhysFSException(this); return new PhysFSStream(this, handle, false); } - bool disposed = false; + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls - protected virtual void Dispose(bool disposing) + void Dispose(bool disposing) { - if (!disposed) + if (!disposedValue) { if (disposing) { Deinit(); } - - disposed = true; + disposedValue = true; } } - + + ~PhysFS() { + Dispose(false); + } + public void Dispose() { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); + GC.SuppressFinalize(this); } + #endregion } } diff --git a/SharpPhysFS/PhysFSStream.cs b/SharpPhysFS/PhysFSStream.cs index b231cc5..49fd46e 100644 --- a/SharpPhysFS/PhysFSStream.cs +++ b/SharpPhysFS/PhysFSStream.cs @@ -65,14 +65,14 @@ namespace SharpPhysFS } } - public long Read(byte[] buffer, uint offset, uint count) + public long Read(byte[] buffer, int offset, uint count) { - return PhysFS.LowLevel.Read(handle, buffer, 1, count); + return PhysFS.LowLevel.Read(handle, buffer, 1, count, offset); } public override int Read(byte[] buffer, int offset, int count) { - return (int)Read(buffer, (uint)offset, (uint)count); + return (int)Read(buffer, offset, (uint)count); } public override long Seek(long offset, SeekOrigin origin) diff --git a/SharpPhysFS/SharpPhysFS.csproj b/SharpPhysFS/SharpPhysFS.csproj index 9cc4be1..a6da2b4 100644 --- a/SharpPhysFS/SharpPhysFS.csproj +++ b/SharpPhysFS/SharpPhysFS.csproj @@ -31,6 +31,46 @@ 4 true + + true + bin\x64\Debug\ + DEBUG;TRACE + true + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x86\Debug\ + DEBUG;TRACE + true + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + diff --git a/Test/Program.cs b/Test/Program.cs index 2d7be11..3453263 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -314,6 +314,17 @@ namespace Test return true; } + static bool GetMountPoint(string[] args) + { + if (args.Length < 1) + { + Console.WriteLine("Usage: getmountpoint "); + return false; + } + Console.WriteLine(physFS.GetMountPoint(args[0])); + return true; + } + #endregion static void Main(string[] args) @@ -358,6 +369,7 @@ namespace Test commands.Add("issymlink", IsSymlink); commands.Add("cat", Cat); commands.Add("filelength", FileLength); + commands.Add("getmountpoint", GetMountPoint); while (true) { diff --git a/Test/Test.csproj b/Test/Test.csproj index e691970..761419c 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -32,6 +32,46 @@ prompt 4 + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + diff --git a/UnitTests/Properties/AssemblyInfo.cs b/UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c782e86 --- /dev/null +++ b/UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f1848cf4-b858-4f6d-a1ce-032633be3c28")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UnitTests/Tests.cs b/UnitTests/Tests.cs new file mode 100644 index 0000000..4bfd698 --- /dev/null +++ b/UnitTests/Tests.cs @@ -0,0 +1,169 @@ +using System; +using System.IO; +using System.Linq; +using Xunit; +using SharpPhysFS; + +namespace UnitTests +{ + public class Tests + { + [Fact] + void IsInit() + { + using (var pfs = new PhysFS("")) + Assert.True(pfs.IsInit(), "PhysFS was not initialized"); + } + + [Theory] + [InlineData(2, 1, 0)] + void VersionCheck(byte major, byte minor, byte patch) + { + using (var pfs = new PhysFS("")) + Assert.Equal(new SharpPhysFS.Version() { major = major, minor = minor, patch = patch }, pfs.GetLinkedVersion()); + } + + [Fact] + void DirSeparator() + { + using (var pfs = new PhysFS("")) + { + Assert.NotNull(pfs.GetDirSeparator()); + Assert.NotEqual("", pfs.GetDirSeparator()); + } + } + + [Fact] + void PermitSymbolicLinks() + { + using (var pfs = new PhysFS("")) + { + Assert.False(pfs.SymbolicLinksPermitted()); + pfs.PermitSymbolicLinks(true); + Assert.True(pfs.SymbolicLinksPermitted()); + pfs.PermitSymbolicLinks(false); + Assert.False(pfs.SymbolicLinksPermitted()); + } + } + + [Fact] + void Mounting() + { + using (var pfs = new PhysFS("")) + { + Assert.Empty(pfs.GetSearchPath()); + pfs.Mount("./", "/", false); + Assert.Equal(new string[] { "./" }, pfs.GetSearchPath()); + Assert.Equal("/", pfs.GetMountPoint("./")); + Assert.True(pfs.IsDirectory("/")); + + pfs.Mount("../", "foo", true); + Assert.Equal(new string[] { "./", "../", }, pfs.GetSearchPath()); + Assert.Equal("foo/", pfs.GetMountPoint("../")); + Assert.True(pfs.IsDirectory("/foo")); + + pfs.Mount("../../", "bar", false); + Assert.Equal(new string[] { "../../", "./", "../", }, pfs.GetSearchPath()); + Assert.Equal("bar/", pfs.GetMountPoint("../../")); + Assert.True(pfs.IsDirectory("/bar")); + + pfs.RemoveFromSearchPath("../"); + Assert.Equal(new string[] { "../../", "./", }, pfs.GetSearchPath()); + } + } + + [Fact] + void FileEnumeration() + { + using (var pfs = new PhysFS("")) + { + pfs.Mount("./", "/", false); + + var effectiveFiles = Directory.GetFiles("./").Select(x => Path.GetFileName(x)).ToArray(); + Array.Sort(effectiveFiles); + var enumeratedFiles = pfs.EnumerateFiles("/"); + Array.Sort(enumeratedFiles); + + Assert.Equal(effectiveFiles, enumeratedFiles); + } + } + + [Fact] + void DriveEnumeration() + { + using(var pfs = new PhysFS("")) + { + var effectiveCdDrives = DriveInfo.GetDrives() + .Where(x => x.DriveType == DriveType.CDRom) + .Select(x => x.RootDirectory.FullName) + .ToArray(); + + var enumeratedCdDrives = pfs.GetCdRomDirs(); + + Array.Sort(effectiveCdDrives); + Array.Sort(enumeratedCdDrives); + + Assert.Equal(effectiveCdDrives, enumeratedCdDrives); + } + } + + [Fact] + void UserDirectory() + { + using(var pfs = new PhysFS("")) + { + var userDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var pfsUserDirectory = pfs.GetUserDir(); + Assert.Equal(Path.GetPathRoot(userDirectory), Path.GetPathRoot(pfsUserDirectory)); + } + } + + [Fact] + void DirectoryManipulation() + { + using(var pfs = new PhysFS("")) + { + pfs.SetWriteDir("./"); + Assert.Equal("./", pfs.GetWriteDir()); + + pfs.Mkdir("hello"); + Assert.True(Directory.Exists("./hello")); + + pfs.Delete("hello"); + Assert.False(Directory.Exists("./hello")); + } + } + + [Fact] + void FileManipulation() + { + using (var pfs = new PhysFS("")) + { + pfs.SetWriteDir("./"); + pfs.Mount("./", "/", true); + + using(var sw = new StreamWriter(pfs.OpenWrite("foo"))) + { + sw.Write("hello, world! èòàùã こんにちは世界 你好世界"); + } + + Assert.True(File.Exists("./foo")); + + var fileContent = File.ReadAllText("./foo"); + using(var sr = new StreamReader(pfs.OpenRead("foo"))) + { + Assert.Equal(fileContent, sr.ReadToEnd()); + } + + using (var sw = new StreamWriter(pfs.OpenAppend("foo"))) + { + sw.Write("foo"); + } + Assert.Equal(fileContent + "foo", File.ReadAllText("./foo")); + + pfs.Delete("foo"); + Assert.False(File.Exists("./foo")); + } + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj new file mode 100644 index 0000000..fb2b45e --- /dev/null +++ b/UnitTests/UnitTests.csproj @@ -0,0 +1,118 @@ + + + + + + Debug + AnyCPU + {F1848CF4-B858-4F6D-A1CE-032633BE3C28} + Library + Properties + UnitTests + UnitTests + v4.5.2 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + x86 + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + + ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll + + + + + + + + + + + + {ad6aa182-8c7f-4f3a-aaef-7bd993d1d262} + SharpPhysFS + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/UnitTests/packages.config b/UnitTests/packages.config new file mode 100644 index 0000000..8351e7b --- /dev/null +++ b/UnitTests/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file