From e33731e84369c1ec5cbd0b640e05198c8cbbb357 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 13:29:46 -0400 Subject: [PATCH 1/9] Correct Harmony reference in patching guide --- docs/modders/patching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modders/patching.md b/docs/modders/patching.md index 8820fb3..3d51b09 100644 --- a/docs/modders/patching.md +++ b/docs/modders/patching.md @@ -46,7 +46,7 @@ public class Example This will mostly be a repeat of what the [Harmony Docs](https://harmony.pardeike.net/articles/patching.html) say. But here we go!
For this example, I will be patching `Example.PrivateMethod(int param1)`. -Harmony is included in `MelonLoader.dll`, so there's no need to download the nuget package or reference the dll. +Harmony is included in the MelonLoader folder, named `0Harmony.dll`, be sure to reference it before continuing. Let's create a new class. It can be named anything and have any access modifiers, however, we must add the `HarmonyPatch` attribute for it to be picked up by Harmony.
In the attribute, we specify what method we would like to patch. This is similar to [getting a method using Reflection](modders/reflection?id=calling-a-method-using-reflection).
From a7d6fba81884f4b966bb1a5953320b5cd1220e5b Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 13:41:26 -0400 Subject: [PATCH 2/9] Correct Il2Cpp capitalization --- docs/modders/il2cppdifferences.md | 8 ++++---- docs/modders/quickstart.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modders/il2cppdifferences.md b/docs/modders/il2cppdifferences.md index 61fdaed..d3895a3 100644 --- a/docs/modders/il2cppdifferences.md +++ b/docs/modders/il2cppdifferences.md @@ -4,11 +4,11 @@ MelonLoader have some "small" things that doesn't work the exact same as if you > If you find something that doesn't seems natural with Il2Cpp and that isn't listed here, please ping _loukylor#0001_ on the [MelonLoader Discord](https://discord.gg/2Wn3N2P). -### Il2cppInterop Generated Names +### Il2CppInterop Generated Names ?> You may ignore this section if your game is not obfuscated -Il2cppInterop is what is used to generate proxy mono assemblies from Il2Cpp code. It will automatically assign auto-generated to obfuscated names. +Il2CppInterop is what is used to generate proxy mono assemblies from Il2Cpp code. It will automatically assign auto-generated to obfuscated names. The names are generated following certain rules: For fields and properties: @@ -46,7 +46,7 @@ Note that `MelonLoader.RegisterTypeInIl2Cpp` will register all parent types if a Here is a very basic example: ```cs -// You must reference `Il2cppInterop.Runtime.dll` for this to work +// You must reference `Il2CppInterop.Runtime.dll` for this to work using Il2CppInterop.Runtime; [RegisterTypeInIl2Cpp] @@ -124,7 +124,7 @@ MelonCoroutines.Stop(routine); In case you want to run an Il2Cpp method taking a type, you may want to use `.GetType()`.
`.GetType()` would actually returns the Mono type, and not the original Il2Cpp type. To do so, we need to replace it with `Il2CppInterop.Runtime.Il2CppType.Of()`. ```cs -// You must reference `Il2cppInterop.Runtime.dll` for this. +// You must reference `Il2CppInterop.Runtime.dll` for this. using Il2CppInterop.Runtime; Resources.FindObjectsOfTypeAll(Il2CppType.Of()); diff --git a/docs/modders/quickstart.md b/docs/modders/quickstart.md index 3519a86..8037a5a 100644 --- a/docs/modders/quickstart.md +++ b/docs/modders/quickstart.md @@ -185,8 +185,8 @@ For games using the IL2CPP runtime, all the game/Unity assemblies can be found i Since IL2CPP converts all game assemblies to C++, MelonLoader is using [Il2CppInterop](https://github.com/BepInEx/Il2CppInterop), an IL2CPP proxy assembly generator which allows us to use IL2CPP assemblies from C#. Before we can use any assemblies generated by the Unhollower, it's required to reference the following assemblies first: -- `il2cppmscorlib` -- `Il2cppInterop` +- `Il2Cppmscorlib` +- `Il2CppInterop` At this point, you're ready to make your first functional Melon. From 4fb8d755989d9db580ed5cd291bb67c55dc02e58 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 13:41:51 -0400 Subject: [PATCH 3/9] Have example mod use proper mod instance --- docs/modders/quickstart.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/modders/quickstart.md b/docs/modders/quickstart.md index 8037a5a..b099fa5 100644 --- a/docs/modders/quickstart.md +++ b/docs/modders/quickstart.md @@ -205,8 +205,6 @@ namespace TimeFreezer { public class TimeFreezerMod : MelonMod { - public static TimeFreezerMod instance; - private static KeyCode freezeToggleKey; private static bool frozen; @@ -214,7 +212,6 @@ namespace TimeFreezer public override void OnEarlyInitializeMelon() { - instance = this; freezeToggleKey = KeyCode.Space; } @@ -237,7 +234,7 @@ namespace TimeFreezer if (frozen) { - instance.LoggerInstance.Msg("Freezing"); + Melon.Msg("Freezing"); MelonEvents.OnGUI.Subscribe(DrawFrozenText, 100); // Register the 'Frozen' label baseTimeScale = Time.timeScale; // Save the original time scale before freezing @@ -245,7 +242,7 @@ namespace TimeFreezer } else { - instance.LoggerInstance.Msg("Unfreezing"); + Melon.Msg("Unfreezing"); MelonEvents.OnGUI.Unsubscribe(DrawFrozenText); // Unregister the 'Frozen' label Time.timeScale = baseTimeScale; // Reset the time scale to what it was before we froze the time From 67ed789f2390d27e1aaea08b1ffd68e0a47b17f7 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 13:44:55 -0400 Subject: [PATCH 4/9] Fix logger mistake --- docs/modders/quickstart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modders/quickstart.md b/docs/modders/quickstart.md index b099fa5..0470cc6 100644 --- a/docs/modders/quickstart.md +++ b/docs/modders/quickstart.md @@ -234,7 +234,7 @@ namespace TimeFreezer if (frozen) { - Melon.Msg("Freezing"); + Melon.Logger.Msg("Freezing"); MelonEvents.OnGUI.Subscribe(DrawFrozenText, 100); // Register the 'Frozen' label baseTimeScale = Time.timeScale; // Save the original time scale before freezing @@ -242,7 +242,7 @@ namespace TimeFreezer } else { - Melon.Msg("Unfreezing"); + Melon.Logger.Msg("Unfreezing"); MelonEvents.OnGUI.Unsubscribe(DrawFrozenText); // Unregister the 'Frozen' label Time.timeScale = baseTimeScale; // Reset the time scale to what it was before we froze the time From dfbaf43f6b8db7fd53c6d4b19a381f082c0f3b2f Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 13:57:12 -0400 Subject: [PATCH 5/9] Code style adjustments PR NOTE: The example code in "Patching using Native Hooks" seems to be very wrong, but I don't know enough on how to fix it --- docs/modders/patching.md | 28 ++++++++++++---------------- docs/modders/preferences.md | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/docs/modders/patching.md b/docs/modders/patching.md index 3d51b09..9018b36 100644 --- a/docs/modders/patching.md +++ b/docs/modders/patching.md @@ -150,50 +150,46 @@ Here's the code that we're going to be using, its a simple patch of the getter f then we return a string of our choice. Will probably break some things in a game if they rely on the name but this is just for fun ```cs -//delegate for our patch, same number of parameters as our patch method +// Delegate for our patch, same number of parameters as our patch method [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr GetNameDelegate( IntPtr instance, IntPtr methodInfo ); -//two static fields with our delegate type +// Two static fields with our delegate type private static NativeHook Hook; private static GetNameDelegate _patchDelegate; -//the patch method, dealing with unmanaged to managed then back to unmanaged so pointers galore +// The patch method, dealing with unmanaged to managed then back to unmanaged, so pointers galore public static unsafe IntPtr GetName(IntPtr instance, IntPtr methodInfo) { IntPtr result = hook.Trampoline(instance, methodName); string name = IL2CPP.PointerToValueGeneric (result, false, false); - Logger.Msg(name); + Melon.Logger.Msg(name); return IL2CPP.ManagedStringToIl2Cpp("MelonLoader"); } -//logging instance -public static MelonLogger.Instance Logger; -//our mods initialize method, prefer OnLateInitializeMelon to make sure everything is loaded and available +// Our mod's initialize method, prefer OnLateInitializeMelon to make sure everything is loaded and available public override unsafe void OnLateInitializeMelon() { - Logger=LoggerInstance; - - //getting the IntPtr for our target method with GetIl2CppMethodInfoPointerFieldForGeneratedMethod + // Getting the IntPtr for our target method with GetIl2CppMethodInfoPointerFieldForGeneratedMethod IntPtr originalMethod = *(IntPtr*) (IntPtr) Il2CppInteropUtils. - GetIl2CppMethodInfoPointerFieldForGeneratedMethod(typeof(UnityEngine.Object).GetMethod("get_name").GetValue(null); + GetIl2CppMethodInfoPointerFieldForGeneratedMethod(typeof(UnityEngine.Object).GetMethod("get_name").GetValue(null)); - //storing our patch method in one of the delegate fields + // Storing our patch method in one of the delegate fields _patchDelegate = GetName; - //getting the IntPtr from _patchDelegate + // Getting the IntPtr from _patchDelegate IntPtr delegatePointer = Marshal.GetFunctionPointerForDelegate(_patchDelegate); - //creating the NativeHook with our target method' IntPtr and patch delegate' IntPtr + // Creating the NativeHook with our target method' IntPtr and patch delegate' IntPtr NativeHook hook = new NativeHook (originalMethod, delegatePointer); - //very important part, actually telling it to attach and hook into the target method + // Very important part, actually telling it to attach and hook into the target method hook.Attach(); - //storing the hook so we can use the trampoline in it to run the original method in our patch + // Storing the hook so we can use the trampoline in it to run the original method in our patch Hook = hook.Trampoline; } ``` diff --git a/docs/modders/preferences.md b/docs/modders/preferences.md index 1ea0439..7114517 100644 --- a/docs/modders/preferences.md +++ b/docs/modders/preferences.md @@ -14,7 +14,7 @@ namespace MyProject { public override void OnUpdate() { - if(Input.GetKeyDown(KeyCode.T)) + if (Input.GetKeyDown(KeyCode.T)) { LoggerInstance.Log("You just pressed T"); } From 6680d72491f195d5802d70e037856b7b60878709 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 15:28:59 -0400 Subject: [PATCH 6/9] Add VSWizard to quickstart --- docs/modders/quickstart.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/modders/quickstart.md b/docs/modders/quickstart.md index 0470cc6..592022f 100644 --- a/docs/modders/quickstart.md +++ b/docs/modders/quickstart.md @@ -2,6 +2,21 @@ !> This tutorial assumes that you have a fair grasp of the C# programming language and basic knowledge of Visual Studio and Unity Engine. +### Visual Studio Template +An automated Visual Studio 2022 template for creating MelonLoader mods and plugins is available.
+It handles the creation of the required boilerplate (the `MelonMod`/`MelonPlugin` class, `MelonInfo`, and `MelonGame`) as well as referencing the required assemblies for mod development, mainly MelonLoader, Harmony, and for Il2Cpp, proxy assemblies and the unhollower (Il2CppAssemblyUnhollower or Il2CppInterop). It also handles variation between MelonLoader or Unity versions, such as framework versions or override changes. + +0. Download MelonLoader to your game and run it once before continuing. +1. Download the VSIX from the [GitHub repo](https://github.com/TrevTV/MelonLoader.VSWizard/releases). +2. Close all instances of Visual Studio and run the VSIX installer (double-clicking it should open it). +3. Open Visual Studio and create a new project. +4. Search for `MelonLoader` and click on either Mod or Plugin. +5. Enter the project info and press Create. +6. Select the EXE of the game you are modding and press Open. +7. Wait for the project creation and it should open a Visual Studio window with a working project. + +You may want to change the author in the `MelonInfo` attribute. It defaults to your computer's username. + ### Basic mod setup First, you will need to create a new project. Unity versions require different project templates:
From b1bfeb2afb591423c62961abb4239ebbb9fb69be Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 15:29:12 -0400 Subject: [PATCH 7/9] Clarify 0Harmony DLL --- docs/modders/patching.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/modders/patching.md b/docs/modders/patching.md index 9018b36..598bfb2 100644 --- a/docs/modders/patching.md +++ b/docs/modders/patching.md @@ -46,7 +46,8 @@ public class Example This will mostly be a repeat of what the [Harmony Docs](https://harmony.pardeike.net/articles/patching.html) say. But here we go!
For this example, I will be patching `Example.PrivateMethod(int param1)`. -Harmony is included in the MelonLoader folder, named `0Harmony.dll`, be sure to reference it before continuing. +With MelonLoader 0.5.4+, Harmony is included in the MelonLoader folder, named `0Harmony.dll`, be sure to reference it before continuing.
+On previous versions, it is directly embedded into `MelonLoader.dll`. Let's create a new class. It can be named anything and have any access modifiers, however, we must add the `HarmonyPatch` attribute for it to be picked up by Harmony.
In the attribute, we specify what method we would like to patch. This is similar to [getting a method using Reflection](modders/reflection?id=calling-a-method-using-reflection).
From 2f8362d8dd7946d192b28c8a08094138739922c4 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 15:30:53 -0400 Subject: [PATCH 8/9] Clarify reference DLLs --- docs/modders/quickstart.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/modders/quickstart.md b/docs/modders/quickstart.md index 592022f..34c6b06 100644 --- a/docs/modders/quickstart.md +++ b/docs/modders/quickstart.md @@ -200,8 +200,9 @@ For games using the IL2CPP runtime, all the game/Unity assemblies can be found i Since IL2CPP converts all game assemblies to C++, MelonLoader is using [Il2CppInterop](https://github.com/BepInEx/Il2CppInterop), an IL2CPP proxy assembly generator which allows us to use IL2CPP assemblies from C#. Before we can use any assemblies generated by the Unhollower, it's required to reference the following assemblies first: -- `Il2Cppmscorlib` -- `Il2CppInterop` +- `Il2Cppmscorlib.dll` +- `Il2CppInterop.Common.dll` +- `Il2CppInterop.Runtime.dll` At this point, you're ready to make your first functional Melon. From 94787596275d3ba56569e1ab3f70c5130873eb56 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 29 Aug 2024 15:42:39 -0400 Subject: [PATCH 9/9] Clarify Harmony location more --- docs/modders/patching.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/modders/patching.md b/docs/modders/patching.md index 598bfb2..a4f4069 100644 --- a/docs/modders/patching.md +++ b/docs/modders/patching.md @@ -46,7 +46,8 @@ public class Example This will mostly be a repeat of what the [Harmony Docs](https://harmony.pardeike.net/articles/patching.html) say. But here we go!
For this example, I will be patching `Example.PrivateMethod(int param1)`. -With MelonLoader 0.5.4+, Harmony is included in the MelonLoader folder, named `0Harmony.dll`, be sure to reference it before continuing.
+With MelonLoader 0.6.0+, Harmony is included in the `MelonLoader\net6` folder for Il2Cpp and `MelonLoader\net35` for Mono, named `0Harmony.dll`.
+With MelonLoader 0.5.4-0.5.7, Harmony is included directly under the `MelonLoader` folder for both Il2Cpp and Mono, also named `0Harmony.dll`.
On previous versions, it is directly embedded into `MelonLoader.dll`. Let's create a new class. It can be named anything and have any access modifiers, however, we must add the `HarmonyPatch` attribute for it to be picked up by Harmony.