* Follow PE export jmp thunks before applying a native detour.
GetProcAddress of il2cpp_runtime_invoke can be a jmp rel32 stub. Dobby aborting on that stub is BepInEx#1188-class. Resolve E9 and FF 25 in INativeDetour.Create so Default/Dobby and Funchook all patch the prologue. No-op when the pointer is already a function body.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use IntPtr.Size when following FF 25 thunks.
nint.Size does not exist on the net6.0 BepInEx IL2CPP target.
Co-authored-by: Cursor <cursoragent@cursor.com>
Two related failures on aggressively stripped Unity Mono builds:
1. InitializeLoggers reads Application.unityVersion, which the managed
code stripper can remove. The version is only used for an
informational log line, but a MissingMethodException there aborts the
rest of chainloader initialisation. Read it defensively instead.
2. UnityChainloader.Initialize caught every exception, logged it as
Fatal, and returned normally. StaticStart then called Execute() on a
chainloader that was never initialised, so BepInEx logged
"Chainloader startup complete" while every plugin silently never ran.
Rethrow so a failed init cannot be mistaken for a successful one.
Verified on Beacon Pines (Unity 2021.3.45f1, Mono, x64), which strips
both Application.unityVersion and the log-callback members.
Co-authored-by: ManlyMarco <39247311+ManlyMarco@users.noreply.github.com>
UnityLogSource's static constructor falls back to
Application.RegisterLogCallback when Application.logMessageReceived is
absent. Unity's managed code stripper can remove both, in which case
GetMethod returns null and the unguarded Invoke throws a
NullReferenceException out of the static constructor.
That exception propagates through chainloader initialisation, so the
chainloader never starts and no plugins load. The failure is close to
undiagnosable: LogOutput.log ends at "Preloader finished" with no error,
and the exception only appears on stdout.
Unity log forwarding is optional, so warn and continue instead of taking
the whole loader down.
Seen on Beacon Pines (Unity 2021.3.45f1, Mono), whose
UnityEngine.Application has neither member.
* Degrade instead of crashing when the interop assemblies are missing
OnInvokeMethod references interop types (UnityEngine.Application/LogType) in its body. The CLR JIT-compiles the whole method before running any of it, so when the interop assemblies cannot be resolved the method throws FileNotFoundException at JIT time -- before its own try is entered -- and the exception escapes into the native runtime-invoke detour and crashes the process with nothing logged.
Move the interop-referencing call into its own SetupUnityLogging method called inside the try. OnInvokeMethod then JITs without the interop assemblies present, the missing-interop failure is caught, and the loader degrades (logs and continues) so the game runs unmodded. Set unhook at the top of the try so the detour fires exactly once even on failure.
* Condense the interop-degrade comments and reword the fatal log
Per review: trim the JIT-bypass comments to one line each and change
the fatal message to "Unable to execute IL2CPP chainloader, no plugins
will be loaded".
When the Unity base-libraries download fails (commonly a 404 for a
Unity version not yet hosted on unity.bepinex.dev), the uncaught
HttpRequestException surfaced only as a cryptic "Failed to generate
Il2Cpp interop assemblies" and the game would not launch -- even
though a local fallback (the unity-libs directory) already exists.
Catch the download failure and throw a clear message naming the Unity
version, the URL and the two workarounds: place a matching
base-libraries .zip in the unity-libs directory, or point the
[IL2CPP] UnityBaseLibrariesSource config at a valid URL.
Refs #1295, #1084, #986, #941, #515
Assemblies are often loaded on the il2cpp side but not on the mono side, causing confusion when using reflection.
This will load all interop assemblies right before plugins are loaded, which should mostly avoid these issues at a low time cost of around 40ms.