Skip to content

Commit

Permalink
Merge pull request #861 from aldelaro5/mono-debug-server
Browse files Browse the repository at this point in the history
Implement a mono debug server feature
  • Loading branch information
HerpDerpinstine authored Feb 12, 2025
2 parents 2db31d0 + ef75a22 commit 63dee71
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 9 deletions.
10 changes: 10 additions & 0 deletions MelonLoader.Bootstrap/Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ private static void InitConfig()
if (uint.TryParse(ArgParser.GetValue("melonloader.maxlogs"), out var maxLogs))
LoaderConfig.Current.Logs.MaxLogs = maxLogs;

if (ArgParser.IsDefined("melonloader.debugsuspend"))
LoaderConfig.Current.MonoDebugServer.DebugSuspend = true;

var debugIpAddress = ArgParser.GetValue("melonloader.debugipaddress");
if (debugIpAddress != null)
LoaderConfig.Current.MonoDebugServer.DebugIpAddress = debugIpAddress;

if (uint.TryParse(ArgParser.GetValue("melonloader.debugport"), out var debugPort))
LoaderConfig.Current.MonoDebugServer.DebugPort = debugPort;

var unityVersionOverride = ArgParser.GetValue("melonloader.unityversion");
if (unityVersionOverride != null)
LoaderConfig.Current.UnityEngine.VersionOverride = unityVersionOverride;
Expand Down
75 changes: 71 additions & 4 deletions MelonLoader.Bootstrap/RuntimeHandlers/Mono/MonoHandler.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
using MelonLoader.Bootstrap.Utils;
using System.Diagnostics;
using System.Text;

namespace MelonLoader.Bootstrap.RuntimeHandlers.Mono;

internal static class MonoHandler
{
private static Dobby.Patch<MonoLib.JitInitVersionFn>? initPatch;
private static Dobby.Patch<MonoLib.JitParseOptionsFn>? jitParseOptionsPatch;
private static Dobby.Patch<MonoLib.DebugInitFn>? debugInitPatch;
private static Dobby.Patch<MonoLib.RuntimeInvokeFn>? invokePatch;

private static nint assemblyManagerResolve;
private static nint assemblyManagerLoadInfo;
private static nint coreStart;
private static bool debugInitCalled;

internal static nint Domain { get; private set; }
internal static MonoLib Mono { get; private set; } = null!;

private const string MonoDebugArgsStart = "--debugger-agent=transport=dt_socket,server=y,address=";
private const string MonoDebugNoSuspendArg = ",suspend=n";
private const string MonoDebugNoSuspendArgOldMono = ",suspend=n,defer=y";

public static bool TryInitialize()
{
var monoLib = MonoLib.TryLoad(Core.GameDir);
Expand All @@ -27,10 +35,26 @@ public static bool TryInitialize()

MelonDebug.Log("Patching mono init");
initPatch = Dobby.CreatePatch<MonoLib.JitInitVersionFn>(Mono.JitInitVersionPtr, InitDetour);
MelonDebug.Log("Patching mono jit parse options");
jitParseOptionsPatch = Dobby.CreatePatch<MonoLib.JitParseOptionsFn>(Mono.JitParseOptionsPtr, JitParseOptionsDetour);
MelonDebug.Log("Patching mono debug init");
debugInitPatch = Dobby.CreatePatch<MonoLib.DebugInitFn>(Mono.DebugInitPtr, DebugInitDetour);

return true;
}

private static void DebugInitDetour(MonoLib.MonoDebugFormat format)
{
if (debugInitPatch == null)
return;

debugInitPatch.Destroy();

debugInitCalled = true;

debugInitPatch.Original(format);
}

private static nint InitDetour(nint name, nint b)
{
if (initPatch == null)
Expand All @@ -41,13 +65,17 @@ private static nint InitDetour(nint name, nint b)
ConsoleHandler.ResetHandles();
MelonDebug.Log("In init detour");

Domain = initPatch.Original(name, b);
JitParseOptionsDetour(0, []);
bool debuggerAlreadyEnabled = debugInitCalled || (Mono.DebugEnabled != null && Mono.DebugEnabled());

if (LoaderConfig.Current.Loader.DebugMode && Mono.DebugDomainCreate != null)
if (LoaderConfig.Current.Loader.DebugMode && !debuggerAlreadyEnabled)
{
MelonDebug.Log("Creating Mono Debug Domain");
Mono.DebugDomainCreate(Domain);
MelonDebug.Log("Initialising mono debugger");
Mono.DebugInit(MonoLib.MonoDebugFormat.MONO_DEBUG_FORMAT_MONO);
}

MelonDebug.Log("Original init jit version");
Domain = initPatch.Original(name, b);

MelonDebug.Log("Setting Mono Main Thread");
Mono.SetCurrentThreadAsMain();
Expand All @@ -60,10 +88,49 @@ private static nint InitDetour(nint name, nint b)
}

InitializeManaged();
jitParseOptionsPatch?.Destroy();

return Domain;
}

