You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
10 KiB
324 lines
10 KiB
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using AOT;
|
|
using UnityEngine;
|
|
|
|
public class SysCalls: CallbackHandler<SysCalls>
|
|
{
|
|
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;
|
|
startTime = Time.timeAsDouble;
|
|
|
|
var callbacks = new Callbacks
|
|
{
|
|
target = SelfPtr,
|
|
|
|
SysPrint = CreateCallback<SysPrintCallback>(Callback_SysPrint),
|
|
SysError = CreateCallback<SysErrorCallback>(Callback_SysError),
|
|
SysQuit = CreateCallback<SysQuitCallback>(Callback_SysQuit),
|
|
SysFloatTime = CreateCallback<SysFloatTimeCallback>(Callback_SysFloatTime),
|
|
|
|
SysFileOpenRead = CreateCallback<SysFileOpenReadCallback>(Callback_SysFileOpenRead),
|
|
SysFileOpenWrite = CreateCallback<SysFileOpenWriteCallback>(Callback_SysFileOpenWrite),
|
|
SysFileClose = CreateCallback<SysFileCloseCallback>(Callback_SysFileClose),
|
|
SysFileSeek = CreateCallback<SysFileSeekCallback>(Callback_SysFileSeek),
|
|
SysFileRead = CreateCallback<SysFileReadCallback>(Callback_SysFileRead),
|
|
SysFileWrite = CreateCallback<SysFileWriteCallback>(Callback_SysFileWrite),
|
|
SysFileTime = CreateCallback<SysFileTimeCallback>(Callback_SysFileTime),
|
|
SysMkDir = CreateCallback<SysMkDirCallback>(Callback_SysMkDir),
|
|
};
|
|
|
|
RegisterCallbacks(callbacks);
|
|
}
|
|
|
|
public override void Destroy()
|
|
{
|
|
for (int i = 0; i < MaxFileHandles; ++i)
|
|
{
|
|
if (fileHandles[i] != null)
|
|
fileHandles[i].Dispose();
|
|
}
|
|
|
|
base.Destroy();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This matches struct unity_syscalls_s from uniquake.h in native code.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, Pack = 0)]
|
|
private class Callbacks
|
|
{
|
|
public IntPtr target;
|
|
|
|
public IntPtr SysPrint;
|
|
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);
|
|
|
|
[MonoPInvokeCallback(typeof(SysPrintCallback))]
|
|
private static void Callback_SysPrint(IntPtr target, string message)
|
|
{
|
|
GetSelf(target).Print(message);
|
|
}
|
|
|
|
private void Print(string 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);
|
|
|
|
[MonoPInvokeCallback(typeof(SysErrorCallback))]
|
|
private static void Callback_SysError(IntPtr target, string message)
|
|
{
|
|
GetSelf(target).Error(message);
|
|
}
|
|
|
|
private void Error(string message)
|
|
{
|
|
Debug.LogError(message);
|
|
// TODO: kill execution of the DLL entirely (Sys_Quit expects immediate exit, which this doesn't do)
|
|
Application.Quit(1);
|
|
}
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void SysQuitCallback(IntPtr target);
|
|
|
|
[MonoPInvokeCallback(typeof(SysQuitCallback))]
|
|
private static void Callback_SysQuit(IntPtr target)
|
|
{
|
|
GetSelf(target).Quit();
|
|
}
|
|
|
|
private void Quit()
|
|
{
|
|
Debug.Log($"Quitting application normally");
|
|
Application.Quit(0);
|
|
}
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate double SysFloatTimeCallback(IntPtr target);
|
|
|
|
[MonoPInvokeCallback(typeof(SysFloatTimeCallback))]
|
|
private static double Callback_SysFloatTime(IntPtr target)
|
|
{
|
|
return GetSelf(target).FloatTime();
|
|
}
|
|
|
|
private double FloatTime()
|
|
{
|
|
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}");
|
|
}
|
|
}
|
|
}
|