using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using BepInEx.Bootstrap; using BepInEx.Logging; using BepInEx.Preloader.Core; using BepInEx.Preloader.Core.Logging; using Iced.Intel; using MonoMod.RuntimeDetour; using UnhollowerBaseLib.Runtime; using UnhollowerRuntimeLib; using UnityEngine; using Logger = BepInEx.Logging.Logger; namespace BepInEx.IL2CPP { public class IL2CPPChainloader : BaseChainloader { private static ManualLogSource UnityLogSource = new ManualLogSource("Unity"); public static void UnityLogCallback(string logLine, string exception, LogType type) { UnityLogSource.LogInfo(logLine.Trim()); } // public const CallingConvention ArchConvention = //#if X64 // CallingConvention.Stdcall; //#else // CallingConvention.Cdecl; //#endif [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate IntPtr RuntimeInvokeDetour(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc); [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); private static RuntimeInvokeDetour originalInvoke; private static ManualLogSource unhollowerLogSource = Logger.CreateLogSource("Unhollower"); private class DetourHandler : UnhollowerRuntimeLib.ManagedDetour { public T Detour(IntPtr from, T to) where T : Delegate { var detour = new NativeDetour(from, to, new NativeDetourConfig { ManualApply = true }); var trampoline = detour.GenerateTrampoline(); detour.Apply(); return trampoline; } } private static void Disassemble(ManualLogSource logSource, IntPtr memoryPtr, int size) { byte[] data = new byte[size]; Marshal.Copy(memoryPtr, data, 0, size); var formatter = new NasmFormatter(); var output = new StringOutput(); var codeReader = new ByteArrayCodeReader(data); var decoder = Decoder.Create(64, codeReader); decoder.IP = (ulong)memoryPtr.ToInt64(); while (codeReader.CanReadByte) { decoder.Decode(out var instr); formatter.Format(instr, output); logSource.LogDebug($"{instr.IP:X16} {output.ToStringAndReset()}"); } } public override unsafe void Initialize(string gameExePath = null) { base.Initialize(gameExePath); UnityVersionHandler.Initialize(2019, 2, 17); // One or the other here for Unhollower to work correctly ////////ClassInjector.Detour = new DetourHandler(); ClassInjector.DoHook = (ptr, intPtr) => { IntPtr originalFunc = new IntPtr(*(void**)ptr); unhollowerLogSource.LogDebug($"DoHook 0x{originalFunc.ToString("X")} -> 0x{intPtr.ToString("X")}"); var trampolineAlloc = DetourHelper.Native.MemAlloc(80); DetourHelper.Native.MakeExecutable(trampolineAlloc, 80); DetourHelper.Native.MakeWritable(originalFunc, 24); unhollowerLogSource.LogDebug($"Trampoline allocation: 0x{ptr.ToString("X")}"); unhollowerLogSource.LogDebug("Original (24) asm"); Disassemble(unhollowerLogSource, originalFunc, 24); var trampolineLength = TrampolineGenerator.Generate(originalFunc, intPtr, trampolineAlloc, IntPtr.Size == 8 ? 64 : 32); unhollowerLogSource.LogDebug("Modified (24) asm"); Disassemble(unhollowerLogSource, originalFunc, 24); unhollowerLogSource.LogDebug($"Trampoline ({trampolineLength}) asm"); Disassemble(unhollowerLogSource, trampolineAlloc, trampolineLength); *(void**)ptr = (void*)trampolineAlloc; }; var gameAssemblyModule = Process.GetCurrentProcess().Modules.Cast().First(x => x.ModuleName.Contains("GameAssembly")); var functionPtr = GetProcAddress(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); //DynDll.GetFunction(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); PreloaderLogger.Log.LogDebug($"Runtime invoke pointer: 0x{functionPtr.ToInt64():X}"); var invokeDetour = new NativeDetour(functionPtr, Marshal.GetFunctionPointerForDelegate(new RuntimeInvokeDetour(OnInvokeMethod)), new NativeDetourConfig { ManualApply = true }); originalInvoke = invokeDetour.GenerateTrampoline(); invokeDetour.Apply(); } private static bool HasSet = false; private static IntPtr OnInvokeMethod(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc) { string methodName = Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method)); if (!HasSet && methodName == "Internal_ActiveSceneChanged") { try { UnityEngine.Application.s_LogCallbackHandler = new Action(UnityLogCallback); UnityLogSource.LogMessage($"callback set - {methodName}"); UnityEngine.Application.CallLogCallback("test from OnInvokeMethod", "", LogType.Log, true); } catch (Exception ex) { UnityLogSource.LogError(ex); } HasSet = true; } //UnityLogSource.LogDebug(methodName); return originalInvoke(method, obj, parameters, exc); } protected override void InitializeLoggers() { //Logger.Listeners.Add(new UnityLogListener()); //if (ConfigUnityLogging.Value) // Logger.Sources.Add(new UnityLogSource()); Logger.Sources.Add(UnityLogSource); UnhollowerBaseLib.LogSupport.InfoHandler += unhollowerLogSource.LogInfo; UnhollowerBaseLib.LogSupport.WarningHandler += unhollowerLogSource.LogWarning; UnhollowerBaseLib.LogSupport.TraceHandler += unhollowerLogSource.LogDebug; UnhollowerBaseLib.LogSupport.ErrorHandler += unhollowerLogSource.LogError; base.InitializeLoggers(); //if (!ConfigDiskWriteUnityLog.Value) //{ // DiskLogListener.BlacklistedSources.Add("Unity Log"); //} foreach (var preloaderLogEvent in PreloaderConsoleListener.LogEvents) { PreloaderLogger.Log.Log(preloaderLogEvent.Level, preloaderLogEvent.Data); } //UnityEngine.Application.s_LogCallbackHandler = DelegateSupport.ConvertDelegate(new Action(UnityLogCallback)); //UnityEngine.Application.s_LogCallbackHandler = (Application.LogCallback)new Action(UnityLogCallback); //var loggerPointer = Marshal.GetFunctionPointerForDelegate(new UnityLogCallbackDelegate(UnityLogCallback)); //UnhollowerBaseLib.IL2CPP.il2cpp_register_log_callback(loggerPointer); } public override BasePlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly) { var type = pluginAssembly.GetType(pluginInfo.TypeName); var pluginInstance = (BasePlugin)Activator.CreateInstance(type); pluginInstance.Load(); return pluginInstance; } } }