private static void JitParseOptionsDetour(IntPtr argc, string[] argv)
{
if (jitParseOptionsPatch == null)
return;

if (!LoaderConfig.Current.Loader.DebugMode)
{
jitParseOptionsPatch.Original(argc, argv);
return;
}

string newArgs;
string? dnSpyEnv = Environment.GetEnvironmentVariable("DNSPY_UNITY_DBG2");
if (dnSpyEnv == null)
{
StringBuilder newArgsSb = new(MonoDebugArgsStart);
newArgsSb.Append(LoaderConfig.Current.MonoDebugServer.DebugIpAddress);
newArgsSb.Append(':');
newArgsSb.Append(LoaderConfig.Current.MonoDebugServer.DebugPort);
if (!LoaderConfig.Current.MonoDebugServer.DebugSuspend)
newArgsSb.Append(Mono.IsOld ? MonoDebugNoSuspendArgOldMono : MonoDebugNoSuspendArg);
newArgs = newArgsSb.ToString();
}
else
{
newArgs = dnSpyEnv;
}

string[] newArgv = new string[argc + 1];
Array.Copy(argv, 0, newArgv, 0, argc);
argc++;
newArgv[argc - 1] = newArgs;

MelonDebug.Log($"Adding jit option: {string.Join(' ', newArgs)}");

jitParseOptionsPatch.Original(argc, newArgv);
}

