MelonLoader
MelonLoader copied to clipboard
Make IL2CPP games use a dotnet/runtime setup
This PR represents mine and Rin's painful attempts to replace the mono domain with a modern dotnet runtime when running on an IL2CPP game.
It's mostly backwards-compatible - of a random sample of 51 mods for VRChat I've tested, 49 seem to function as intended, with one not working due to poor code, and one not working due to some confusion around cctors.
Muse dash custom album mod works, but fails to load charts from zipped files - they have to be unzipped. This is due to the fact that it bundles a netframework-targeting build of Ionic.Zip, which attempts to load the IBM437 encoding, which isn't supported on modern .NET. A simple recompilation of the mod, targeting net6, should fix this.
The Long Dark mods are loading and working fine in my tests.
A lot of BTD6 mods are not working due to exceptions in the mod helper's harmony patches, but that mod uses old-style csproj files and can't be compiled against this branch and I'm not willing to spend the time required to fix compilation and then diagnose the issue. However mods that don't use the helper seem to work (as much as can be expected).
So far from this limited sample set, compatibility seems to be > 96% of all mods.
~~However, due to the removal of the mono reference remapper, very old mods, such as those which require MelonLoader.ModHandler.dll, which was removed in 0.3.0, will no longer work, but the likelyhood of most of them working regardless, considering how much your average game's codebase changes in 15 months, is slim.~~ I've implemented specifically ModHandler backwards-compat.
Minor things left to do
- [x] Move ML welcome message to managed
- [x] Add a PrintEnvironment method to MelonEnvironment, replacing the game path printing in the unmanaged log
- [x] (Potentially?) move analytics blocker to unmanaged
- [x] Hook AssemblyVerifier up to AssemblyLoadContext
- [x] Rename bootstrap's log to Latest_Bootstrap.log
- [x] Rename managed log to Latest.log
- [x] Potentially move hash code calc to managed
- [x] Switch managed log to ANSI color code-style system, allowing full use of 32-bit colour in log files.
- [ ] Investigate loading each melon into its own load context, and pulling dependent assemblies across the boundary between them, thus making them shared, and allowing unloading of entire contexts and thus melons.
- [ ] Update InternalCalls to reflect re/moved methods, and move remaining ones to BootstrapInterop class
- [ ] Test mono games still work
- [ ] Test x86 il2cpp games.
- [ ] Investigate potential user experience improvements when no .net runtime installed, when incorrect version/arch, when corrupt, etc.
Implementation Notes
- The assembly generator is currently using a build from my forked branch of Il2CppAssemblyUnhollower, which contains changes required to get the assemblies to load in the new runtime (which is must stricter than mono).
- The new bootstrap DotnetRuntime manager creates and spins up a coreclr 6.0 runtime, grabs a reference to the new
MelonLoader.NativeHost
assembly, and calls an entry point in there. This assembly has to be separate because the method has to be marked as[UnmanagedCallersOnly]
, which was added in .NET 5.0 - Compiling currently requires you to have specifically .NET 6.0.3 SDK (SDK 6.0.103) installed to the default location, as it will look for that path for the header and lib files.
- Launching ML also currently requires an installed .NET 6.0.3 runtime.
- The change of runtime allows mods for IL2CPP games, and parts of ML itself, to target net6, and get all the performance optimizations made by the .NET foundation in the last 6 years.
- Debugging works out of the box, and I've added a new command-line argument,
--melonloader.launchdebugger
, which callsDebugger.Launch()
very early in the load process, allowing Visual Studio to attach to the process.- Breakpoints seem to work entirely as expected.
- Hot reload needs more investigation - the button appears but didn't function the one time I tried it.
- Other debuggers such as the one built in to JetBrains Rider didn't seem to work when I tried them - investigating why would be nice.
- A non-trivial number of mods (about 5 of my sample size of 51, for VRChat, and the CustomAlbums mod for Muse Dash) call
MelonUtils.NativeHookAttach
with the detour being a pointer to a managed method. This worked (for god knows what reason) under mono but crashes the entire runtime when the method is called under CoreCLR.- A system is implemented to attempt to wrap these bad hooks in a
[UnmanagedFunctionPointer]
-annotated delegate type, and callMarshal.GetFunctionPointerForDelegate
(which is what these mods should be doing) before attaching the hook, as well as logging a warning (or error and skip the hook attach, if the hook can't be wrapped) but having it enabled slows down the hook process considerably (to about 500ms per hook on my PC, which is no slouch) as each call must have its method pointer evaluated to see if it can be mapped to a managed method, which requires taking a snapshot of the runtime status. - I'm debating DISABLING this wrapping functionality by default, and requiring communities which refuse to fix their mods to tell their users to specify a new launch option (e.g.
--melonloader.hookfix
) to enable it, at the cost of (startup) performance.
- A system is implemented to attempt to wrap these bad hooks in a
- Updating the runtime to future .NET versions (e.g. 7.0) should be trivial, and it may even be automatic as long as the user has a newer one installed.
Other Considerations
- Some general code cleanup of both managed and unmanaged sides would probably not go amiss - moving things to c# where possible, splitting out il2cpp-specific code into a separate (non-net3.5) assembly, etc.
- I've been investigating a way to obtain a full stacktrace, including native frames, where desired (e.g. find what's calling a harmony patch) but this seems to be very difficult to do.
Superseeded by #299