Merge branch 'dllmap' into devel
commit
9ae0d9d02d
|
@ -10,4 +10,7 @@
|
|||
|
||||
## Version 0.2.0
|
||||
- New dataless enumeration functions are available now, which do not cause GC havoc
|
||||
- Unsafe enumeration functions are now marked obsolete
|
||||
- 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/
|
|
@ -4,19 +4,19 @@ using System.Runtime.InteropServices;
|
|||
|
||||
namespace SharpPhysFS
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate int InitDelegate();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate void DeinitDelegate();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate IntPtr MallocDelegate(ulong size);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate IntPtr ReallocDelegate(IntPtr ptr, ulong size);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate void FreeDelegate(IntPtr ptr);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate void StringCallback(IntPtr data, string str);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate void EnumFilesCallback(IntPtr data, string origdir, string fname);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -62,225 +62,141 @@ namespace SharpPhysFS
|
|||
public FreeDelegate Free;
|
||||
}
|
||||
|
||||
static class DynamicLoader
|
||||
static class Interop
|
||||
{
|
||||
#region Windows
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr LoadLibrary(string lpFileName);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern void PHYSFS_getLinkedVersion(ref Version v);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
|
||||
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_init(string argv0);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
|
||||
public static extern bool FreeLibrary(IntPtr hModule);
|
||||
#endregion
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_deinit();
|
||||
|
||||
#region Unix
|
||||
[DllImport("libdl.so", CallingConvention = CallingConvention.Cdecl, EntryPoint = "dlopen")]
|
||||
public static extern IntPtr unix_dlopen(string filename, int flags);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_supportedArchiveTypes();
|
||||
|
||||
[DllImport("libdl.so", CallingConvention = CallingConvention.Cdecl, EntryPoint = "dlsym")]
|
||||
public static extern IntPtr unix_dlsym(IntPtr handle, string symbol);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern void PHYSFS_freeList(IntPtr h);
|
||||
|
||||
[DllImport("libdl.so", CallingConvention = CallingConvention.Cdecl, EntryPoint = "dlclose")]
|
||||
public static extern bool unix_dlclose(IntPtr handle);
|
||||
#endregion
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getLastError();
|
||||
|
||||
#region OSX
|
||||
[DllImport("libdyld.dylib", CallingConvention = CallingConvention.Cdecl, EntryPoint = "dlopen")]
|
||||
public static extern IntPtr osx_dlopen(string filename, int flags);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getDirSeparator();
|
||||
|
||||
[DllImport("libdyld.dylib", CallingConvention = CallingConvention.Cdecl, EntryPoint = "dlsym")]
|
||||
public static extern IntPtr osx_dlsym(IntPtr handle, string symbol);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern void PHYSFS_permitSymbolicLinks(int permit);
|
||||
|
||||
[DllImport("libdyld.dylib", CallingConvention = CallingConvention.Cdecl, EntryPoint = "dlclose")]
|
||||
public static extern bool osx_dlclose(IntPtr handle);
|
||||
#endregion
|
||||
}
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getCdRomDirs();
|
||||
|
||||
class Interop
|
||||
: IDisposable
|
||||
{
|
||||
InvalidOperationException initException = new InvalidOperationException("Callbacks not initialized yet");
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void FnGetLinkedVersion(ref Version v);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnInit(string argv0);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnDeinit();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnClose(IntPtr ptr);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate IntPtr FnSupportedArchiveTypes();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void FnFreeList(IntPtr h);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate IntPtr FnGetLastError();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void FnPermitSymbolicLinks(int permit);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnSetWriteDir(string s);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnAddToSearchPath(string s, int i);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnSetSaneConfig(string s1, string s2, string s3, int i1, int i2);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate IntPtr FnEnumerateFiles(string s);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long FnGetLastModTime(string s);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long FnRead(IntPtr ptr1, IntPtr ptr2, uint i1, uint i2);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long FnTell(IntPtr ptr);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnSeek(IntPtr ptr, ulong u);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long FnFileLength(IntPtr ptr);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnSetAllocator(Allocator alloc);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FnMount(string s1, string s2, int i);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void FnGetCdRomDirsCallback(StringCallback c, IntPtr ptr);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void FnEnumerateFilesCallback(string s, EnumFilesCallback c, IntPtr ptr);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getBaseDir();
|
||||
|
||||
// When the callbacks are not yet initialized, instead of throwing a
|
||||
// null reference exception we explain the problem.
|
||||
// I think it makes for a more graceful failure.
|
||||
public FnGetLinkedVersion PHYSFS_getLinkedVersion => throw initException;
|
||||
public FnInit PHYSFS_init => throw initException;
|
||||
public FnDeinit PHYSFS_deinit => throw initException;
|
||||
public FnSupportedArchiveTypes PHYSFS_supportedArchiveTypes => throw initException;
|
||||
public FnFreeList PHYSFS_freeList => throw initException;
|
||||
public FnGetLastError PHYSFS_getLastError => throw initException;
|
||||
public FnGetLastError PHYSFS_getDirSeparator => throw initException;
|
||||
public FnPermitSymbolicLinks PHYSFS_permitSymbolicLinks => throw initException;
|
||||
public FnSupportedArchiveTypes PHYSFS_getCdRomDirs => throw initException;
|
||||
public FnGetLastError PHYSFS_getBaseDir => throw initException;
|
||||
public FnGetLastError PHYSFS_getUserDir => throw initException;
|
||||
public FnGetLastError PHYSFS_getWriteDir => throw initException;
|
||||
public FnSetWriteDir PHYSFS_setWriteDir => throw initException;
|
||||
public FnAddToSearchPath PHYSFS_addToSearchPath => throw initException;
|
||||
public FnSetWriteDir PHYSFS_removeFromSearchPath => throw initException;
|
||||
public FnSupportedArchiveTypes PHYSFS_getSearchPath => throw initException;
|
||||
public FnSetSaneConfig PHYSFS_setSaneConfig => throw initException;
|
||||
public FnSetWriteDir PHYSFS_mkdir => throw initException;
|
||||
public FnSetWriteDir PHYSFS_delete => throw initException;
|
||||
public FnEnumerateFiles PHYSFS_getRealDir => throw initException;
|
||||
public FnEnumerateFiles PHYSFS_enumerateFiles => throw initException;
|
||||
public FnSetWriteDir PHYSFS_exists => throw initException;
|
||||
public FnSetWriteDir PHYSFS_isDirectory => throw initException;
|
||||
public FnSetWriteDir PHYSFS_isSymbolicLink => throw initException;
|
||||
public FnGetLastModTime PHYSFS_getLastModTime => throw initException;
|
||||
public FnEnumerateFiles PHYSFS_openWrite => throw initException;
|
||||
public FnEnumerateFiles PHYSFS_openAppend => throw initException;
|
||||
public FnEnumerateFiles PHYSFS_openRead => throw initException;
|
||||
public FnClose PHYSFS_close => throw initException;
|
||||
public FnRead PHYSFS_read => throw initException;
|
||||
public FnRead PHYSFS_write => throw initException;
|
||||
public FnClose PHYSFS_eof => throw initException;
|
||||
public FnTell PHYSFS_tell => throw initException;
|
||||
public FnSeek PHYSFS_seek => throw initException;
|
||||
public FnFileLength PHYSFS_fileLength => throw initException;
|
||||
public FnSeek PHYSFS_setBuffer => throw initException;
|
||||
public FnClose PHYSFS_flush => throw initException;
|
||||
public FnDeinit PHYSFS_isInit => throw initException;
|
||||
public FnDeinit PHYSFS_symbolicLinksPermitted => throw initException;
|
||||
public FnSetAllocator PHYSFS_setAllocator => throw initException;
|
||||
public FnMount PHYSFS_mount => throw initException;
|
||||
public FnEnumerateFiles PHYSFS_getMountPoint => throw initException;
|
||||
public FnGetCdRomDirsCallback PHYSFS_getCdRomDirsCallback => throw initException;
|
||||
public FnGetCdRomDirsCallback PHYSFS_getSearchPathCallback => throw initException;
|
||||
public FnEnumerateFilesCallback PHYSFS_enumerateFilesCallback => throw initException;
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getUserDir();
|
||||
|
||||
public Interop()
|
||||
: this("physfs.dll", "libphysfs.dylib", "libphysfs.so")
|
||||
{ }
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getWriteDir();
|
||||
|
||||
public Interop(string libname)
|
||||
: this($"{libname}.dll", $"{libname}.dylib", $"{libname}.so")
|
||||
{ }
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_setWriteDir(string s);
|
||||
|
||||
Func<string, IntPtr> loadLibrary;
|
||||
Func<IntPtr, string, IntPtr> loadSymbol;
|
||||
Func<IntPtr, bool> freeLibrary;
|
||||
IntPtr library;
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_addToSearchPath(string s, int i);
|
||||
|
||||
public Interop(string winlib, string maclib, string unixlib)
|
||||
{
|
||||
/* This method is used to dynamically load the physfs
|
||||
* library. It works by detecting the current platform
|
||||
* and deciding whether to use LoadLibrary/GetProcAddr
|
||||
* on Windows or dlopen/dlsym on Linux and OSX.
|
||||
* The the class is then scanned using reflection
|
||||
* to populate all the callbacks with the right function
|
||||
* pointers from the loaded library
|
||||
*/
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_removeFromSearchPath(string s);
|
||||
|
||||
string libraryName;
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getSearchPath();
|
||||
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
{
|
||||
loadLibrary = DynamicLoader.LoadLibrary;
|
||||
loadSymbol = DynamicLoader.GetProcAddress;
|
||||
freeLibrary = DynamicLoader.FreeLibrary;
|
||||
libraryName = winlib;
|
||||
}
|
||||
else if(Environment.OSVersion.Platform == PlatformID.MacOSX)
|
||||
{
|
||||
loadLibrary = n => DynamicLoader.osx_dlopen(n, 1);
|
||||
loadSymbol = DynamicLoader.osx_dlsym;
|
||||
freeLibrary = DynamicLoader.osx_dlclose;
|
||||
libraryName = maclib;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadLibrary = n => DynamicLoader.unix_dlopen(n, 1);
|
||||
loadSymbol = DynamicLoader.unix_dlsym;
|
||||
freeLibrary = DynamicLoader.unix_dlclose;
|
||||
libraryName = unixlib;
|
||||
}
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_setSaneConfig(string s1, string s2, string s3, int i1, int i2);
|
||||
|
||||
library = loadLibrary(libraryName);
|
||||
if (library == IntPtr.Zero)
|
||||
{
|
||||
throw new PhysFSLibNotFound();
|
||||
}
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_mkdir(string s);
|
||||
|
||||
var fields = typeof(Interop).GetFields();
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_delete(string s);
|
||||
|
||||
foreach(var field in fields.Where(x => x.Name.StartsWith("PHYSFS_")))
|
||||
{
|
||||
var funcPtr = loadSymbol(library, field.Name);
|
||||
var del = Marshal.GetDelegateForFunctionPointer(funcPtr, field.FieldType);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getRealDir(string s);
|
||||
|
||||
field.SetValue(this, del);
|
||||
}
|
||||
}
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_enumerateFiles(string s);
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_exists(string s);
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
/*if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects).
|
||||
}*/
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_isDirectory(string s);
|
||||
|
||||
freeLibrary(library);
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_isSymbolicLink(string s);
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern long PHYSFS_getLastModTime(string s);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
#endregion
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_openWrite(string s);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_openAppend(string s);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_openRead(string s);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_close(IntPtr ptr);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern long PHYSFS_read(IntPtr ptr1, IntPtr ptr2, uint i1, uint i2);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern long PHYSFS_write(IntPtr ptr1, IntPtr ptr2, uint i1, uint i2);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_eof(IntPtr ptr);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern long PHYSFS_tell(IntPtr ptr);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_seek(IntPtr ptr, ulong u);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern long PHYSFS_fileLength(IntPtr ptr);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_setBuffer(IntPtr ptr, ulong u);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_flush(IntPtr ptr);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_isInit();
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_symbolicLinksPermitted();
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_setAllocator(Allocator alloc);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern int PHYSFS_mount(string s1, string s2, int i);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr PHYSFS_getMountPoint(string s);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern void PHYSFS_getCdRomDirsCallback(StringCallback c, IntPtr p);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern void PHYSFS_getSearchPathCallback(StringCallback c, IntPtr p);
|
||||
|
||||
[DllImport("physfs.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public static extern void PHYSFS_enumerateFilesCallback(string s, EnumFilesCallback c, IntPtr p);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SharpPhysFS
|
|||
{
|
||||
public static IntPtr OpenWrite(string filename, PhysFS physFS)
|
||||
{
|
||||
var val = physFS.interop.PHYSFS_openWrite(filename);
|
||||
var val = Interop.PHYSFS_openWrite(filename);
|
||||
if (val == null)
|
||||
throw new PhysFSException(physFS);
|
||||
return val;
|
||||
|
@ -16,7 +16,7 @@ namespace SharpPhysFS
|
|||
|
||||
public static IntPtr OpenAppend(string filename, PhysFS physFS)
|
||||
{
|
||||
var val = physFS.interop.PHYSFS_openAppend(filename);
|
||||
var val = Interop.PHYSFS_openAppend(filename);
|
||||
if (val == null)
|
||||
throw new PhysFSException(physFS);
|
||||
return val;
|
||||
|
@ -24,7 +24,7 @@ namespace SharpPhysFS
|
|||
|
||||
public static IntPtr OpenRead(string filename, PhysFS physFS)
|
||||
{
|
||||
var val = physFS.interop.PHYSFS_openRead(filename);
|
||||
var val = Interop.PHYSFS_openRead(filename);
|
||||
if (val == null)
|
||||
throw new PhysFSException(physFS);
|
||||
return val;
|
||||
|
@ -32,62 +32,62 @@ namespace SharpPhysFS
|
|||
|
||||
public static void Close(IntPtr file, PhysFS physFS)
|
||||
{
|
||||
int err = physFS.interop.PHYSFS_close(file);
|
||||
int err = Interop.PHYSFS_close(file);
|
||||
physFS.ThrowException(err);
|
||||
}
|
||||
|
||||
public static long Read(IntPtr file, byte[] buffer, uint objSize, uint objCount, PhysFS physFS)
|
||||
public static long Read(IntPtr file, byte[] buffer, uint objSize, uint objCount)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (void* ptr = buffer)
|
||||
{
|
||||
return physFS.interop.PHYSFS_read(file, (IntPtr)ptr, objSize, objCount);
|
||||
return Interop.PHYSFS_read(file, (IntPtr)ptr, objSize, objCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static long Write(IntPtr file, byte[] buffer, uint objSize, uint objCount, PhysFS physFS)
|
||||
public static long Write(IntPtr file, byte[] buffer, uint objSize, uint objCount)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (void* ptr = buffer)
|
||||
{
|
||||
return physFS.interop.PHYSFS_write(file, (IntPtr)ptr, objSize, objCount);
|
||||
return Interop.PHYSFS_write(file, (IntPtr)ptr, objSize, objCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool EOF(IntPtr file, PhysFS physFS)
|
||||
public static bool EOF(IntPtr file)
|
||||
{
|
||||
return physFS.interop.PHYSFS_eof(file) != 0;
|
||||
return Interop.PHYSFS_eof(file) != 0;
|
||||
}
|
||||
|
||||
public static long Tell(IntPtr file, PhysFS physFS)
|
||||
public static long Tell(IntPtr file)
|
||||
{
|
||||
return physFS.interop.PHYSFS_tell(file);
|
||||
return Interop.PHYSFS_tell(file);
|
||||
}
|
||||
|
||||
public static void Seek(IntPtr file, ulong pos, PhysFS physFS)
|
||||
{
|
||||
int err = physFS.interop.PHYSFS_seek(file, pos);
|
||||
int err = Interop.PHYSFS_seek(file, pos);
|
||||
physFS.ThrowException(err);
|
||||
}
|
||||
|
||||
public static long FileLength(IntPtr file, PhysFS physFS)
|
||||
public static long FileLength(IntPtr file)
|
||||
{
|
||||
return physFS.interop.PHYSFS_fileLength(file);
|
||||
return Interop.PHYSFS_fileLength(file);
|
||||
}
|
||||
|
||||
public static void SetBuffer(IntPtr file, ulong bufSize, PhysFS physFS)
|
||||
{
|
||||
int err = physFS.interop.PHYSFS_setBuffer(file, bufSize);
|
||||
int err = Interop.PHYSFS_setBuffer(file, bufSize);
|
||||
physFS.ThrowException(err);
|
||||
}
|
||||
|
||||
public static void Flush(IntPtr file, PhysFS physFS)
|
||||
{
|
||||
int err = physFS.interop.PHYSFS_flush(file);
|
||||
int err = Interop.PHYSFS_flush(file);
|
||||
physFS.ThrowException(err);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,11 +24,13 @@ namespace SharpPhysFS
|
|||
{ }
|
||||
}
|
||||
|
||||
Interop interop;
|
||||
|
||||
public PhysFS(string argv0)
|
||||
{
|
||||
interop = new Interop();
|
||||
Init(argv0);
|
||||
}
|
||||
|
||||
public PhysFS(string argv0, string libname)
|
||||
{
|
||||
Init(argv0);
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ namespace SharpPhysFS
|
|||
public Version GetLinkedVersion()
|
||||
{
|
||||
Version v = new Version();
|
||||
interop.PHYSFS_getLinkedVersion(ref v);
|
||||
Interop.PHYSFS_getLinkedVersion(ref v);
|
||||
return v;
|
||||
}
|
||||
|
||||
|
@ -67,7 +69,7 @@ namespace SharpPhysFS
|
|||
/// <param name="argv0">This should be the path of the executable (first arguments passed to main function in C programs)</param>
|
||||
public void Init(string argv0)
|
||||
{
|
||||
int err = interop.PHYSFS_init(argv0);
|
||||
int err = Interop.PHYSFS_init(argv0);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -90,7 +92,7 @@ namespace SharpPhysFS
|
|||
/// </remarks>
|
||||
public void Deinit()
|
||||
{
|
||||
int err = interop.PHYSFS_deinit();
|
||||
int err = Interop.PHYSFS_deinit();
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -117,7 +119,7 @@ namespace SharpPhysFS
|
|||
/// <param name="appendToPath">True to append to search path, false to prepend.</param>
|
||||
public void Mount(string dir, string mountPoint, bool appendToPath)
|
||||
{
|
||||
int err = interop.PHYSFS_mount(dir, mountPoint, appendToPath ? 1 : 0);
|
||||
int err = Interop.PHYSFS_mount(dir, mountPoint, appendToPath ? 1 : 0);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -134,7 +136,7 @@ namespace SharpPhysFS
|
|||
/// <returns>String of last error message.</returns>
|
||||
public string GetLastError()
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(interop.PHYSFS_getLastError());
|
||||
return Marshal.PtrToStringAnsi(Interop.PHYSFS_getLastError());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -150,7 +152,7 @@ namespace SharpPhysFS
|
|||
/// <returns>Platform-dependent dir separator string</returns>
|
||||
public string GetDirSeparator()
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(interop.PHYSFS_getDirSeparator());
|
||||
return Marshal.PtrToStringAnsi(Interop.PHYSFS_getDirSeparator());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -196,7 +198,7 @@ namespace SharpPhysFS
|
|||
/// <returns>An enumeration of supported archive types</returns>
|
||||
public IEnumerable<ArchiveInfo> SupportedArchiveTypes()
|
||||
{
|
||||
IntPtr archives = interop.PHYSFS_supportedArchiveTypes();
|
||||
IntPtr archives = Interop.PHYSFS_supportedArchiveTypes();
|
||||
IntPtr i = archives;
|
||||
for (i = archives; Marshal.ReadIntPtr(i) != IntPtr.Zero; i = IntPtr.Add(i, IntPtr.Size))
|
||||
{
|
||||
|
@ -231,7 +233,7 @@ namespace SharpPhysFS
|
|||
/// <param name="permit">true to permit symlinks, false to deny linking.</param>
|
||||
public void PermitSymbolicLinks(bool permit)
|
||||
{
|
||||
interop.PHYSFS_permitSymbolicLinks(permit ? 1 : 0);
|
||||
Interop.PHYSFS_permitSymbolicLinks(permit ? 1 : 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -268,7 +270,7 @@ namespace SharpPhysFS
|
|||
/// <returns></returns>
|
||||
public string GetBaseDir()
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(interop.PHYSFS_getBaseDir());
|
||||
return Marshal.PtrToStringAnsi(Interop.PHYSFS_getBaseDir());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -286,7 +288,7 @@ namespace SharpPhysFS
|
|||
/// <returns>String of user dir in platform-dependent notation.</returns>
|
||||
public string GetUserDir()
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(interop.PHYSFS_getUserDir());
|
||||
return Marshal.PtrToStringAnsi(Interop.PHYSFS_getUserDir());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -298,7 +300,7 @@ namespace SharpPhysFS
|
|||
/// <returns>String of write dir in platform-dependent notation, OR null IF NO WRITE PATH IS CURRENTLY SET</returns>
|
||||
public string GetWriteDir()
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(interop.PHYSFS_getWriteDir());
|
||||
return Marshal.PtrToStringAnsi(Interop.PHYSFS_getWriteDir());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -316,7 +318,7 @@ namespace SharpPhysFS
|
|||
/// </param>
|
||||
public void SetWriteDir(string path)
|
||||
{
|
||||
int err = interop.PHYSFS_setWriteDir(path);
|
||||
int err = Interop.PHYSFS_setWriteDir(path);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -328,7 +330,7 @@ namespace SharpPhysFS
|
|||
[Obsolete("AddToSearchPath is deprecated, please use Mount instead")]
|
||||
public void AddToSearchPath(string newDir, bool appendToPath)
|
||||
{
|
||||
int err = interop.PHYSFS_addToSearchPath(newDir, appendToPath ? 1 : 0);
|
||||
int err = Interop.PHYSFS_addToSearchPath(newDir, appendToPath ? 1 : 0);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -344,7 +346,7 @@ namespace SharpPhysFS
|
|||
/// <param name="oldDir"> dir/archive to remove.</param>
|
||||
public void RemoveFromSearchPath(string oldDir)
|
||||
{
|
||||
int err = interop.PHYSFS_removeFromSearchPath(oldDir);
|
||||
int err = Interop.PHYSFS_removeFromSearchPath(oldDir);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -414,7 +416,7 @@ namespace SharpPhysFS
|
|||
/// <param name="archivesFirst">True to prepend the archives to the search path. False to append them. Ignored if !<paramref name="archiveExt"/>.</param>
|
||||
public void SetSaneConfig(string organization, string appName, string archiveExt, bool includeCdRoms, bool archivesFirst)
|
||||
{
|
||||
int err = interop.PHYSFS_setSaneConfig(organization, appName, archiveExt, includeCdRoms ? 1 : 0, archivesFirst ? 1 : 0);
|
||||
int err = Interop.PHYSFS_setSaneConfig(organization, appName, archiveExt, includeCdRoms ? 1 : 0, archivesFirst ? 1 : 0);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -434,7 +436,7 @@ namespace SharpPhysFS
|
|||
/// <param name="dirName">New dir to create.</param>
|
||||
public void Mkdir(string dirName)
|
||||
{
|
||||
int err = interop.PHYSFS_mkdir(dirName);
|
||||
int err = Interop.PHYSFS_mkdir(dirName);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -461,7 +463,7 @@ namespace SharpPhysFS
|
|||
/// <param name="filename">Filename to delete.</param>
|
||||
public void Delete(string filename)
|
||||
{
|
||||
int err = interop.PHYSFS_delete(filename);
|
||||
int err = Interop.PHYSFS_delete(filename);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -485,7 +487,7 @@ namespace SharpPhysFS
|
|||
/// <returns>String of element of search path containing the the file in question. null if not found.</returns>
|
||||
public string GetRealDir(string filename)
|
||||
{
|
||||
return Marshal.PtrToStringAnsi(interop.PHYSFS_getRealDir(filename));
|
||||
return Marshal.PtrToStringAnsi(Interop.PHYSFS_getRealDir(filename));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -500,7 +502,7 @@ namespace SharpPhysFS
|
|||
/// <returns>True if filename exists. false otherwise.</returns>
|
||||
public bool Exists(string fname)
|
||||
{
|
||||
return interop.PHYSFS_exists(fname) != 0;
|
||||
return Interop.PHYSFS_exists(fname) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -512,7 +514,7 @@ namespace SharpPhysFS
|
|||
/// <returns>True if filename exists and is a directory. False otherwise.</returns>
|
||||
public bool IsDirectory(string fname)
|
||||
{
|
||||
return interop.PHYSFS_isDirectory(fname) != 0;
|
||||
return Interop.PHYSFS_isDirectory(fname) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -524,7 +526,7 @@ namespace SharpPhysFS
|
|||
/// <returns>True if filename exists and is a symlink. False otherwise.</returns>
|
||||
public bool IsSymbolicLink(string fname)
|
||||
{
|
||||
return interop.PHYSFS_isSymbolicLink(fname) != 0;
|
||||
return Interop.PHYSFS_isSymbolicLink(fname) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -539,7 +541,7 @@ namespace SharpPhysFS
|
|||
/// <returns>Last modified time of the file. -1 if it can't be determined.</returns>
|
||||
public long GetLastModTime(string fname)
|
||||
{
|
||||
return interop.PHYSFS_getLastModTime(fname);
|
||||
return Interop.PHYSFS_getLastModTime(fname);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -552,7 +554,7 @@ namespace SharpPhysFS
|
|||
/// <returns>True if library is initialized, false if library is not.</returns>
|
||||
public bool IsInit()
|
||||
{
|
||||
return interop.PHYSFS_isInit() != 0;
|
||||
return Interop.PHYSFS_isInit() != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -565,12 +567,12 @@ namespace SharpPhysFS
|
|||
/// <returns>True if symlinks are permitted, false if not.</returns>
|
||||
public bool SymbolicLinksPermitted()
|
||||
{
|
||||
return interop.PHYSFS_symbolicLinksPermitted() != 0;
|
||||
return Interop.PHYSFS_symbolicLinksPermitted() != 0;
|
||||
}
|
||||
|
||||
public void SetAllocator(Allocator allocator)
|
||||
{
|
||||
int err = interop.PHYSFS_setAllocator(allocator);
|
||||
int err = Interop.PHYSFS_setAllocator(allocator);
|
||||
ThrowException(err);
|
||||
}
|
||||
|
||||
|
@ -588,7 +590,7 @@ namespace SharpPhysFS
|
|||
/// <returns>String of mount point if added to path</returns>
|
||||
public string GetMountPoint(string dir)
|
||||
{
|
||||
var s = Marshal.PtrToStringAnsi(interop.PHYSFS_getMountPoint(dir));
|
||||
var s = Marshal.PtrToStringAnsi(Interop.PHYSFS_getMountPoint(dir));
|
||||
if(s == null)
|
||||
{
|
||||
throw new PhysFSException(this);
|
||||
|
@ -608,7 +610,7 @@ namespace SharpPhysFS
|
|||
void GetCdRomDirsCallback(StringCallback c, object data)
|
||||
{
|
||||
GCHandle objHandle = GCHandle.Alloc(data);
|
||||
interop.PHYSFS_getCdRomDirsCallback(c, GCHandle.ToIntPtr(objHandle));
|
||||
Interop.PHYSFS_getCdRomDirsCallback(c, GCHandle.ToIntPtr(objHandle));
|
||||
objHandle.Free();
|
||||
}
|
||||
|
||||
|
@ -633,13 +635,13 @@ namespace SharpPhysFS
|
|||
/// <param name="c">Callback function to notify about detected drives.</param>
|
||||
public void GetCdRomDirsCallback(Action<string> c)
|
||||
{
|
||||
interop.PHYSFS_getCdRomDirsCallback((p, s) => c(s), IntPtr.Zero);
|
||||
Interop.PHYSFS_getCdRomDirsCallback((p, s) => c(s), IntPtr.Zero);
|
||||
}
|
||||
|
||||
void GetSearchPathCallback(StringCallback c, object data)
|
||||
{
|
||||
GCHandle objHandle = GCHandle.Alloc(data);
|
||||
interop.PHYSFS_getSearchPathCallback(c, GCHandle.ToIntPtr(objHandle));
|
||||
Interop.PHYSFS_getSearchPathCallback(c, GCHandle.ToIntPtr(objHandle));
|
||||
objHandle.Free();
|
||||
}
|
||||
|
||||
|
@ -664,13 +666,13 @@ namespace SharpPhysFS
|
|||
/// <param name="c">Callback function to notify about search path elements.</param>
|
||||
public void GetSearchPathCallback(Action<string> c)
|
||||
{
|
||||
interop.PHYSFS_getSearchPathCallback((p, s) => c(s), IntPtr.Zero);
|
||||
Interop.PHYSFS_getSearchPathCallback((p, s) => c(s), IntPtr.Zero);
|
||||
}
|
||||
|
||||
void EnumerateFilesCallback(string dir, EnumFilesCallback c, object data)
|
||||
{
|
||||
GCHandle objHandle = GCHandle.Alloc(data);
|
||||
interop.PHYSFS_enumerateFilesCallback(dir, c, GCHandle.ToIntPtr(objHandle));
|
||||
Interop.PHYSFS_enumerateFilesCallback(dir, c, GCHandle.ToIntPtr(objHandle));
|
||||
objHandle.Free();
|
||||
}
|
||||
|
||||
|
@ -701,7 +703,7 @@ namespace SharpPhysFS
|
|||
/// <param name="c">Callback function to notify about search path elements.</param>
|
||||
public void EnumerateFilesCallback(string dir, Action<string, string> c)
|
||||
{
|
||||
interop.PHYSFS_enumerateFilesCallback(dir, (data, origdir, fname) => c(origdir, fname), IntPtr.Zero);
|
||||
Interop.PHYSFS_enumerateFilesCallback(dir, (data, origdir, fname) => c(origdir, fname), IntPtr.Zero);
|
||||
}
|
||||
|
||||
public PhysFSStream OpenAppend(string file)
|
||||
|
@ -731,7 +733,6 @@ namespace SharpPhysFS
|
|||
if (disposing)
|
||||
{
|
||||
Deinit();
|
||||
interop.Dispose();
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace SharpPhysFS
|
|||
{
|
||||
get
|
||||
{
|
||||
return PhysFS.LowLevel.FileLength(handle, physFS);
|
||||
return PhysFS.LowLevel.FileLength(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace SharpPhysFS
|
|||
{
|
||||
get
|
||||
{
|
||||
return PhysFS.LowLevel.Tell(handle, physFS);
|
||||
return PhysFS.LowLevel.Tell(handle);
|
||||
}
|
||||
set
|
||||
{
|
||||
|
@ -67,7 +67,7 @@ namespace SharpPhysFS
|
|||
|
||||
public long Read(byte[] buffer, uint offset, uint count)
|
||||
{
|
||||
return PhysFS.LowLevel.Read(handle, buffer, 1, count, physFS);
|
||||
return PhysFS.LowLevel.Read(handle, buffer, 1, count);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
|
@ -81,9 +81,9 @@ namespace SharpPhysFS
|
|||
if (origin == SeekOrigin.Begin)
|
||||
pos = 0;
|
||||
else if (origin == SeekOrigin.Current)
|
||||
pos = PhysFS.LowLevel.Tell(handle, physFS);
|
||||
pos = PhysFS.LowLevel.Tell(handle);
|
||||
else
|
||||
pos = PhysFS.LowLevel.FileLength(handle, physFS);
|
||||
pos = PhysFS.LowLevel.FileLength(handle);
|
||||
|
||||
PhysFS.LowLevel.Seek(handle, (ulong)(pos + offset), physFS);
|
||||
return pos + offset;
|
||||
|
@ -91,7 +91,7 @@ namespace SharpPhysFS
|
|||
|
||||
public long Write(byte[] buffer, uint offset, uint count)
|
||||
{
|
||||
return PhysFS.LowLevel.Write(handle, buffer, 1, count, physFS);
|
||||
return PhysFS.LowLevel.Write(handle, buffer, 1, count);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
|
|
|
@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Francesco Bertolaccini")]
|
||||
[assembly: AssemblyProduct("SharpPhysFS")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016 Francesco Bertolaccini")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||
// 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("0.2.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.2.0.0")]
|
||||
[assembly: AssemblyVersion("0.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.1.0.0")]
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
<dllmap os="!windows" dll="physfs.dll" target="libphysfs.so" />
|
||||
</configuration>
|
Loading…
Reference in New Issue