private static unsafe void InitializeManaged()
{
MelonDebug.Log("Initializing managed assemblies");
Expand Down
30 changes: 25 additions & 5 deletions MelonLoader.Bootstrap/RuntimeHandlers/Mono/MonoLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ internal class MonoLib
public required bool IsOld { get; init; }

public required nint JitInitVersionPtr { get; init; }
public required nint DebugInitPtr { get; init; }
public required nint JitParseOptionsPtr { get; init; }
public required nint RuntimeInvokePtr { get; init; }

public required ThreadCurrentFn ThreadCurrent { get; init; }
public required DebugInitFn DebugInit { get; init; }
public required ThreadSetMainFn ThreadSetMain { get; init; }
public required RuntimeInvokeFn RuntimeInvoke { get; init; }
public required StringNewFn StringNew { get; init; }
Expand All @@ -49,8 +52,8 @@ internal class MonoLib
public required InstallAssemblySearchHookFn InstallAssemblySearchHook { get; init; }
public required InstallAssemblyLoadHookFn InstallAssemblyLoadHook { get; init; }

public DebugDomainCreateFn? DebugDomainCreate { get; init; }
public DomainSetConfigFn? DomainSetConfig { get; init; }
public DebugEnabledFn? DebugEnabled { get; init; }

public static MonoLib? TryLoad(string searchDir)
{
Expand All @@ -73,6 +76,8 @@ internal class MonoLib

if (!NativeLibrary.TryGetExport(hRuntime, "mono_jit_init_version", out var jitInitVersionPtr)
|| !NativeLibrary.TryGetExport(hRuntime, "mono_runtime_invoke", out var runtimeInvokePtr)
|| !NativeLibrary.TryGetExport(hRuntime, "mono_jit_parse_options", out var jitParseOptionsPtr)
|| !NativeLibrary.TryGetExport(hRuntime, "mono_debug_init", out var debugInitPtr)
|| !NativeFunc.GetExport<ThreadCurrentFn>(hRuntime, "mono_thread_current", out var threadCurrent)
|| !NativeFunc.GetExport<ThreadSetMainFn>(hRuntime, "mono_thread_set_main", out var threadSetMain)
|| !NativeFunc.GetExport<StringNewFn>(hRuntime, "mono_string_new", out var stringNew)
Expand All @@ -89,8 +94,9 @@ internal class MonoLib
return null;

var runtimeInvoke = Marshal.GetDelegateForFunctionPointer<RuntimeInvokeFn>(runtimeInvokePtr);
var debugInit = Marshal.GetDelegateForFunctionPointer<DebugInitFn>(debugInitPtr);

var debugDomainCreate = NativeFunc.GetExport<DebugDomainCreateFn>(hRuntime, "mono_debug_domain_create");
var debugEnabled = NativeFunc.GetExport<DebugEnabledFn>(hRuntime, "mono_debug_enabled");
var domainSetConfig = NativeFunc.GetExport<DomainSetConfigFn>(hRuntime, "mono_domain_set_config");

return new()
Expand All @@ -99,8 +105,12 @@ internal class MonoLib
IsOld = isOld,
RuntimeInvoke = runtimeInvoke,
JitInitVersionPtr = jitInitVersionPtr,
JitParseOptionsPtr = jitParseOptionsPtr,
RuntimeInvokePtr = runtimeInvokePtr,
ThreadCurrent = threadCurrent,
DebugInitPtr = debugInitPtr,
DebugEnabled = debugEnabled,
DebugInit = debugInit,
ThreadSetMain = threadSetMain,
StringNew = stringNew,
AssemblyGetObject = assemblyGetObject,
Expand All @@ -113,8 +123,7 @@ internal class MonoLib
InstallAssemblyPreloadHook = installAssemblyPreloadHook,
InstallAssemblySearchHook = installAssemblySearchHook,
InstallAssemblyLoadHook = installAssemblyLoadHook,
DomainSetConfig = domainSetConfig,
DebugDomainCreate = debugDomainCreate
DomainSetConfig = domainSetConfig
};
}

Expand Down Expand Up @@ -195,7 +204,11 @@ public void InstallAssemblyHooks(AssemblyPreloadHookFn? preloadHook, AssemblySea
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate nint JitInitVersionFn(nint name, nint b);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DebugDomainCreateFn(nint domain);
public delegate void JitParseOptionsFn(nint argc, string[] argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DebugInitFn(MonoDebugFormat format);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool DebugEnabledFn();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate nint ThreadCurrentFn();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
Expand Down Expand Up @@ -244,6 +257,13 @@ public void InstallAssemblyHooks(AssemblyPreloadHookFn? preloadHook, AssemblySea
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void AssemblyLoadHookFn(nint monoAssembly, nint userData);

public enum MonoDebugFormat
{
MONO_DEBUG_FORMAT_NONE,
MONO_DEBUG_FORMAT_MONO,
MONO_DEBUG_FORMAT_DEBUGGER
}

[StructLayout(LayoutKind.Sequential)]
public unsafe struct AssemblyName
{
Expand Down
19 changes: 19 additions & 0 deletions MelonLoader/LoaderConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public class LoaderConfig

[TomlProperty("logs")]
public LogsConfig Logs { get; internal set; } = new();

[TomlProperty("mono_debug_server")]
public MonoDebugServerConfig MonoDebugServer { get; internal set; } = new();

[TomlProperty("unityengine")]
public UnityEngineConfig UnityEngine { get; internal set; } = new();
Expand Down Expand Up @@ -95,6 +98,22 @@ public class LogsConfig
public uint MaxLogs { get; internal set; } = 10;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class MonoDebugServerConfig
{
[TomlProperty("debug_suspend")]
[TomlPrecedingComment("Let the Mono debug server wait until a debugger is attached when debug_mode is true (only for Mono games). Equivalent to the '--melonloader.debugsuspend' launch option")]
public bool DebugSuspend { get; internal set; }

[TomlProperty("debug_ip_address")]
[TomlPrecedingComment("The IP address the Mono debug server will listen to when debug_mode is true (only for Mono games). Equivalent to the '--melonloader.debugipaddress' launch option")]
public string DebugIpAddress { get; internal set; } = "127.0.0.1";

[TomlProperty("debug_port")]
[TomlPrecedingComment("The port the Mono debug server will listen to when debug_mode is true (only for Mono games). Equivalent to the '--melonloader.debugport' launch option")]
public uint DebugPort { get; internal set; } = 55555;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class UnityEngineConfig
{
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ dont_set_title = false
# Sets the maximum amount of log files in the Logs folder (Default: 10). Equivalent to the '--melonloader.maxlogs' launch option
max_logs = 10

[mono_debug_server]
# Let the Mono debug server wait until a debugger is attached when debug_mode is true (only for Mono games). Equivalent to the '--melonloader.debugsuspend' launch option
debug_suspend = false
# The IP address the Mono debug server will listen to when debug_mode is true (only for Mono games). Equivalent to the '--melonloader.debugipaddress' launch option
debug_ip_address = "127.0.0.1"
# The port the Mono debug server will listen to when debug_mode is true (only for Mono games). Equivalent to the '--melonloader.debugport' launch option
debug_port = 10000

[unityengine]
# Overrides the detected UnityEngine version. Equivalent to the '--melonloader.unityversion' launch option
version_override = ""
Expand Down Expand Up @@ -192,6 +200,9 @@ enable_cpp2il_native_method_detector = false
| --melonloader.hideconsole | Hides the Console |
| --melonloader.hidewarnings | Hides Warnings from Displaying |
| --melonloader.debug | Debug Mode |
| --melonloader.debugsuspend | Let the Mono debug server wait until a debugger is attached when in Debug Mode (only for Mono games) |
| --melonloader.debugipaddress | The IP address the Mono debug server will listen to when in Debug Mode (only for Mono games) |
| --melonloader.debugport | The port the Mono debug server will listen to when in Debug Mode (only for Mono games) |
| --melonloader.maxlogs | Max Log Files [ Default: 10 ] [ NoCap: 0 ] |
| --melonloader.loadmodeplugins | Load Mode for Plugins [ Default: 0 ] |
| --melonloader.loadmodemods | Load Mode for Mods [ Default: 0 ] |
Expand Down

0 comments on commit 63dee71

Please sign in to comment.