Skip to content

Commit

Permalink
Added support for Mono
Browse files Browse the repository at this point in the history
  • Loading branch information
slxdy committed Jun 8, 2024
1 parent efc135c commit c29a95c
Show file tree
Hide file tree
Showing 16 changed files with 302 additions and 73 deletions.
6 changes: 3 additions & 3 deletions TweaksLauncher.Game/Dobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ namespace TweaksLauncher;
public unsafe static partial class Dobby
{
[LibraryImport("dobby", EntryPoint = "DobbyPrepare")]
public static partial int Prepare(nint target, nint detour, nint* original);
private static partial int Prepare(nint target, nint detour, nint* original);

[LibraryImport("dobby", EntryPoint = "DobbyCommit")]
public static partial int Commit(nint target);
private static partial int Commit(nint target);

[LibraryImport("dobby", EntryPoint = "DobbyDestroy")]
public static partial int Destroy(nint target);
private static partial int Destroy(nint target);

public static nint Prepare(nint target, nint detour)
{
Expand Down
4 changes: 2 additions & 2 deletions TweaksLauncher.Game/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static void Log(string? message, Color baseColor = default, string? modul

if (message != string.Empty)
{
if (baseColor == default)
if (baseColor.A == 0)
baseColor = Color.LightCyan;

message = message.Pastel(baseColor);
Expand All @@ -63,7 +63,7 @@ public static void Log(string? message, Color baseColor = default, string? modul
var consoleLog = $"[{time.Pastel(Color.DarkGray)}]";
if (moduleName != null)
{
if (moduleColor == default)
if (moduleColor.A == 0)
moduleColor = Color.Magenta;

consoleLog += $"[{moduleName.Pastel(moduleColor)}]";
Expand Down
55 changes: 55 additions & 0 deletions TweaksLauncher.Game/Mono.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Runtime.InteropServices;

namespace TweaksLauncher;

internal static unsafe class Mono
{
public static void Init()
{
var lib = NativeLibrary.Load(Program.runtimePath);

mono_domain_assembly_open = Marshal.GetDelegateForFunctionPointer<mono_domain_assembly_open_sig>(NativeLibrary.GetExport(lib, "mono_domain_assembly_open"));
mono_assembly_get_image = Marshal.GetDelegateForFunctionPointer<mono_assembly_get_image_sig>(NativeLibrary.GetExport(lib, "mono_assembly_get_image"));
mono_class_from_name = Marshal.GetDelegateForFunctionPointer<mono_class_from_name_sig>(NativeLibrary.GetExport(lib, "mono_class_from_name"));
mono_class_get_method_from_name = Marshal.GetDelegateForFunctionPointer<mono_class_get_method_from_name_sig>(NativeLibrary.GetExport(lib, "mono_class_get_method_from_name"));
mono_runtime_invoke = Marshal.GetDelegateForFunctionPointer<mono_runtime_invoke_sig>(NativeLibrary.GetExport(lib, "mono_runtime_invoke"));
mono_string_new = Marshal.GetDelegateForFunctionPointer<mono_string_new_sig>(NativeLibrary.GetExport(lib, "mono_string_new"));
mono_add_internal_call = Marshal.GetDelegateForFunctionPointer<mono_add_internal_call_sig>(NativeLibrary.GetExport(lib, "mono_add_internal_call"));
mono_string_to_utf16 = Marshal.GetDelegateForFunctionPointer<mono_string_to_utf16_sig>(NativeLibrary.GetExport(lib, "mono_string_to_utf16"));
}

#pragma warning disable IDE1006 // Naming Styles
internal static mono_domain_assembly_open_sig mono_domain_assembly_open { get; private set; } = null!;
internal static mono_assembly_get_image_sig mono_assembly_get_image { get; private set; } = null!;
internal static mono_class_from_name_sig mono_class_from_name { get; private set; } = null!;
internal static mono_class_get_method_from_name_sig mono_class_get_method_from_name { get; private set; } = null!;
internal static mono_runtime_invoke_sig mono_runtime_invoke { get; private set; } = null!;
internal static mono_string_new_sig mono_string_new { get; private set; } = null!;
internal static mono_add_internal_call_sig mono_add_internal_call { get; private set; } = null!;
internal static mono_string_to_utf16_sig mono_string_to_utf16 { get; private set; } = null!;
#pragma warning restore IDE1006 // Naming Styles

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
internal delegate nint mono_domain_assembly_open_sig(nint domain, string name);

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate nint mono_assembly_get_image_sig(nint assembly);

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
internal delegate nint mono_class_from_name_sig(nint image, string nameSpace, string name);

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
internal delegate nint mono_class_get_method_from_name_sig(nint clas, string name, int argsCount);

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate nint mono_runtime_invoke_sig(nint method, nint obj, nint* args, nint* exception);

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
internal delegate nint mono_string_new_sig(nint domain, string value);

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
internal delegate nint mono_add_internal_call_sig(string methodName, nint func);

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
internal delegate string? mono_string_to_utf16_sig(nint monoString);
}
84 changes: 84 additions & 0 deletions TweaksLauncher.Game/MonoHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;

namespace TweaksLauncher;

internal static unsafe class MonoHandler
{
[NotNull] private static Dobby.Patch<MonoJitInitSig>? monoJitInit = null;

private static LogImplSig? logImpl;

internal static int Start()
{
monoJitInit = Dobby.CreatePatch<MonoJitInitSig>(Program.runtimePath, "mono_jit_init_version", OnDomainInit);

return GameManager.Start();
}

private static void LoadModHandler(nint domain)
{
var handlerPath = Path.Combine(Program.baseDir, "bin", "Mono", "TweaksLauncher.dll");
if (!File.Exists(handlerPath))
{
Logger.Log($"Could not find the mod handler. Please reinstall TweaksLauncher.", Color.Red);
return;
}

Mono.Init();

var modHandlerAsm = Mono.mono_domain_assembly_open(domain, handlerPath);
if (modHandlerAsm == 0)
{
Logger.Log($"Failed to load Mod Handler into the Mono domain.", Color.Red);
return;
}

var modHandlerImg = Mono.mono_assembly_get_image(modHandlerAsm);
var modHandlerClass = Mono.mono_class_from_name(modHandlerImg, "TweaksLauncher", "ModHandler");
var startMethod = Mono.mono_class_get_method_from_name(modHandlerClass, "Start", 3);

if (startMethod == 0)
{
Logger.Log($"Failed to get the Mod Handler's Start method'.", Color.Red);
return;
}

logImpl = new LogImplSig(LogImpl);
var logImplPtr = Marshal.GetFunctionPointerForDelegate(logImpl);
Mono.mono_add_internal_call("TweaksLauncher.ModHandler::LogInternal", logImplPtr);

var baseDir = Mono.mono_string_new(domain, Program.baseDir);
var gameName = Mono.mono_string_new(domain, Program.gameName);
var gameDir = Mono.mono_string_new(domain, Program.gamePath);

var args = stackalloc nint[3];
args[0] = baseDir;
args[1] = gameName;
args[2] = gameDir;

Mono.mono_runtime_invoke(startMethod, 0, args, null);
}

private static void LogImpl(nint monoMessage, int baseColor, nint monoModuleName, int moduleColor)
{
Logger.Log(monoMessage == 0 ? null : Mono.mono_string_to_utf16(monoMessage), Color.FromArgb(baseColor), monoModuleName == 0 ? null : Mono.mono_string_to_utf16(monoModuleName), Color.FromArgb(moduleColor));
}

private static nint OnDomainInit(nint domainName, nint a)
{
monoJitInit.Destroy();

Logger.Log("Creating Mono Domain");

var domain = monoJitInit.Original(domainName, a);

LoadModHandler(domain);

return domain;
}

internal delegate nint MonoJitInitSig(nint domainName, nint a);
private delegate void LogImplSig(nint monoMessage, int baseColor, nint monoModuleName, int moduleColor);
}
6 changes: 5 additions & 1 deletion TweaksLauncher.Game/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace TweaksLauncher;

internal static class Program
{
internal static readonly string baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..");
internal static readonly string baseDir = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", ".."));

internal static string runtimePath = null!;
internal static string gamePath = null!;
Expand Down Expand Up @@ -117,6 +117,10 @@ private static int Main(string[] args)
case RuntimeType.Il2Cpp:
return Il2CppHandler.Start();

case RuntimeType.Mono:
case RuntimeType.OldMono:
return MonoHandler.Start();

default:
Logger.Log($"Game runtime not supported.", Color.Red);
return 8;
Expand Down
5 changes: 4 additions & 1 deletion TweaksLauncher.ModHandler/Dobby.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Runtime.InteropServices;
#if NETCOREAPP
using System;
using System.Runtime.InteropServices;

namespace TweaksLauncher;

Expand Down Expand Up @@ -95,3 +97,4 @@ public void Destroy()
}
}
}
#endif
24 changes: 23 additions & 1 deletion TweaksLauncher.ModHandler/IMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,34 @@

/// <summary>
/// The main interface that defines a mod type. Gives the mod the opportunity to initialize early. Multiple mod types are allowed.
/// <para>
/// <b>Requires a </b><c><see langword="static void"/> Initialize(<see cref="LoadedMod"/> mod)</c><b> method implementation.</b>
/// </para>
/// <para>
/// Example:
/// <code>
///<see langword="using"/> <see cref="TweaksLauncher"/>;
///<br></br><br></br>
///<see langword="namespace"/> MyMod;
///<br></br><br></br>
///<see langword="public class"/> Main : <see cref="IMod"/><br></br>
///{
/// // Runs early, when Unity has initialized, but before the first game scene is loaded.
/// <see langword="public static void"/> Initialize(<see cref="LoadedMod"/> mod)
/// {
/// <see cref="ModLogger"/>.Log("Hello World!");
/// }
///}
/// </code>
/// </para>
/// </summary>
public interface IMod
{
#if NETCOREAPP
/// <summary>
/// Runs early, when Unity has initialized, but before the first game scene is loaded
/// </summary>
/// <param name="mod">The returned mod instance</param>
/// <param name="mod">The current mod instance</param>
public static abstract void Initialize(LoadedMod mod);
#endif
}
9 changes: 6 additions & 3 deletions TweaksLauncher.ModHandler/Il2Cpp/DobbyDetourProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#if IL2CPP

using Il2CppInterop.Runtime.Injection;
using System;
using System.Runtime.InteropServices;

namespace TweaksLauncher.Il2Cpp;
Expand Down Expand Up @@ -37,16 +38,18 @@ public void Apply()
IsPrepared = true;
}

Dobby.Commit(Target);
_ = Dobby.Commit(Target);
IsApplied = true;
}

public void Dispose()
void IDisposable.Dispose()
{
GC.SuppressFinalize(this);

if (!IsApplied)
return;

Dobby.Destroy(Target);
_ = Dobby.Destroy(Target);
IsApplied = false;
}

Expand Down
31 changes: 27 additions & 4 deletions TweaksLauncher.ModHandler/LoadedMod.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using System;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Reflection;



#if IL2CPP
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
#endif

namespace TweaksLauncher;

public class LoadedMod
Expand Down Expand Up @@ -36,6 +43,7 @@ internal LoadedMod(string name, string modPath, Assembly modAssembly, ReadOnlyCo
ModPath = modPath;
ModAssembly = modAssembly;

#if IL2CPP
// Automatically register Il2Cpp types
foreach (var type in modAssembly.GetTypes())
{
Expand All @@ -44,6 +52,7 @@ internal LoadedMod(string name, string modPath, Assembly modAssembly, ReadOnlyCo
ClassInjector.RegisterTypeInIl2Cpp(type);
}
}
#endif

// Automatically register all Harmony patches
Harmony = Harmony.CreateAndPatchAll(modAssembly, modAssembly.FullName);
Expand All @@ -54,14 +63,28 @@ internal LoadedMod(string name, string modPath, Assembly modAssembly, ReadOnlyCo
public void Init()
{
foreach (var iMod in ModInterfaces)
iMod.Initialize(this);
{
try
{
iMod.Initialize(this);
}
catch (MissingMethodException)
{
ModHandler.Log($"Mod interface doesn't implement an 'Initialize' method: '{iMod.InterfaceType.FullName}'", Color.Red);
}
catch (Exception ex)
{
ModHandler.Log($"Mod interface failed to initialize: '{iMod.InterfaceType.FullName}'", Color.Red);
ModHandler.Log(ex.ToString(), Color.Red);
}
}
}

public void InitUnitySingletons()
{
foreach (var type in ModAssembly.GetTypes())
{
if (type.GetCustomAttribute<UnitySingletonAttribute>() != null && type.IsAssignableTo(UnityTools.MonoBehaviour))
if (type.GetCustomAttributes(typeof(UnitySingletonAttribute), false).Length != 0 && UnityTools.MonoBehaviour.IsAssignableFrom(type))
{
UnityTools.CreateComponentSingleton(type);
}
Expand Down
Loading

0 comments on commit c29a95c

Please sign in to comment.