diff --git a/Assets/Scripts/CallbackHandler.cs b/Assets/Scripts/CallbackHandler.cs index f224841..f525c70 100644 --- a/Assets/Scripts/CallbackHandler.cs +++ b/Assets/Scripts/CallbackHandler.cs @@ -17,7 +17,7 @@ public abstract class CallbackHandler selfHandle = GCHandle.Alloc(this); } - public void Destroy() + public virtual void Destroy() { if (callbacksHandle.IsAllocated) callbacksHandle.Free(); @@ -32,6 +32,7 @@ public abstract class CallbackHandler protected void RegisterCallbacks(TCallbacks callbacks) { + // Pin the callbacks struct so that native code can safely keep a pointer to it and re-use it on subsequent calls callbacksHandle = GCHandle.Alloc(callbacks, GCHandleType.Pinned); } diff --git a/Assets/Scripts/SysCalls.cs b/Assets/Scripts/SysCalls.cs index 2eea816..36fb1f6 100644 --- a/Assets/Scripts/SysCalls.cs +++ b/Assets/Scripts/SysCalls.cs @@ -1,12 +1,18 @@ using System; +using System.IO; using System.Runtime.InteropServices; +using AOT; using UnityEngine; public class SysCalls: CallbackHandler { + private const int MaxFileHandles = 50; + private readonly UniQuake uq; private readonly double startTime; + private readonly FileStream[] fileHandles = new FileStream[MaxFileHandles]; + public SysCalls(UniQuake uniQuake) { uq = uniQuake; @@ -15,15 +21,39 @@ public class SysCalls: CallbackHandler var callbacks = new Callbacks { target = SelfPtr, + SysPrint = CreateCallback(Callback_SysPrint), SysError = CreateCallback(Callback_SysError), SysQuit = CreateCallback(Callback_SysQuit), SysFloatTime = CreateCallback(Callback_SysFloatTime), + + SysFileOpenRead = CreateCallback(Callback_SysFileOpenRead), + SysFileOpenWrite = CreateCallback(Callback_SysFileOpenWrite), + SysFileClose = CreateCallback(Callback_SysFileClose), + SysFileSeek = CreateCallback(Callback_SysFileSeek), + SysFileRead = CreateCallback(Callback_SysFileRead), + SysFileWrite = CreateCallback(Callback_SysFileWrite), + SysFileTime = CreateCallback(Callback_SysFileTime), + SysMkDir = CreateCallback(Callback_SysMkDir), }; RegisterCallbacks(callbacks); } - + + public override void Destroy() + { + for (int i = 0; i < MaxFileHandles; ++i) + { + if (fileHandles[i] != null) + fileHandles[i].Dispose(); + } + + base.Destroy(); + } + + /// + /// This matches struct unity_syscalls_s from uniquake.h in native code. + /// [StructLayout(LayoutKind.Sequential, Pack = 0)] private class Callbacks { @@ -33,12 +63,21 @@ public class SysCalls: CallbackHandler public IntPtr SysError; public IntPtr SysQuit; public IntPtr SysFloatTime; + + public IntPtr SysFileOpenRead; + public IntPtr SysFileOpenWrite; + public IntPtr SysFileClose; + public IntPtr SysFileSeek; + public IntPtr SysFileRead; + public IntPtr SysFileWrite; + public IntPtr SysFileTime; + public IntPtr SysMkDir; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SysPrintCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string message); - [AOT.MonoPInvokeCallback(typeof(SysPrintCallback))] + [MonoPInvokeCallback(typeof(SysPrintCallback))] private static void Callback_SysPrint(IntPtr target, string message) { GetSelf(target).Print(message); @@ -46,13 +85,13 @@ public class SysCalls: CallbackHandler private void Print(string message) { - Debug.Log(message); + // Debug.Log(message); // TODO: collect logs per frame and print after each Update loop } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SysErrorCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string message); - [AOT.MonoPInvokeCallback(typeof(SysErrorCallback))] + [MonoPInvokeCallback(typeof(SysErrorCallback))] private static void Callback_SysError(IntPtr target, string message) { GetSelf(target).Error(message); @@ -68,7 +107,7 @@ public class SysCalls: CallbackHandler [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SysQuitCallback(IntPtr target); - [AOT.MonoPInvokeCallback(typeof(SysQuitCallback))] + [MonoPInvokeCallback(typeof(SysQuitCallback))] private static void Callback_SysQuit(IntPtr target) { GetSelf(target).Quit(); @@ -83,7 +122,7 @@ public class SysCalls: CallbackHandler [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate double SysFloatTimeCallback(IntPtr target); - [AOT.MonoPInvokeCallback(typeof(SysFloatTimeCallback))] + [MonoPInvokeCallback(typeof(SysFloatTimeCallback))] private static double Callback_SysFloatTime(IntPtr target) { return GetSelf(target).FloatTime(); @@ -93,4 +132,193 @@ public class SysCalls: CallbackHandler { return Time.timeAsDouble - startTime; } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int SysFileOpenReadCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string path, out int handle); + + [MonoPInvokeCallback(typeof(SysFileOpenReadCallback))] + private static int Callback_SysFileOpenRead(IntPtr target, string path, out int handle) + { + return GetSelf(target).FileOpenRead(path, out handle); + } + + private int FileOpenRead(string path, out int handle) + { + int i = FindFileHandle(); + try + { + Debug.Log($"Opening file for reading: {path}"); + FileStream fileStream = File.OpenRead(path); + fileHandles[i] = fileStream; + handle = i; + return (int)fileStream.Length; + } + catch + { + handle = -1; + return -1; + } + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int SysFileOpenWriteCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string path); + + [MonoPInvokeCallback(typeof(SysFileOpenWriteCallback))] + private static int Callback_SysFileOpenWrite(IntPtr target, string path) + { + return GetSelf(target).FileOpenWrite(path); + } + + private int FileOpenWrite(string path) + { + int i = FindFileHandle(); + try + { + Debug.Log($"Opening file for writing: {path}"); + FileStream fileStream = File.OpenWrite(path); + fileHandles[i] = fileStream; + return i; + } + catch (Exception ex) + { + Error($"Error opening {path}: {ex.Message}"); + return -1; + } + } + + private int FindFileHandle() + { + for (int i = 1; i < MaxFileHandles; ++i) + { + if (fileHandles[i] == null) + return i; + } + + Error("Out of handles!"); + return -1; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void SysFileCloseCallback(IntPtr target, int handle); + + [MonoPInvokeCallback(typeof(SysFileCloseCallback))] + private static void Callback_SysFileClose(IntPtr target, int handle) + { + GetSelf(target).FileClose(handle); + } + + private void FileClose(int handle) + { + if (handle < 0 || handle >= MaxFileHandles) + { + Debug.LogWarning($"Invalid file handle! {handle}"); + return; + } + + fileHandles[handle].Dispose(); + fileHandles[handle] = null; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void SysFileSeekCallback(IntPtr target, int handle, int position); + + [MonoPInvokeCallback(typeof(SysFileSeekCallback))] + private static void Callback_SysFileSeek(IntPtr target, int handle, int position) + { + GetSelf(target).FileSeek(handle, position); + } + + private void FileSeek(int handle, int position) + { + if (handle < 0 || handle >= MaxFileHandles) + { + Debug.LogWarning($"Invalid file handle! {handle}"); + return; + } + + fileHandles[handle].Seek(position, SeekOrigin.Begin); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int SysFileReadCallback(IntPtr target, int handle, IntPtr dest, int count); + + [MonoPInvokeCallback(typeof(SysFileReadCallback))] + private static int Callback_SysFileRead(IntPtr target, int handle, IntPtr dest, int count) + { + return GetSelf(target).FileRead(handle, dest, count); + } + + private int FileRead(int handle, IntPtr dest, int count) + { + if (handle < 0 || handle >= MaxFileHandles) + { + Debug.LogWarning($"Invalid file handle! {handle}"); + return 0; + } + + byte[] buf = new byte[count]; // TODO: reuse buffer + int numRead = fileHandles[handle].Read(buf, 0, count); + Marshal.Copy(buf, 0, dest, numRead); + return numRead; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int SysFileWriteCallback(IntPtr target, int handle, IntPtr data, int count); + + [MonoPInvokeCallback(typeof(SysFileWriteCallback))] + private static int Callback_SysFileWrite(IntPtr target, int handle, IntPtr data, int count) + { + return GetSelf(target).FileWrite(handle, data, count); + } + + private int FileWrite(int handle, IntPtr data, int count) + { + if (handle < 0 || handle >= MaxFileHandles) + { + Debug.LogWarning($"Invalid file handle! {handle}"); + return 0; + } + + byte[] buf = new byte[count]; // TODO: reuse buffer + Marshal.Copy(data, buf, 0, count); + fileHandles[handle].Write(buf, 0, count); + return count; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int SysFileTimeCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string path); + + [MonoPInvokeCallback(typeof(SysFileTimeCallback))] + private static int Callback_SysFileTime(IntPtr target, string path) + { + return GetSelf(target).FileTime(path); + } + + private int FileTime(string path) + { + // It would logically make more sense to return an actual file write time here, + // but a signed 32-bit int is quite limited and we don't want any Year 2038 problems here. + return File.Exists(path) ? 1 : -1; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void SysMkDirCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string path); + + [MonoPInvokeCallback(typeof(SysMkDirCallback))] + private static void Callback_SysMkDir(IntPtr target, string path) + { + GetSelf(target).MkDir(path); + } + + private void MkDir(string path) + { + try + { + Directory.CreateDirectory(path); + } + catch + { + Debug.LogWarning($"Could not create directory: {path}"); + } + } } diff --git a/engine/code/sys_win.c b/engine/code/sys_win.c index 1d2dce5..d341d3b 100644 --- a/engine/code/sys_win.c +++ b/engine/code/sys_win.c @@ -162,7 +162,7 @@ int FileLength (FILE *f) return end; } -int Sys_FileOpenRead (char *path, int *hndl) +int Sys_FileOpenRead_Obsolete (char *path, int *hndl) { FILE *f; int i, retval; @@ -191,7 +191,7 @@ int Sys_FileOpenRead (char *path, int *hndl) return retval; } -int Sys_FileOpenWrite (char *path) +int Sys_FileOpenWrite_Obsolete(char *path) { FILE *f; int i; @@ -221,7 +221,7 @@ int Sys_FileOpenWrite (char *path) sys_handles[handle] = NULL; VID_ForceLockState (t); }*/ -void Sys_FileClose (int handle) +void Sys_FileClose_Obsolete(int handle) { int t; @@ -241,7 +241,7 @@ void Sys_FileClose (int handle) } // jkrige - pk3 file support -void Sys_FileSeek (int handle, int position) +void Sys_FileSeek_Obsolete(int handle, int position) { int t; @@ -296,7 +296,7 @@ void Sys_FFileClose (FILE *f) VID_ForceLockState (t); return x; }*/ -int Sys_FileRead (int handle, void *dest, int count) +int Sys_FileRead_Obsolete(int handle, void *dest, int count) { int t, x; @@ -311,7 +311,7 @@ int Sys_FileRead (int handle, void *dest, int count) } // jkrige - pk3 file support -int Sys_FileWrite (int handle, void *data, int count) +int Sys_FileWrite_Obsolete(int handle, void *data, int count) { int t, x; @@ -322,7 +322,7 @@ int Sys_FileWrite (int handle, void *data, int count) return x; } -int Sys_FileTime (char *path) +int Sys_FileTime_Obsolete(char *path) { FILE *f; int t, retval; @@ -345,7 +345,7 @@ int Sys_FileTime (char *path) return retval; } -void Sys_mkdir (char *path) +void Sys_mkdir_Obsolete(char *path) { _mkdir (path); } diff --git a/engine/projects/uniquake/sys_uniquake.c b/engine/projects/uniquake/sys_uniquake.c index a9982cf..3d2f838 100644 --- a/engine/projects/uniquake/sys_uniquake.c +++ b/engine/projects/uniquake/sys_uniquake.c @@ -38,3 +38,69 @@ double Sys_FloatTime(void) { return unity_syscalls->SysFloatTime(unity_syscalls->target); } + +int Sys_FileOpenRead(char *path, int *hndl) +{ + int t, retval; + t = VID_ForceUnlockedAndReturnState(); + retval = unity_syscalls->SysFileOpenRead(unity_syscalls->target, path, hndl); + VID_ForceLockState(t); + return retval; +} + +int Sys_FileOpenWrite(char *path) +{ + int t, retval; + t = VID_ForceUnlockedAndReturnState(); + retval = unity_syscalls->SysFileOpenWrite(unity_syscalls->target, path); + VID_ForceLockState(t); + return retval; +} + +void Sys_FileClose(int handle) +{ + int t; + t = VID_ForceUnlockedAndReturnState(); + unity_syscalls->SysFileClose(unity_syscalls->target, handle); + VID_ForceLockState(t); +} + +void Sys_FileSeek(int handle, int position) +{ + int t; + t = VID_ForceUnlockedAndReturnState(); + unity_syscalls->SysFileSeek(unity_syscalls->target, handle, position); + VID_ForceLockState(t); +} + +int Sys_FileRead(int handle, void *dest, int count) +{ + int t, retval; + t = VID_ForceUnlockedAndReturnState(); + retval = unity_syscalls->SysFileRead(unity_syscalls->target, handle, dest, count); + VID_ForceLockState(t); + return retval; +} + +int Sys_FileWrite(int handle, void *data, int count) +{ + int t, retval; + t = VID_ForceUnlockedAndReturnState(); + retval = unity_syscalls->SysFileWrite(unity_syscalls->target, handle, data, count); + VID_ForceLockState(t); + return retval; +} + +int Sys_FileTime(char *path) +{ + int t, retval; + t = VID_ForceUnlockedAndReturnState(); + retval = unity_syscalls->SysFileTime(unity_syscalls->target, path); + VID_ForceLockState(t); + return retval; +} + +void Sys_mkdir(char *path) +{ + unity_syscalls->SysMkDir(unity_syscalls->target, path); +} diff --git a/engine/projects/uniquake/uniquake.h b/engine/projects/uniquake/uniquake.h index a11ba31..644676d 100644 --- a/engine/projects/uniquake/uniquake.h +++ b/engine/projects/uniquake/uniquake.h @@ -17,6 +17,15 @@ typedef struct unity_syscalls_s void(*SysError)(void *target, const char *msg); void(*SysQuit)(void *target); double(*SysFloatTime)(void *target); + + int(*SysFileOpenRead)(void *target, char *path, int *hndl); + int(*SysFileOpenWrite)(void *target, char *path); + void(*SysFileClose)(void *target, int handle); + void(*SysFileSeek)(void *target, int handle, int position); + int(*SysFileRead)(void *target, int handle, void *dest, int count); + int(*SysFileWrite)(void *target, int handle, void *data, int count); + int(*SysFileTime)(void *target, char *path); + void(*SysMkDir)(void *target, char *path); } unity_syscalls_t; extern const unity_syscalls_t *unity_syscalls;