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.
 
 
 
 
 

323 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)
{
// Use an exception to emulate Quake's behavior of immediately terminating the game's code execution on error
throw new QuakeException(message, 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()
{
// This exception will be caught by the UniQuake update loop where the game will be shut down
throw new QuakeException("Quitting application normally");
}
[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}");
}
}
}