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.
264 lines
7.1 KiB
264 lines
7.1 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
public class UniQuake: MonoBehaviour
|
|
{
|
|
#if UNITY_EDITOR
|
|
private const string DllPath = "Plugins/windows/x86_64/uniquake.dll";
|
|
#elif UNITY_STANDALONE_WIN
|
|
#if UNITY_64
|
|
private const string DllPath = "Plugins/x86_64/uniquake.dll";
|
|
#else
|
|
private const string DllPath = "Plugins/x86/uniquake.dll";
|
|
#endif
|
|
#endif
|
|
|
|
private const int MemSize = 0x8000000; // 128 MB of heap space
|
|
|
|
private IntPtr libraryHandle;
|
|
|
|
private QuakeParms quakeParms;
|
|
private SystemModule systemModule;
|
|
private RenderModule renderModule;
|
|
|
|
private bool initialized = false;
|
|
private double startTime;
|
|
|
|
public MissionPack BaseGame { get; set; }
|
|
public string ModDirectory { get; set; }
|
|
|
|
/// <summary>
|
|
/// Time since startup for this particular instance of Quake
|
|
/// </summary>
|
|
public double CurrentTime => Time.timeAsDouble - startTime;
|
|
|
|
private Action<string> logHandler;
|
|
|
|
void Start()
|
|
{
|
|
logHandler = PrintLog; // Send each log statement to Unity immediately during startup
|
|
|
|
systemModule = new SystemModule(this);
|
|
renderModule = new RenderModule(this);
|
|
|
|
LoadLibrary();
|
|
|
|
List<string> arguments = new List<string>
|
|
{
|
|
"",
|
|
"-window",
|
|
"-width", "1440",
|
|
"-height", "1080",
|
|
};
|
|
|
|
switch (BaseGame)
|
|
{
|
|
case MissionPack.Hipnotic:
|
|
arguments.Add("-hipnotic");
|
|
break;
|
|
case MissionPack.Rogue:
|
|
arguments.Add("-rogue");
|
|
break;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(ModDirectory))
|
|
{
|
|
arguments.AddRange(new[] { "-game", ModDirectory });
|
|
}
|
|
|
|
if (Debug.isDebugBuild)
|
|
{
|
|
arguments.AddRange(new[] { "+developer", "1" });
|
|
}
|
|
|
|
quakeParms = new QuakeParms
|
|
{
|
|
baseDir = Application.persistentDataPath,
|
|
userDir = null, // TODO set this if user prefs need to be stored somewhere specific, otherwise this will be the same as baseDir
|
|
numCpus = SystemInfo.processorCount,
|
|
errState = 0,
|
|
};
|
|
quakeParms.SetArguments(arguments.ToArray());
|
|
quakeParms.AllocateMemory(MemSize);
|
|
|
|
startTime = Time.timeAsDouble;
|
|
|
|
try
|
|
{
|
|
UniQuake_SetFmodSystem(AudioManager.Instance.FmodSystem.handle);
|
|
UniQuake_Init(quakeParms.ToNativePtr(), systemModule.CallbacksPtr, renderModule.CallbacksPtr);
|
|
initialized = true;
|
|
}
|
|
catch (QuakeException ex)
|
|
{
|
|
if (ex.ExitCode == 0)
|
|
Debug.Log(ex.Message);
|
|
else
|
|
Debug.LogException(ex);
|
|
|
|
Shutdown();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (!initialized)
|
|
return;
|
|
|
|
logHandler = CollectLog; // Collect and dump logs to Unity once per frame
|
|
|
|
try
|
|
{
|
|
UniQuake_Update(Time.deltaTime);
|
|
}
|
|
catch (QuakeException ex)
|
|
{
|
|
if (ex.ExitCode == 0)
|
|
Debug.Log(ex.Message);
|
|
else
|
|
Debug.LogException(ex);
|
|
|
|
Shutdown();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
Shutdown();
|
|
}
|
|
|
|
FlushLog();
|
|
}
|
|
|
|
private void Shutdown()
|
|
{
|
|
initialized = false;
|
|
UniQuake_Shutdown();
|
|
Destroy(this);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
renderModule.Destroy();
|
|
systemModule.Destroy();
|
|
|
|
if (quakeParms != null)
|
|
{
|
|
quakeParms.Destroy();
|
|
quakeParms = null;
|
|
}
|
|
|
|
FreeLibrary();
|
|
}
|
|
|
|
public void AddLog(string log)
|
|
{
|
|
logHandler?.Invoke(log);
|
|
}
|
|
|
|
private void PrintLog(string log)
|
|
{
|
|
Debug.Log(log);
|
|
}
|
|
|
|
private StringBuilder logBuffer = new StringBuilder();
|
|
|
|
private void CollectLog(string log)
|
|
{
|
|
logBuffer.Append(log);
|
|
}
|
|
|
|
private void FlushLog()
|
|
{
|
|
if (logBuffer.Length > 0)
|
|
{
|
|
Debug.Log(logBuffer.ToString());
|
|
logBuffer.Clear();
|
|
}
|
|
}
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void UniQuake_InitFunc(IntPtr parms, IntPtr sysCalls, IntPtr glCalls);
|
|
private UniQuake_InitFunc UniQuake_Init;
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void UniQuake_UpdateFunc(float deltaTime);
|
|
private UniQuake_UpdateFunc UniQuake_Update;
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void UniQuake_ShutdownFunc();
|
|
private UniQuake_ShutdownFunc UniQuake_Shutdown;
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void UniQuake_SetFmodSystemFunc(IntPtr fmodSystem);
|
|
private UniQuake_SetFmodSystemFunc UniQuake_SetFmodSystem;
|
|
|
|
private void LoadLibrary()
|
|
{
|
|
string dllFile = Path.Combine(Application.dataPath, DllPath);
|
|
|
|
// Experimental code to allow running multiple instances of Quake next to each other
|
|
// string dllName = Path.GetFileNameWithoutExtension(dllFile);
|
|
// string dllExt = Path.GetExtension(dllFile);
|
|
// string dllCopy = Path.Combine(Application.persistentDataPath, $"{dllName}{GetInstanceID()}{dllExt}");
|
|
// File.Copy(dllFile, dllCopy, true);
|
|
// dllFile = dllCopy;
|
|
|
|
libraryHandle = SystemLibrary.LoadLibrary(dllFile);
|
|
if (libraryHandle == IntPtr.Zero)
|
|
{
|
|
throw new DllNotFoundException($"Failed to load UniQuake library from path: {dllFile}, last error = {Marshal.GetLastWin32Error()}");
|
|
}
|
|
|
|
UniQuake_Init = LoadLibraryFunction<UniQuake_InitFunc>("UniQuake_Init");
|
|
UniQuake_Update = LoadLibraryFunction<UniQuake_UpdateFunc>("UniQuake_Update");
|
|
UniQuake_Shutdown = LoadLibraryFunction<UniQuake_ShutdownFunc>("UniQuake_Shutdown");
|
|
UniQuake_SetFmodSystem = LoadLibraryFunction<UniQuake_SetFmodSystemFunc>("UniQuake_SetFmodSystem");
|
|
}
|
|
|
|
private TDelegate LoadLibraryFunction<TDelegate>(string functionName)
|
|
{
|
|
IntPtr procAddress = SystemLibrary.GetProcAddress(libraryHandle, functionName);
|
|
if (procAddress == IntPtr.Zero)
|
|
{
|
|
throw new DllNotFoundException($"Could not find library function: {functionName}");
|
|
}
|
|
|
|
return Marshal.GetDelegateForFunctionPointer<TDelegate>(procAddress);
|
|
}
|
|
|
|
private void FreeLibrary()
|
|
{
|
|
if (libraryHandle != IntPtr.Zero)
|
|
{
|
|
SystemLibrary.FreeLibrary(libraryHandle);
|
|
libraryHandle = IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum MissionPack
|
|
{
|
|
Quake, // Vanilla Quake
|
|
Hipnotic, // Scourge of Armagon
|
|
Rogue, // Dissolution of Eternity
|
|
}
|
|
|
|
public class QuakeException: Exception
|
|
{
|
|
public int ExitCode { get; }
|
|
|
|
public QuakeException(string message, int exitCode = 0)
|
|
: base(message)
|
|
{
|
|
ExitCode = exitCode;
|
|
}
|
|
}
|