84b6f56a50
Initialize the DataCenter-RustBridge submodule to integrate the Rust bridge plugin for the DataCenter project. This enables using the external Rust-based components as a git submodule.
264 lines
9.0 KiB
C#
264 lines
9.0 KiB
C#
using MelonLoader;
|
|
using MelonLoader.Utils;
|
|
using System;
|
|
using System.IO;
|
|
using HarmonyLib;
|
|
using UnityEngine;
|
|
|
|
[assembly: MelonInfo(typeof(DataCenterModLoader.Core), "gregCore", "1.0.0", "TeamGreg")]
|
|
[assembly: MelonGame("", "Data Center")]
|
|
|
|
namespace DataCenterModLoader;
|
|
|
|
// file-based crash logger, never throws
|
|
public static class CrashLog
|
|
{
|
|
private static string _logPath;
|
|
private static readonly object _lock = new();
|
|
|
|
public static void Init(string gameRoot)
|
|
{
|
|
try
|
|
{
|
|
_logPath = Path.Combine(gameRoot, "dc_modloader_debug.log");
|
|
var header =
|
|
$"===== RustBridge Debug Log ====={Environment.NewLine}" +
|
|
$"Started: {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}{Environment.NewLine}" +
|
|
$"========================================={Environment.NewLine}";
|
|
File.WriteAllText(_logPath, header);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
public static void Log(string msg)
|
|
{
|
|
try
|
|
{
|
|
if (_logPath == null) return;
|
|
lock (_lock)
|
|
{
|
|
File.AppendAllText(_logPath,
|
|
$"[{DateTime.Now:HH:mm:ss.fff}] {msg}{Environment.NewLine}");
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
public static void LogException(string context, Exception ex)
|
|
{
|
|
try
|
|
{
|
|
if (_logPath == null) return;
|
|
lock (_lock)
|
|
{
|
|
File.AppendAllText(_logPath,
|
|
$"[{DateTime.Now:HH:mm:ss.fff}] EXCEPTION in {context}:{Environment.NewLine}" +
|
|
$" Type: {ex.GetType().FullName}{Environment.NewLine}" +
|
|
$" Message: {ex.Message}{Environment.NewLine}" +
|
|
$" StackTrace:{Environment.NewLine}{ex.StackTrace}{Environment.NewLine}" +
|
|
(ex.InnerException != null
|
|
? $" InnerException: {ex.InnerException.GetType().FullName}: {ex.InnerException.Message}{Environment.NewLine}" +
|
|
$" InnerStackTrace:{Environment.NewLine}{ex.InnerException.StackTrace}{Environment.NewLine}"
|
|
: "") +
|
|
Environment.NewLine);
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
public class Core : MelonMod
|
|
{
|
|
public static Core Instance { get; private set; }
|
|
|
|
private FFIBridge _ffiBridge;
|
|
private MultiplayerBridge _mpBridge;
|
|
private string _modsPath;
|
|
|
|
public override void OnInitializeMelon()
|
|
{
|
|
try
|
|
{
|
|
Instance = this;
|
|
|
|
CrashLog.Init(MelonEnvironment.GameRootDirectory);
|
|
CrashLog.Log("step: CrashLog initialized");
|
|
|
|
_modsPath = Path.Combine(MelonEnvironment.GameRootDirectory, "Mods", "native");
|
|
|
|
LoggerInstance.Msg("╔══════════════════════════════════════════╗");
|
|
LoggerInstance.Msg("║ Rust Bridge v0.1.0 ║");
|
|
LoggerInstance.Msg("║ Rust FFI Bridge Active ║");
|
|
LoggerInstance.Msg("╚══════════════════════════════════════════╝");
|
|
|
|
if (!Directory.Exists(_modsPath))
|
|
{
|
|
Directory.CreateDirectory(_modsPath);
|
|
LoggerInstance.Msg($"Created Mods/native directory: {_modsPath}");
|
|
}
|
|
|
|
CrashLog.Log("step: creating FFIBridge");
|
|
_ffiBridge = new FFIBridge(LoggerInstance, _modsPath);
|
|
|
|
CrashLog.Log("step: initializing EventDispatcher");
|
|
EventDispatcher.Initialize(_ffiBridge, LoggerInstance);
|
|
|
|
CrashLog.Log("step: applying Harmony patches");
|
|
try
|
|
{
|
|
HarmonyInstance.PatchAll(typeof(Core).Assembly);
|
|
LoggerInstance.Msg("Harmony patches applied.");
|
|
CrashLog.Log("step: Harmony patches applied successfully");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LoggerInstance.Error($"Failed to apply Harmony patches: {ex.Message}");
|
|
LoggerInstance.Msg("Continuing without full event support.");
|
|
CrashLog.LogException("Harmony patching", ex);
|
|
}
|
|
|
|
CrashLog.Log("step: initializing ModConfigSystem");
|
|
ModConfigSystem.Initialize(LoggerInstance);
|
|
|
|
CrashLog.Log("step: loading all mods");
|
|
_ffiBridge.LoadAllMods();
|
|
|
|
if (!_ffiBridge.IsRustAvailable)
|
|
{
|
|
LoggerInstance.Warning("═══════════════════════════════════════════════════════════");
|
|
LoggerInstance.Warning("⚠ Rust Bridge: Running in C# Compatibility Mode Only");
|
|
LoggerInstance.Warning($" → {_ffiBridge.RustStatusMessage}");
|
|
LoggerInstance.Warning("═══════════════════════════════════════════════════════════");
|
|
}
|
|
else
|
|
{
|
|
LoggerInstance.Msg($"✓ {_ffiBridge.RustStatusMessage}");
|
|
}
|
|
|
|
|
|
var mpDllPath = Path.Combine(_modsPath, "dc_multiplayer.dll");
|
|
if (File.Exists(mpDllPath))
|
|
{
|
|
_mpBridge = new MultiplayerBridge(LoggerInstance);
|
|
}
|
|
|
|
LoggerInstance.Msg("Modloader initialization complete.");
|
|
CrashLog.Log("step: OnInitializeMelon complete");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CrashLog.LogException("OnInitializeMelon", ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
|
|
{
|
|
try
|
|
{
|
|
_ffiBridge?.OnSceneLoaded(sceneName);
|
|
_mpBridge?.OnSceneLoaded(sceneName);
|
|
ModConfigSystem.OnSceneLoaded(sceneName);
|
|
CustomEmployeeManager.ResetInjectionState();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CrashLog.LogException("OnSceneWasLoaded", ex);
|
|
}
|
|
}
|
|
|
|
// Drain the TechnicianManager.pendingDispatches queue periodically so that jobs
|
|
// queued by the game's own "Add all broken devices" button (or restored from a
|
|
// save) are assigned to free technicians even when no CommandCenterOperator is hired.
|
|
private float _queueDrainTimer = 0f;
|
|
private const float QUEUE_DRAIN_INTERVAL = 2f;
|
|
|
|
public override void OnUpdate()
|
|
{
|
|
try
|
|
{
|
|
_ffiBridge?.OnUpdate(Time.deltaTime);
|
|
_mpBridge?.OnUpdate(Time.deltaTime);
|
|
ModConfigSystem.OnUpdate(Time.deltaTime);
|
|
CustomEmployeeManager.ReregisterSalariesIfNeeded();
|
|
EntityManager.Update();
|
|
CarryStateMonitor.Update();
|
|
|
|
// Periodically force-process any pending dispatch queue entries that
|
|
// the game's ProcessDispatchQueue coroutine would normally handle only
|
|
// when a CommandCenterOperator is hired.
|
|
_queueDrainTimer += Time.deltaTime;
|
|
if (_queueDrainTimer >= QUEUE_DRAIN_INTERVAL)
|
|
{
|
|
_queueDrainTimer = 0f;
|
|
try
|
|
{
|
|
var tm = Il2Cpp.TechnicianManager.instance;
|
|
if (tm != null)
|
|
GameHooks.ForceProcessPendingQueue(tm);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CrashLog.LogException("OnUpdate", ex);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public override void OnFixedUpdate()
|
|
{
|
|
try
|
|
{
|
|
_ffiBridge?.OnFixedUpdate(Time.fixedDeltaTime);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CrashLog.LogException("OnFixedUpdate", ex);
|
|
}
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
try
|
|
{
|
|
_mpBridge?.DrawGUI();
|
|
ModConfigSystem.DrawGUI();
|
|
|
|
// Show Rust Bridge status in top-left corner
|
|
if (_ffiBridge != null && !_ffiBridge.IsRustAvailable)
|
|
{
|
|
var oldColor = GUI.color;
|
|
GUI.color = new Color(1f, 0.6f, 0f, 0.8f);
|
|
GUI.Label(new Rect(10, 10, 400, 20), $"[RustBridge] C# Compatibility Mode");
|
|
GUI.color = oldColor;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CrashLog.LogException("OnGUI", ex);
|
|
}
|
|
}
|
|
|
|
public override void OnApplicationQuit()
|
|
{
|
|
try
|
|
{
|
|
LoggerInstance.Msg("Shutting down modloader...");
|
|
CrashLog.Log("step: OnApplicationQuit starting");
|
|
EntityManager.DestroyAll();
|
|
_mpBridge?.Shutdown();
|
|
ModConfigSystem.Shutdown();
|
|
_ffiBridge?.Shutdown();
|
|
_ffiBridge?.Dispose();
|
|
CrashLog.Log("step: OnApplicationQuit complete");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CrashLog.LogException("OnApplicationQuit", ex);
|
|
}
|
|
}
|
|
}
|