Skip to content

Commit

Permalink
Merge pull request #84 from LemonLoader/fixes
Browse files Browse the repository at this point in the history
Various fixes and adjustments
  • Loading branch information
loukylor authored Aug 29, 2024
2 parents e5832d6 + 9478759 commit 269bb55
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 29 deletions.
8 changes: 4 additions & 4 deletions docs/modders/il2cppdifferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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()`.<br/>
`.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<T>()`.
```cs
// You must reference `Il2cppInterop.Runtime.dll` for this.
// You must reference `Il2CppInterop.Runtime.dll` for this.
using Il2CppInterop.Runtime;

Resources.FindObjectsOfTypeAll(Il2CppType.Of<Camera>());
Expand Down
32 changes: 15 additions & 17 deletions docs/modders/patching.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ 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!<br>
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.
With MelonLoader 0.6.0+, Harmony is included in the `MelonLoader\net6` folder for Il2Cpp and `MelonLoader\net35` for Mono, named `0Harmony.dll`.<br>
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`.<br>
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.<br>
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).<br>
Expand Down Expand Up @@ -150,50 +152,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<GetNameDelegate> 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<string> (result, false, false);
Logger.Msg(name);
Melon<MyMod>.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<GetNameDelegate> hook = new NativeHook<GetNameDelegate> (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;
}
```
Expand Down
2 changes: 1 addition & 1 deletion docs/modders/preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
27 changes: 20 additions & 7 deletions docs/modders/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br>
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:<br>
Expand Down Expand Up @@ -185,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.

Expand All @@ -205,16 +221,13 @@ namespace TimeFreezer
{
public class TimeFreezerMod : MelonMod
{
public static TimeFreezerMod instance;

private static KeyCode freezeToggleKey;

private static bool frozen;
private static float baseTimeScale;

public override void OnEarlyInitializeMelon()
{
instance = this;
freezeToggleKey = KeyCode.Space;
}

Expand All @@ -237,15 +250,15 @@ namespace TimeFreezer

if (frozen)
{
instance.LoggerInstance.Msg("Freezing");
Melon<TimeFreezerMod>.Logger.Msg("Freezing");

MelonEvents.OnGUI.Subscribe(DrawFrozenText, 100); // Register the 'Frozen' label
baseTimeScale = Time.timeScale; // Save the original time scale before freezing
Time.timeScale = 0;
}
else
{
instance.LoggerInstance.Msg("Unfreezing");
Melon<TimeFreezerMod>.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
Expand Down

0 comments on commit 269bb55

Please sign in to comment.