diff --git a/CHANGELOG.md b/CHANGELOG.md index 13c67b6a..a4c377a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,20 +39,20 @@ ### v0.6.5 -1. Replaced [BepInEx/Il2CppInterop](https://github.com/BepInEx/Il2CppInterop) with [ds5678/Il2CppInterop](https://github.com/ds5678/Il2CppInterop) -2. Updated Il2CppInterop to 1.5.4 -3. Updated Cpp2IL to 2022.1.0-pre-release.18 -4. Updated AsmResolver to 6.0.0-beta.1 -5. Updated AssetRipper.VersionUtilities to 1.5.0 -6. Updated AssetsTools.NET to 3.0.0 -7. Updated UnityEngine.Il2CppAssetBundleManager for latest compatibility -8. Updated UnityEngine.Il2CppImageConversionManager for latest compatibility -9. Implemented `--cpp2il.callanalyzer` launch option to enable Cpp2IL's CallAnalyzer processor -10. Implemented `--cpp2il.nativemethoddetector` launch option to enable Cpp2IL's NativeMethodDetector processor -11. Implemented several fixes for Il2CppInterop related issues -12. Implemented Cpp2IL's StrippedCodeRegSupport plugin -13. Implemented `ExternalArguments` Dictionary for MelonLaunchOptions (Credits to [HAHOOS](https://github.com/HAHOOS) :P) -14. Implemented `Peek` Method for LemonEnumerator +1. Updated Il2CppInterop to 1.4.6-ci.545 +2. Updated Cpp2IL to 2022.1.0-pre-release.18 +3. Updated AsmResolver to 6.0.0-beta.1 +4. Updated AssetRipper.VersionUtilities to 1.5.0 +5. Updated AssetsTools.NET to 3.0.0 +6. Updated UnityEngine.Il2CppAssetBundleManager for latest compatibility +7. Updated UnityEngine.Il2CppImageConversionManager for latest compatibility +8. Implemented `--cpp2il.callanalyzer` launch option to enable Cpp2IL's CallAnalyzer processor +9. Implemented `--cpp2il.nativemethoddetector` launch option to enable Cpp2IL's NativeMethodDetector processor +10. Implemented several fixes for Il2CppInterop related issues +11. Implemented `ExternalArguments` Dictionary for MelonLaunchOptions (Credits to [HAHOOS](https://github.com/HAHOOS) :P) +12. Implemented `MsgPastel` Method for MelonLogger (Credits to [HAHOOS](https://github.com/HAHOOS) :P) +13. Implemented `Peek` Method for LemonEnumerator +14. Implemented ICallInjector for handling when Unity strips or renames Internal Calls 15. Fixed an accidental regression with LemonSHA256 16. Fixed an issue with Native logs using the wrong Colors 17. Fixed an issue with Preload module not replacing Mono libraries on Older Mono Games @@ -66,9 +66,13 @@ 25. Fixed an issue with EOS Support Module not being properly error handled 26. Fixed an issue with NativeStackWalk not unregistering addresses 27. Fixed an issue with Errors being Spammed if `UnityEngine.Transform::SetAsLastSibling` fails to resolve -28. Fixed an issue with MelonLaunchOptions failing to parse arguments if an `=` sign is used instead of a space -29. Fixed an issue with MelonLaunchOptions failing to parse options if a `-` prefix is used instead of `--` +28. Fixed an issue with MelonLaunchOptions failing to parse options if a `-` prefix is used instead of `--` +29. Fixed an issue with MelonLaunchOptions failing to parse option arguments if an `=` sign is used instead of a space 30. Fixed an issue with Command Line Arguments not being logged +31. Fixed an issue with Il2CppInterop Assembly Resolving when Il2Cpp Prefixing is used +32. Fixed an issue with .NET Executables not being Resolved properly when used as Dependencies +33. Fixed an issue with Dependency Graph failing to Resolve Assemblies properly +34. Fixed an issue with Il2Cpp Support Module not attempting to use direct references --- diff --git a/Dependencies/Il2CppAssemblyGenerator/Core.cs b/Dependencies/Il2CppAssemblyGenerator/Core.cs index 43b0f986..8f35de95 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Core.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Core.cs @@ -60,7 +60,7 @@ private static int Run() RemoteAPI.Contact(); cpp2il = new Cpp2IL(); - cpp2il_scrs = new Cpp2IL_StrippedCodeRegSupport(cpp2il); + //cpp2il_scrs = new Cpp2IL_StrippedCodeRegSupport(cpp2il); il2cppinterop = new Packages.Il2CppInterop(); unitydependencies = new UnityDependencies(); @@ -73,7 +73,7 @@ private static int Run() Logger.Msg($"Using Deobfuscation Regex = {(string.IsNullOrEmpty(deobfuscationRegex.Regex) ? "null" : deobfuscationRegex.Regex)}"); if (!cpp2il.Setup() - || !cpp2il_scrs.Setup() + //|| !cpp2il_scrs.Setup() || !il2cppinterop.Setup() || !unitydependencies.Setup() || !deobfuscationMap.Setup()) diff --git a/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj b/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj index 5bf17fd1..963968d9 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj +++ b/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/Dependencies/SupportModules/Component.cs b/Dependencies/SupportModules/Component.cs index d220c80e..90f03456 100644 --- a/Dependencies/SupportModules/Component.cs +++ b/Dependencies/SupportModules/Component.cs @@ -1,10 +1,10 @@ using System; +using System.Reflection; +using UnityEngine; + #if SM_Il2Cpp using Il2CppInterop.Runtime; -#else -using System.Reflection; #endif -using UnityEngine; namespace MelonLoader.Support { @@ -12,13 +12,14 @@ internal class SM_Component : MonoBehaviour { private bool isQuitting; private static bool hadError; + private static bool useGeneratedAssembly = true; + + private static MethodInfo SetAsLastSiblingMethod; #if SM_Il2Cpp private delegate bool SetAsLastSiblingDelegate(IntPtr transformptr); private static SetAsLastSiblingDelegate SetAsLastSiblingDelegateField; public SM_Component(IntPtr value) : base(value) { } -#else - private static MethodInfo SetAsLastSiblingMethod; #endif static SM_Component() @@ -26,21 +27,21 @@ static SM_Component() try { #if SM_Il2Cpp + SetAsLastSiblingMethod = typeof(Transform).GetMethod("SetAsLastSibling", BindingFlags.Public | BindingFlags.Instance); + if (SetAsLastSiblingMethod != null) + return; + + useGeneratedAssembly = false; SetAsLastSiblingDelegateField = IL2CPP.ResolveICall("UnityEngine.Transform::SetAsLastSibling"); if (SetAsLastSiblingDelegateField == null) throw new Exception("Unable to find Internal Call for UnityEngine.Transform::SetAsLastSibling"); #else SetAsLastSiblingMethod = typeof(Transform).GetMethod("SetAsLastSibling", BindingFlags.Public | BindingFlags.Instance); if (SetAsLastSiblingMethod == null) - throw new Exception("Unable to find Internal Call for UnityEngine.Transform::SetAsLastSibling"); + throw new Exception("Unable to find UnityEngine.Transform::SetAsLastSibling"); #endif } - catch (Exception ex) - { - hadError = true; - MelonLogger.Warning($"Exception while Getting Transform.SetAsLastSibling: {ex}"); - MelonLogger.Warning("Melon Events might run before some MonoBehaviour Events"); - } + catch (Exception ex) { LogError("Getting UnityEngine.Transform::SetAsLastSibling", ex); } } internal static void Create() @@ -51,14 +52,24 @@ internal static void Create() Main.obj = new GameObject(); DontDestroyOnLoad(Main.obj); Main.obj.hideFlags = HideFlags.DontSave; + #if SM_Il2Cpp Main.component = Main.obj.AddComponent(Il2CppType.Of()).TryCast(); #else Main.component = (SM_Component)Main.obj.AddComponent(typeof(SM_Component)); #endif + Main.component.SiblingFix(); } + private static void LogError(string cat, Exception ex) + { + hadError = true; + useGeneratedAssembly = false; + MelonLogger.Warning($"Exception while {cat}: {ex}"); + MelonLogger.Warning("Melon Events might run before some MonoBehaviour Events"); + } + private void SiblingFix() { if (hadError) @@ -66,19 +77,30 @@ private void SiblingFix() try { + if (useGeneratedAssembly) + { + gameObject.transform.SetAsLastSibling(); + transform.SetAsLastSibling(); + return; + } + #if SM_Il2Cpp SetAsLastSiblingDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(gameObject.transform)); SetAsLastSiblingDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(transform)); -#else - SetAsLastSiblingMethod?.Invoke(gameObject.transform, new object[0]); - SetAsLastSiblingMethod?.Invoke(transform, new object[0]); #endif } catch (Exception ex) { - hadError = true; - MelonLogger.Warning($"Exception while Invoking Transform.SetAsLastSibling: {ex}"); - MelonLogger.Warning("Melon Events might run before some MonoBehaviour Events"); +#if SM_Il2Cpp + if (useGeneratedAssembly) + { + useGeneratedAssembly = false; + SiblingFix(); + return; + } +#endif + + LogError("Invoking UnityEngine.Transform::SetAsLastSibling", ex); } } diff --git a/Dependencies/SupportModules/Il2Cpp/Il2Cpp.csproj b/Dependencies/SupportModules/Il2Cpp/Il2Cpp.csproj index cd5fcb09..8f8a89e8 100644 --- a/Dependencies/SupportModules/Il2Cpp/Il2Cpp.csproj +++ b/Dependencies/SupportModules/Il2Cpp/Il2Cpp.csproj @@ -41,9 +41,9 @@ - - - + + + diff --git a/Dependencies/SupportModules/Il2Cpp/Main.cs b/Dependencies/SupportModules/Il2Cpp/Main.cs index c604ace8..bf4bf2ef 100644 --- a/Dependencies/SupportModules/Il2Cpp/Main.cs +++ b/Dependencies/SupportModules/Il2Cpp/Main.cs @@ -13,6 +13,8 @@ using Il2CppInterop.Common; using Il2CppInterop.Runtime.InteropTypes; using Microsoft.Extensions.Logging; +using MelonLoader.Utils; +using System.IO; namespace MelonLoader.Support { @@ -28,7 +30,17 @@ internal static class Main private static ISupportModule_To Initialize(ISupportModule_From interface_from) { - Interface = interface_from; + Interface = interface_from; + + foreach (var file in Directory.GetFiles(MelonEnvironment.Il2CppAssembliesDirectory, "*.dll")) + { + try + { + Assembly.LoadFrom(file); + } + catch { } + } + UnityMappers.RegisterMappers(); Il2CppInteropRuntime runtime = Il2CppInteropRuntime.Create(new() diff --git a/MelonLoader/Core.cs b/MelonLoader/Core.cs index f5e24392..d688efc3 100644 --- a/MelonLoader/Core.cs +++ b/MelonLoader/Core.cs @@ -94,6 +94,7 @@ internal static int Initialize() #if NET6_0_OR_GREATER Fixes.Il2CppInteropFixes.Install(); + Fixes.Il2CppICallInjector.Install(); #endif PatchShield.Install(); @@ -173,26 +174,29 @@ internal static void WelcomeMessage() var archString = MelonUtils.IsGame32Bit() ? "x86" : "x64"; MelonLogger.MsgDirect($"Game Arch: {archString}"); MelonLogger.MsgDirect("------------------------------"); - MelonLogger.MsgDirect($"CommandLine: {string.Join(" ", MelonLaunchOptions.CommandLineArgs)}"); + MelonLogger.MsgDirect($"Command-Line: {string.Join(" ", MelonLaunchOptions.CommandLineArgs)}"); MelonLogger.MsgDirect("------------------------------"); - MelonEnvironment.PrintEnvironment(); } - internal static void Quit() { - MelonDebug.Msg("[ML Core] Received Quit from Support Module. Shutting down..."); + MelonDebug.Msg("[ML Core] Received Quit Request! Shutting down..."); MelonPreferences.Save(); HarmonyInstance.UnpatchSelf(); bHapticsManager.Disconnect(); +#if NET6_0_OR_GREATER + Fixes.Il2CppInteropFixes.Shutdown(); + Fixes.Il2CppICallInjector.Shutdown(); +#endif + MelonLogger.Flush(); //MelonLogger.Close(); - + Thread.Sleep(200); if (MelonLaunchOptions.Core.QuitFix) diff --git a/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs b/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs index 0884b85f..a5ee6745 100644 --- a/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs +++ b/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs @@ -42,8 +42,8 @@ internal static void Install() public static bool PreAssemblyLoad(byte[] rawAssembly, byte[] rawSymbolStore, ref Assembly __result) { - if(MelonDebug.IsEnabled() && !Environment.StackTrace.Contains("HarmonyLib")) - MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.Load call with {rawAssembly.Length}-byte assembly to AssemblyLoadContext.Default. Mod Devs: You may wish to use this explictly."); + //if(MelonDebug.IsEnabled() && !Environment.StackTrace.Contains("HarmonyLib")) + // MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.Load call with {rawAssembly.Length}-byte assembly to AssemblyLoadContext.Default. Mod Devs: You may wish to use this explictly."); var (ok, reason) = AssemblyVerifier.VerifyByteArray(rawAssembly); if (!ok) @@ -59,7 +59,7 @@ public static bool PreAssemblyLoad(byte[] rawAssembly, byte[] rawSymbolStore, re public static bool PreAssemblyLoadFile(string path, ref Assembly __result) { - MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.LoadFile({path}) call to AssemblyLoadContext.Default.LoadFromAssemblyPath. Mod Devs: You may wish to use this explictly."); + //MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.LoadFile({path}) call to AssemblyLoadContext.Default.LoadFromAssemblyPath. Mod Devs: You may wish to use this explictly."); string normalizedPath = Path.GetFullPath(path); diff --git a/MelonLoader/Fixes/Il2CppICallInjector.cs b/MelonLoader/Fixes/Il2CppICallInjector.cs new file mode 100644 index 00000000..1d5a83e3 --- /dev/null +++ b/MelonLoader/Fixes/Il2CppICallInjector.cs @@ -0,0 +1,342 @@ +#if NET6_0_OR_GREATER + +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.InteropTypes; +using MelonLoader.NativeUtils; +using MonoMod.RuntimeDetour; +using MonoMod.Utils; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; + +namespace MelonLoader.Fixes +{ + internal static class Il2CppICallInjector + { + private const string _customICallSuffix = "_INative"; + + private static Dictionary _lookup = new(); + + private delegate IntPtr dil2cpp_resolve_icall(IntPtr signature); + private static NativeHook il2cpp_resolve_icall_hook; + + private delegate void dil2cpp_add_internal_call(IntPtr signature, IntPtr funcPtr); + private static dil2cpp_add_internal_call il2cpp_add_internal_call; + + private static Type _stringType; + private static Type _intPtrType; + private static Type _exceptionType; + private static Type _il2CppObjectBaseType; + + private static MethodInfo _stringConcat; + private static MethodInfo _objectToString; + private static MethodInfo _melonLoggerError; + private static MethodInfo _stringToIl2CppPtr; + private static MethodInfo _il2CppPtrToString; + private static MethodInfo _il2CppObjectBaseGetPointer; + + private static MelonLogger.Instance _logger; + + internal static unsafe void Install() + { + try + { + _logger = new MelonLogger.Instance(nameof(Il2CppICallInjector)); + + Type thisType = typeof(Il2CppICallInjector); + Type objectType = typeof(object); + Type il2cppType = typeof(IL2CPP); + + _il2CppObjectBaseType = typeof(Il2CppObjectBase); + _exceptionType = typeof(Exception); + _intPtrType = typeof(IntPtr); + _stringType = typeof(string); + + _stringConcat = _stringType.GetMethod(nameof(string.Concat), [_stringType, _stringType]); + if (_stringConcat == null) + throw new Exception("Failed to get string.Concat"); + + _objectToString = objectType.GetMethod(nameof(ToString)); + if (_objectToString == null) + throw new Exception("Failed to get object.ToString"); + + _stringToIl2CppPtr = il2cppType.GetMethod(nameof(IL2CPP.ManagedStringToIl2Cpp)); + if (_stringToIl2CppPtr == null) + throw new Exception("Failed to get IL2CPP.ManagedStringToIl2Cpp"); + + _melonLoggerError = thisType.GetMethod(nameof(LogError), + BindingFlags.Static | BindingFlags.NonPublic, + [_stringType]); + if (_melonLoggerError == null) + throw new Exception("Failed to get MelonLogger.Error"); + + _il2CppPtrToString = il2cppType.GetMethod(nameof(IL2CPP.Il2CppStringToManaged)); + if (_il2CppPtrToString == null) + throw new Exception("Failed to get IL2CPP.Il2CppStringToManaged"); + + PropertyInfo pointerProp = _il2CppObjectBaseType.GetProperty(nameof(Il2CppObjectBase.Pointer)); + if (_il2CppPtrToString == null) + throw new Exception("Failed to get Il2CppObjectBase.Pointer"); + _il2CppObjectBaseGetPointer = pointerProp.GetMethod; + + string gameAssemblyName = "GameAssembly"; + NativeLibrary gameAssemblyLib = NativeLibrary.Load(gameAssemblyName); + if (gameAssemblyLib == null) + throw new Exception($"Failed to load {gameAssemblyName} Native Library"); + + IntPtr il2cpp_resolve_icall = gameAssemblyLib.GetExport(nameof(il2cpp_resolve_icall)); + if (il2cpp_resolve_icall == IntPtr.Zero) + throw new Exception($"Failed to get {nameof(il2cpp_resolve_icall)} Native Export"); + + il2cpp_add_internal_call = gameAssemblyLib.GetExport(nameof(il2cpp_add_internal_call)); + if (il2cpp_add_internal_call == null) + throw new Exception($"Failed to get {nameof(il2cpp_add_internal_call)} Native Export"); + + MelonDebug.Msg("Patching il2cpp_resolve_icall..."); + IntPtr detourPtr = Marshal.GetFunctionPointerForDelegate((dil2cpp_resolve_icall)il2cpp_resolve_icall_Detour); + il2cpp_resolve_icall_hook = new NativeHook(il2cpp_resolve_icall, detourPtr); + il2cpp_resolve_icall_hook.Attach(); + } + catch (Exception e) + { + LogError(e.ToString()); + } + } + + internal static void Shutdown() + { + if (il2cpp_resolve_icall_hook != null) + { + if (il2cpp_resolve_icall_hook.IsHooked) + il2cpp_resolve_icall_hook.Detach(); + il2cpp_resolve_icall_hook = null; + } + + if (_lookup != null) + { + if (_lookup.Count > 0) + _lookup.Clear(); + _lookup = null; + } + } + + private static void LogError(string msg) + => _logger.Error(msg); + + private static IntPtr il2cpp_resolve_icall_Detour(IntPtr signature) + { + // Convert Pointer to String + string signatureStr = Marshal.PtrToStringAnsi(signature); + if (string.IsNullOrEmpty(signatureStr)) + return IntPtr.Zero; + + // Check Cache + if (_lookup.TryGetValue(signatureStr, out var result)) + return result.Item3; + + // Run Original + IntPtr originalResult = il2cpp_resolve_icall_hook.Trampoline(signature); + if (originalResult != IntPtr.Zero) + { + // Cache Original Result + _lookup[signatureStr] = (null, null, originalResult); + return originalResult; + } + + // Check if Injection is Needed + if (!ShouldInject(signatureStr, out MethodInfo unityShimMethod)) + return IntPtr.Zero; + + // Create Injected Function and Cache Return + var pair = + _lookup[signatureStr] = GenerateTrampoline(unityShimMethod); + + // Add New ICall to Il2Cpp Domain + il2cpp_add_internal_call(signature, pair.Item3); + _logger.Msg($"Registered mono icall {signatureStr} in il2cpp domain"); + + // Return New Function Pointer + return pair.Item3; + } + + private static bool ShouldInject(string signature, out MethodInfo unityShimMethod) + { + unityShimMethod = null; + + // Split the Signature + string[] split = signature.Split("::"); + string typeName = split[0]; + + // Find Managed Type + Type newType = Il2CppInteropFixes.FixedFindType(typeName); + if (newType == null) + return false; + + // Find Managed Method + string methodName = split[1]; + MethodInfo method = newType.FindMethod(methodName); + if (method == null) + return false; + + // Inject ICall + unityShimMethod = method; + return true; + } + + private static (DynamicMethodDefinition, MethodInfo, IntPtr) GenerateTrampoline(MethodInfo unityShimMethod) + { + // Convert Method Parameters to Native Parameters + var methodParams = unityShimMethod.GetParameters(); + int offset = unityShimMethod.IsStatic ? 0 : 1; + Type[] paramTypes = new Type[methodParams.Length + offset]; + if (!unityShimMethod.IsStatic) + paramTypes[0] = _intPtrType; + for (int i = 0; i < methodParams.Length; i++) + { + if ((methodParams[i].ParameterType != _stringType) + && methodParams[i].ParameterType.IsValueType) + paramTypes[i + offset] = methodParams[i].ParameterType; + else + paramTypes[i + offset] = _intPtrType; + } + + // Convert Return Type + Type returnType = unityShimMethod.ReturnType; + if ((returnType == _stringType) + || !returnType.IsValueType) + returnType = _intPtrType; + + // Create New Injected ICall Method + string newMethodName = $"{unityShimMethod.Name}{_customICallSuffix}"; + var trampoline = new DynamicMethodDefinition( + newMethodName, + returnType, + paramTypes); + var ilGenerator = trampoline.GetILGenerator(); + + // Begin Try-Catch + ilGenerator.BeginExceptionBlock(); + + // Emit This Object + if (!unityShimMethod.IsStatic) + ilGenerator.EmitPtrArgToManagedObject(0, unityShimMethod.DeclaringType); + + // Convert Method Parameters to Managed Objects + for (var i = 0; i < methodParams.Length; i++) + { + var param = methodParams[i]; + var paramType = param.ParameterType; + if (paramType == _stringType) + ilGenerator.EmitPtrArgToString(i + offset); + else if (paramType.IsValueType) + ilGenerator.EmitArg(i + offset); + else + ilGenerator.EmitPtrArgToManagedObject(i + offset, paramType); + } + + // Call Existing Method + ilGenerator.Emit(OpCodes.Call, unityShimMethod); + + // Convert Managed Return + var oldreturnType = unityShimMethod.ReturnType; + if (oldreturnType == _stringType) + ilGenerator.EmitStringToPtr(); + else if ((oldreturnType == _il2CppObjectBaseType) + || oldreturnType.IsSubclassOf(_il2CppObjectBaseType)) + ilGenerator.EmitIl2CppObjectBaseToPtr(); + + // Cache Return Value in Lcal + LocalBuilder returnLocal = null; + if (returnType != typeof(void)) + { + returnLocal = ilGenerator.DeclareLocal(returnType); + ilGenerator.Emit(OpCodes.Stloc, returnLocal); + } + + // End Try-Catch + ilGenerator.EmitExceptionCatch(); + + // Restore Return Value from Local + if (returnLocal != null) + ilGenerator.Emit(OpCodes.Ldloc, returnLocal); + + // Return even if there is no Return Value + ilGenerator.Emit(OpCodes.Ret); + + // Return the New Method + MethodInfo newMethod = trampoline.Generate().Pin(); + return (trampoline, newMethod, newMethod.GetNativeStart()); + } + + private static void EmitArg(this ILGenerator ilGenerator, + int index) + => ilGenerator.Emit(OpCodes.Ldarg, index); + + private static void EmitPtrArgToString(this ILGenerator ilGenerator, + int argIndex) + { + ilGenerator.EmitArg(argIndex); + ilGenerator.Emit(OpCodes.Call, _il2CppPtrToString); + } + + private static void EmitStringToPtr(this ILGenerator ilGenerator) + => ilGenerator.Emit(OpCodes.Call, _stringToIl2CppPtr); + + private static void EmitPtrArgToManagedObject(this ILGenerator ilGenerator, + int argIndex, + Type managedType) + { + ilGenerator.EmitArg(argIndex); + + var labelNull = ilGenerator.DefineLabel(); + var labelDone = ilGenerator.DefineLabel(); + ilGenerator.Emit(OpCodes.Brfalse, labelNull); + ilGenerator.EmitArg(argIndex); + + ilGenerator.Emit(OpCodes.Newobj, + managedType.GetConstructor([ _intPtrType ])); + + ilGenerator.Emit(OpCodes.Br, labelDone); + ilGenerator.MarkLabel(labelNull); + ilGenerator.Emit(OpCodes.Ldnull); + ilGenerator.MarkLabel(labelDone); + } + + private static void EmitIl2CppObjectBaseToPtr(this ILGenerator ilGenerator) + { + var labelNull = ilGenerator.DefineLabel(); + var labelDone = ilGenerator.DefineLabel(); + ilGenerator.Emit(OpCodes.Dup); + ilGenerator.Emit(OpCodes.Brfalse, labelNull); + + ilGenerator.Emit(OpCodes.Call, _il2CppObjectBaseGetPointer); + + ilGenerator.Emit(OpCodes.Br, labelDone); + ilGenerator.MarkLabel(labelNull); + ilGenerator.Emit(OpCodes.Pop); + ilGenerator.Emit(OpCodes.Ldc_I4_0); + ilGenerator.Emit(OpCodes.Conv_I); + ilGenerator.MarkLabel(labelDone); + } + + private static void EmitExceptionCatch(this ILGenerator ilGenerator) + { + var exceptionLocal = ilGenerator.DeclareLocal(_exceptionType); + ilGenerator.BeginCatchBlock(_exceptionType); + + ilGenerator.Emit(OpCodes.Stloc, exceptionLocal); + ilGenerator.Emit(OpCodes.Ldstr, "Exception in IL2CPP Injected ICall: "); + ilGenerator.Emit(OpCodes.Ldloc, exceptionLocal); + + ilGenerator.Emit(OpCodes.Callvirt, _objectToString); + ilGenerator.Emit(OpCodes.Call, _stringConcat); + ilGenerator.Emit(OpCodes.Call, _melonLoggerError); + + ilGenerator.EndExceptionBlock(); + } + } +} + +#endif \ No newline at end of file diff --git a/MelonLoader/Fixes/Il2CppInteropFixes.cs b/MelonLoader/Fixes/Il2CppInteropFixes.cs index b9e8a289..73eed503 100644 --- a/MelonLoader/Fixes/Il2CppInteropFixes.cs +++ b/MelonLoader/Fixes/Il2CppInteropFixes.cs @@ -15,16 +15,20 @@ using HarmonyLib; using System.IO; using MelonLoader.Utils; +using Il2CppInterop.Generator.Contexts; +using AsmResolver.DotNet; namespace MelonLoader.Fixes { // fixes: https://github.com/BepInEx/Il2CppInterop/pull/103 - // fixes: https://github.com/BepInEx/Il2CppInterop/pull/134 // fixes: https://github.com/BepInEx/Il2CppInterop/issues/135 // reverts: https://github.com/BepInEx/Il2CppInterop/commit/18e58ef5db42a71d6012ab0387b107a4132101eb + // fixes the rest of: https://github.com/BepInEx/Il2CppInterop/pull/134 internal unsafe static class Il2CppInteropFixes { + private static Dictionary> _assemblyLookup = new(); private static Dictionary _typeLookup = new(); + private static Dictionary _typeNameLookup = new(); private static MethodInfo _getType; private static MethodInfo _fixedFindType; @@ -47,6 +51,14 @@ internal unsafe static class Il2CppInteropFixes private static MethodInfo _fixedFindAbstractMethods; private static MethodInfo _emitObjectToPointer; private static MethodInfo _emitObjectToPointer_Prefix; + private static MethodInfo _rewriteGlobalContext_AddAssemblyContext; + private static MethodInfo _rewriteGlobalContext_AddAssemblyContext_Postfix; + private static MethodInfo _rewriteGlobalContext_Dispose; + private static MethodInfo _rewriteGlobalContext_Dispose_Prefix; + private static MethodInfo _rewriteGlobalContext_GetNewAssemblyForOriginal; + private static MethodInfo _rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix; + private static MethodInfo _rewriteGlobalContext_TryGetNewTypeForOriginal; + private static MethodInfo _rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix; internal static void Install() { @@ -56,6 +68,8 @@ internal static void Install() Type thisType = typeof(Il2CppInteropFixes); Type classInjectorType = typeof(ClassInjector); Type ilGeneratorEx = typeof(ILGeneratorEx); + Type rewriteGlobalContextType = typeof(RewriteGlobalContext); + Type il2cppType = typeof(IL2CPP); Type injectorHelpersType = classInjectorType.Assembly.GetType("Il2CppInterop.Runtime.Injection.InjectorHelpers"); if (injectorHelpersType == null) @@ -105,6 +119,26 @@ internal static void Install() if (_get_IsByRef == null) throw new Exception("Failed to get Type.IsByRef.get"); + _rewriteGlobalContext_AddAssemblyContext = rewriteGlobalContextType.GetMethod("AddAssemblyContext", + BindingFlags.NonPublic | BindingFlags.Instance); + if (_rewriteGlobalContext_AddAssemblyContext == null) + throw new Exception("Failed to get RewriteGlobalContext.AddAssemblyContext"); + + _rewriteGlobalContext_Dispose = rewriteGlobalContextType.GetMethod("Dispose", + BindingFlags.Public | BindingFlags.Instance); + if (_rewriteGlobalContext_Dispose == null) + throw new Exception("Failed to get RewriteGlobalContext.Dispose"); + + _rewriteGlobalContext_GetNewAssemblyForOriginal = rewriteGlobalContextType.GetMethod("GetNewAssemblyForOriginal", + BindingFlags.Public | BindingFlags.Instance); + if (_rewriteGlobalContext_GetNewAssemblyForOriginal == null) + throw new Exception("Failed to get RewriteGlobalContext.GetNewAssemblyForOriginal"); + + _rewriteGlobalContext_TryGetNewTypeForOriginal = rewriteGlobalContextType.GetMethod("TryGetNewTypeForOriginal", + BindingFlags.Public | BindingFlags.Instance); + if (_rewriteGlobalContext_TryGetNewTypeForOriginal == null) + throw new Exception("Failed to get RewriteGlobalContext.TryGetNewTypeForOriginal"); + _fixedFindType = thisType.GetMethod(nameof(FixedFindType), BindingFlags.NonPublic | BindingFlags.Static); _fixedAddTypeToLookup = thisType.GetMethod(nameof(FixedAddTypeToLookup), BindingFlags.NonPublic | BindingFlags.Static); _fixedIsByRef = thisType.GetMethod(nameof(FixedIsByRef), BindingFlags.NonPublic | BindingFlags.Static); @@ -116,6 +150,10 @@ internal static void Install() _isTypeSupported_Transpiler = thisType.GetMethod(nameof(IsTypeSupported_Transpiler), BindingFlags.NonPublic | BindingFlags.Static); _convertMethodInfo_Transpiler = thisType.GetMethod(nameof(ConvertMethodInfo_Transpiler), BindingFlags.NonPublic | BindingFlags.Static); _emitObjectToPointer_Prefix = thisType.GetMethod(nameof(EmitObjectToPointer_Prefix), BindingFlags.NonPublic | BindingFlags.Static); + _rewriteGlobalContext_AddAssemblyContext_Postfix = thisType.GetMethod(nameof(RewriteGlobalContext_AddAssemblyContext_Postfix), BindingFlags.NonPublic | BindingFlags.Static); + _rewriteGlobalContext_Dispose_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_Dispose_Prefix), BindingFlags.NonPublic | BindingFlags.Static); + _rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_GetNewAssemblyForOriginal_Prefix), BindingFlags.NonPublic | BindingFlags.Static); + _rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix), BindingFlags.NonPublic | BindingFlags.Static); MelonDebug.Msg("Patching Il2CppInterop ClassInjector.SystemTypeFromIl2CppType..."); Core.HarmonyInstance.Patch(_systemTypeFromIl2CppType, @@ -148,6 +186,22 @@ internal static void Install() MelonDebug.Msg("Patching Il2CppInterop ILGeneratorEx.EmitObjectToPointer..."); Core.HarmonyInstance.Patch(_emitObjectToPointer, new HarmonyMethod(_emitObjectToPointer_Prefix)); + + MelonDebug.Msg("Patching Il2CppInterop RewriteGlobalContext.AddAssemblyContext..."); + Core.HarmonyInstance.Patch(_rewriteGlobalContext_AddAssemblyContext, + null, new HarmonyMethod(_rewriteGlobalContext_AddAssemblyContext_Postfix)); + + MelonDebug.Msg("Patching Il2CppInterop RewriteGlobalContext.Dispose..."); + Core.HarmonyInstance.Patch(_rewriteGlobalContext_Dispose, + new HarmonyMethod(_rewriteGlobalContext_Dispose_Prefix)); + + MelonDebug.Msg("Patching Il2CppInterop RewriteGlobalContext.GetNewAssemblyForOriginal..."); + Core.HarmonyInstance.Patch(_rewriteGlobalContext_GetNewAssemblyForOriginal, + new HarmonyMethod(_rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix)); + + MelonDebug.Msg("Patching Il2CppInterop RewriteGlobalContext.TryGetNewTypeForOriginal..."); + Core.HarmonyInstance.Patch(_rewriteGlobalContext_TryGetNewTypeForOriginal, + new HarmonyMethod(_rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix)); } catch (Exception e) { @@ -155,21 +209,65 @@ internal static void Install() } } + internal static void Shutdown() + { + if (_assemblyLookup != null) + { + if (_assemblyLookup.Count > 0) + { + foreach (Dictionary dict in _assemblyLookup.Values) + dict.Clear(); + _assemblyLookup.Clear(); + } + _assemblyLookup = null; + } + + if (_typeLookup != null) + { + if (_typeLookup.Count > 0) + _typeLookup.Clear(); + _typeLookup = null; + } + } + private static bool FixedIsByRef(Type type) - => type.IsByRef || type.IsPointer; + => (type != null) && (type.IsByRef || type.IsPointer); - private static Type FixedFindType(string il2CppTypeFullName) + internal static Type FixedFindType(string typeFullName) { - Type returnType = Type.GetType($"Il2Cpp.{il2CppTypeFullName}"); - if (returnType == null) - returnType = Type.GetType($"Il2Cpp{il2CppTypeFullName}"); - if (returnType == null) - returnType = Type.GetType(il2CppTypeFullName); - return returnType; + if (string.IsNullOrEmpty(typeFullName)) + return null; + + if (_typeNameLookup.TryGetValue(typeFullName, out Type result)) + return result; + + foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) + { + if (a == null) + continue; + + result = a.GetValidType($"Il2Cpp.{typeFullName}"); + if (result == null) + result = a.GetValidType($"Il2Cpp{typeFullName}"); + if (result == null) + result = a.GetValidType(typeFullName); + + if (result != null) + { + _typeNameLookup[result.FullName] = result; + return result; + } + } + + return null; } private static void FixedAddTypeToLookup(Type type, IntPtr typePointer) { + if ((type == null) + || (typePointer == IntPtr.Zero)) + return; + _injectorHelpers_AddTypeToLookup.Invoke(null, [type, typePointer]); typePointer = IL2CPP.il2cpp_class_get_type(typePointer); @@ -186,6 +284,9 @@ private static bool EmitObjectToPointer_Prefix(bool __7, ref bool __8) private static bool RewriteType_Prefix(Type __0, ref Type __result) { + if (__0 == null) + return true; + if (__0 == typeof(void*)) { __result = __0; @@ -195,6 +296,106 @@ private static bool RewriteType_Prefix(Type __0, ref Type __result) return true; } + private static void RewriteGlobalContext_AddAssemblyContext_Postfix(RewriteGlobalContext __instance, + AssemblyRewriteContext __1) + { + if ((__instance == null) + || (__1 == null) + || __1.OriginalAssembly == null) + return; + + if (!_assemblyLookup.TryGetValue(__instance, out Dictionary contexts) + || (contexts == null)) + contexts = _assemblyLookup[__instance] = new(); + + string assemblyName = __1.OriginalAssembly.Name; + if (string.IsNullOrEmpty(assemblyName)) + return; + + contexts[assemblyName] = __1; + //MelonDebug.Msg($"[RewriteGlobalContext] Added: {assemblyName}"); + } + + private static bool RewriteGlobalContext_Dispose_Prefix(RewriteGlobalContext __instance) + { + if ((__instance == null) + || !_assemblyLookup.ContainsKey(__instance) + || !_assemblyLookup.Remove(__instance, out Dictionary contexts) + || (contexts == null)) + return true; + + contexts.Clear(); + return true; + } + + private static bool RewriteGlobalContext_GetNewAssemblyForOriginal_Prefix(RewriteGlobalContext __instance, + AssemblyDefinition __0, + ref AssemblyRewriteContext __result) + { + if ((__instance == null) + || (__0 == null) + || !_assemblyLookup.TryGetValue(__instance, out Dictionary contexts) + || (contexts == null)) + return true; + + string assemblyName = __0.Name; + if (contexts.TryGetValue(assemblyName, out __result)) + { + //MelonDebug.Msg($"[RewriteGlobalContext] Found: {assemblyName}"); + return false; + } + + if (assemblyName.StartsWith("Il2Cpp")) + assemblyName = assemblyName.Remove(0, 6); + else + assemblyName = $"Il2Cpp{assemblyName}"; + + if (contexts.TryGetValue(assemblyName, out __result)) + { + //MelonDebug.Msg($"[RewriteGlobalContext] Found: {assemblyName}"); + return false; + } + + return true; + } + + private static bool RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix(RewriteGlobalContext __instance, + TypeDefinition __0, + ref TypeRewriteContext? __result) + { + if ((__instance == null) + || (__0 == null) + || (__0.Module == null) + || (__0.Module.Assembly == null) + || !_assemblyLookup.TryGetValue(__instance, out Dictionary contexts) + || (contexts == null)) + return true; + + string assemblyName = __0.Module.Assembly.Name; + if (string.IsNullOrEmpty(assemblyName)) + return false; + + AssemblyRewriteContext rewriteContext = null; + if (contexts.TryGetValue(assemblyName, out rewriteContext)) + { + //MelonDebug.Msg($"[RewriteGlobalContext] Found: {assemblyName}"); + __result = rewriteContext.TryGetContextForOriginalType(__0); + return false; + } + + if (assemblyName.StartsWith("Il2Cpp")) + assemblyName = assemblyName.Remove(0, 6); + else + assemblyName = $"Il2Cpp{assemblyName}"; + if (contexts.TryGetValue(assemblyName, out rewriteContext)) + { + //MelonDebug.Msg($"[RewriteGlobalContext] Found: {assemblyName}"); + __result = rewriteContext.TryGetContextForOriginalType(__0); + return false; + } + + return true; + } private static bool SystemTypeFromIl2CppType_Prefix(Il2CppTypeStruct* __0, ref Type __result) { @@ -279,6 +480,7 @@ private static IEnumerable SystemTypeFromIl2CppType_Transpiler( found = true; instruction.opcode = OpCodes.Call; instruction.operand = _fixedFindType; + MelonDebug.Msg("Patched Il2CppInterop ClassInjector.SystemTypeFromIl2CppType -> Type.GetType"); } diff --git a/MelonLoader/InternalUtils/DependencyGraph.cs b/MelonLoader/InternalUtils/DependencyGraph.cs index 5b4ae8de..db9b81f1 100644 --- a/MelonLoader/InternalUtils/DependencyGraph.cs +++ b/MelonLoader/InternalUtils/DependencyGraph.cs @@ -100,6 +100,7 @@ private DependencyGraph(IList melons) dependencyVertex.dependents.Add(melonVertex); } else if (!TryLoad(dependency) + && !TryResolve(dependency) && !optionalDependencies.Contains(dependency.Name) && !missingDependencies.Contains(dependency)) missingDependencies.Add(dependency); @@ -116,6 +117,7 @@ private DependencyGraph(IList melons) dependencyVertex.dependents.Add(melonVertex); } else if (!TryLoad(dependency) + && !TryResolve(dependency) && !missingDependencies.Contains(dependency)) missingDependencies.Add(dependency); } @@ -140,6 +142,24 @@ private DependencyGraph(IList melons) // Returns true if 'assembly' was already loaded or could be loaded, false if the required assembly was missing. private static bool TryLoad(AssemblyName assembly) + { + try + { + Assembly asm = Assembly.Load(assembly); + if (asm == null) + return false; + return true; + } + catch (FileNotFoundException) { return false; } + catch (Exception ex) + { + MelonLogger.Error("Loading Melon Dependency Failed: " + ex); + return false; + } + } + + // Returns true if 'assembly' was already resolved or could be resolved, false if the required assembly was missing. + private static bool TryResolve(AssemblyName assembly) { try { @@ -155,7 +175,7 @@ private static bool TryLoad(AssemblyName assembly) catch (FileNotFoundException) { return false; } catch (Exception ex) { - MelonLogger.Error("Loading Melon Dependency Failed: " + ex); + MelonLogger.Error("Resolving Melon Dependency Failed: " + ex); return false; } } diff --git a/MelonLoader/MelonLoader.csproj b/MelonLoader/MelonLoader.csproj index 64ab2a96..8e81c7a1 100644 --- a/MelonLoader/MelonLoader.csproj +++ b/MelonLoader/MelonLoader.csproj @@ -45,14 +45,14 @@ - + - - - - + + + + diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs index e6c89766..ff2203a3 100644 --- a/MelonLoader/MelonUtils.cs +++ b/MelonLoader/MelonUtils.cs @@ -302,7 +302,7 @@ public static IEnumerable GetValidTypes(this Assembly asm, LemonFunc GetValidTypes(this Assembly asm, LemonFunc (x != null) && (predicate == null || predicate(x))); } + public static Type GetValidType(this Assembly asm, string typeName) + => GetValidType(asm, typeName, null); + + public static Type GetValidType(this Assembly asm, string typeName, LemonFunc predicate) + { + Type x = null; + try { x = asm.GetType(typeName); } + catch (Exception ex) + { + MelonLogger.Error($"Failed to get type {typeName} from assembly {asm.FullName} due to: {ex.Message}", ex); + x = null; + } + if ((x != null) && (predicate == null || predicate(x))) + return x; + return null; + } + public static bool IsNotImplemented(this MethodBase methodBase) { if (methodBase == null) @@ -470,10 +487,13 @@ public static string GetFileProductName(string filepath) internal static void SetupWineCheck() { - if (MelonUtils.IsUnix || MelonUtils.IsMac) + if (IsUnix || IsMac) return; IntPtr dll = NativeLibrary.LoadLib("ntdll.dll"); + if (dll == IntPtr.Zero) + return; + IntPtr wine_get_version_proc = NativeLibrary.AgnosticGetProcAddress(dll, "wine_get_version"); if (wine_get_version_proc == IntPtr.Zero) return; diff --git a/MelonLoader/Utils/MelonLogger.cs b/MelonLoader/Utils/MelonLogger.cs index 9f0110c0..117a6937 100644 --- a/MelonLoader/Utils/MelonLogger.cs +++ b/MelonLoader/Utils/MelonLogger.cs @@ -6,6 +6,7 @@ using System.Text; using static MelonLoader.Utils.LoggerUtils; using System.Collections.Generic; +using System.Text.RegularExpressions; namespace MelonLoader { @@ -97,34 +98,68 @@ internal static void WriteLogToFile(string message) internal static void MsgDirect(string txt) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, txt, true); public static void Msg(object obj) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, obj.ToString()); + public static void Msg(string txt) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, txt); + public static void Msg(string txt, params object[] args) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); public static void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); + public static void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); + public static void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); //Identical to Msg(Color, string) except it skips walking the stack to find a melon public static void MsgDirect(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt, true); - + public static void Msg(Color txt_color, object obj) => NativeMsg(DefaultMelonColor, txt_color, null, obj.ToString()); + public static void Msg(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt); + public static void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); + //Identical to MsgPastel(string) except it skips walking the stack to find a melon + internal static void MsgPastelDirect(string txt) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, txt, true); + + public static void MsgPastel(object obj) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, obj.ToString()); + + public static void MsgPastel(string txt) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, txt); + + public static void MsgPastel(string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); + + public static void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); + + public static void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); + + public static void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); + + //Identical to MsgPastel(Color, string) except it skips walking the stack to find a melon + public static void MsgPastelDirect(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt, true); + + public static void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DefaultMelonColor, txt_color, null, obj.ToString()); + + public static void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt); + + public static void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); public static void Warning(object obj) => NativeWarning(null, obj.ToString()); + public static void Warning(string txt) => NativeWarning(null, txt); - public static void Warning(string txt, params object[] args) => NativeWarning(null, string.Format(txt, args)); + public static void Warning(string txt, params object[] args) => NativeWarning(null, string.Format(txt, args)); public static void Error(object obj) => NativeError(null, obj.ToString()); + public static void Error(string txt) => NativeError(null, txt); + public static void Error(string txt, params object[] args) => NativeError(null, string.Format(txt, args)); + public static void Error(string txt, Exception ex) => NativeError(null, $"{txt}\n{ex}"); public static void WriteLine(int length = 30) => MsgDirect(new string('-', length)); + public static void WriteLine(Color color, int length = 30) => MsgDirect(color, new string('-', length)); - + private static void NativeMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) { if (string.IsNullOrEmpty(namesection)) @@ -141,6 +176,22 @@ private static void NativeMsg(Color namesection_color, Color txt_color, string n RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); } + private static void NativePastelMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) + { + if (string.IsNullOrEmpty(namesection)) + { + MelonBase melon = MelonUtils.GetMelonFromStackTrace(); + if (melon != null) + { + namesection = melon.Info?.Name?.Replace(" ", "_"); + namesection_color = melon.ConsoleColor; + } + } + + Internal_PastelMsg(namesection_color, txt_color, namesection, txt ?? "null"); + RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); + } + private static void NativeWarning(string namesection, string txt) { namesection ??= MelonUtils.GetMelonFromStackTrace()?.Info?.Name?.Replace(" ", "_"); @@ -177,50 +228,89 @@ internal static void RunMsgCallbacks(Color namesection_color, Color txt_color, s public static event Action MsgCallbackHandler; public static event Action MsgDrawingCallbackHandler; + internal static void RunWarningCallbacks(string namesection, string txt) => WarningCallbackHandler?.Invoke(namesection, txt); + public static event Action WarningCallbackHandler; + internal static void RunErrorCallbacks(string namesection, string txt) => ErrorCallbackHandler?.Invoke(namesection, txt); + public static event Action ErrorCallbackHandler; public class Instance { private string Name = null; + [Obsolete("Color is obsolete. Please use DrawingColor for full Color support.")] private ConsoleColor Color { get => DrawingColorToConsoleColor(DrawingColor); set => DrawingColor = ConsoleColorToDrawingColor(value); } + private Color DrawingColor = DefaultMelonColor; public Instance(string name) => Name = name?.Replace(" ", "_"); + [Obsolete("ConsoleColor is obsolete, use the (string, Color) constructor instead.")] public Instance(string name, ConsoleColor color) : this(name) => Color = color; + public Instance(string name, Color color) : this(name) => DrawingColor = color; + public void Msg(object obj) => NativeMsg(DrawingColor, DefaultTextColor, Name, obj.ToString()); + public void Msg(string txt) => NativeMsg(DrawingColor, DefaultTextColor, Name, txt); - public void Msg(string txt, params object[] args) => NativeMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); + public void Msg(string txt, params object[] args) => NativeMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); public void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); + public void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); + public void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); public void Msg(Color txt_color, object obj) => NativeMsg(DrawingColor, txt_color, Name, obj.ToString()); + public void Msg(Color txt_color, string txt) => NativeMsg(DrawingColor, txt_color, Name, txt); + public void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); + public void MsgPastel(object obj) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, obj.ToString()); + + public void MsgPastel(string txt) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, txt); + + public void MsgPastel(string txt, params object[] args) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); + + public void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); + + public void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); + + public void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); + + public void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DrawingColor, txt_color, Name, obj.ToString()); + + public void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DrawingColor, txt_color, Name, txt); + + public void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); + public void Warning(object obj) => NativeWarning(Name, obj.ToString()); + public void Warning(string txt) => NativeWarning(Name, txt); + public void Warning(string txt, params object[] args) => NativeWarning(Name, string.Format(txt, args)); public void Error(object obj) => NativeError(Name, obj.ToString()); + public void Error(string txt) => NativeError(Name, txt); + public void Error(string txt, params object[] args) => NativeError(Name, string.Format(txt, args)); + public void Error(string txt, Exception ex) => NativeError(Name, $"{txt}\n{ex}"); - + public void WriteSpacer() => MelonLogger.WriteSpacer(); + public void WriteLine(int length = 30) => MelonLogger.WriteLine(length); + public void WriteLine(Color color, int length = 30) => MelonLogger.WriteLine(color, length); public void BigError(string txt) => MelonLogger.BigError(Name, txt); @@ -245,6 +335,28 @@ internal static void Internal_Msg(Color namesection_color, Color txt_color, stri Utils.MelonConsole.WriteLine(builder.ToString()); } + internal static void Internal_PastelMsg(Color namesection_color, Color txt_color, string namesection, string txt) + { + // Regex to check for ANSI + string fileTxt = Regex.Replace(txt, @"(\x1B|\e|\033)\[(.*?)m", ""); + + WriteLogToFile($"[{GetTimeStamp()}] {(namesection is null ? "" : $"[{namesection}] ")}{fileTxt}"); + + StringBuilder builder = new StringBuilder(); + + builder.Append(GetTimestamp(namesection_color == Color.IndianRed && txt_color == Color.IndianRed)); + + if (namesection is not null) + { + builder.Append("[".Pastel(Color.LightGray)); + builder.Append(namesection.Pastel(namesection_color)); + builder.Append("] ".Pastel(Color.LightGray)); + } + + builder.Append(txt.Pastel(txt_color)); + Utils.MelonConsole.WriteLine(builder.ToString()); + } + internal static string GetTimestamp(bool error) { StringBuilder builder = new StringBuilder(); @@ -271,13 +383,10 @@ internal static void Internal_Warning(string namesection, string txt) Internal_Msg(Color.Yellow, Color.Yellow, namesection, txt); } - internal static void Internal_Error(string namesection, string txt) => Internal_Msg(Color.IndianRed, Color.IndianRed, namesection, txt); - internal static void ThrowInternalFailure(string txt) => Assertion.ThrowInternalFailure(txt); - internal static void WriteSpacer() { WriteLogToFile(); @@ -297,7 +406,8 @@ internal static void Internal_PrintModName(Color meloncolor, Color authorcolor, builder.Append(GetTimestamp(false)); builder.Append($"by {author}".Pastel(authorcolor)); - if (additionalCredits is not null) { + if (additionalCredits is not null) + { builder.AppendLine(); builder.Append(GetTimestamp(false)); builder.Append($"Additional credits: {additionalCredits}"); @@ -318,25 +428,33 @@ internal static void Close() CachedLogWriter.Close(); } - [Obsolete("Log is obsolete. Please use Msg instead.")] public static void Log(string txt) => Msg(txt); + [Obsolete("Log is obsolete. Please use Msg instead.")] public static void Log(string txt, params object[] args) => Msg(txt, args); + [Obsolete("Log is obsolete. Please use Msg instead.")] public static void Log(object obj) => Msg(obj); + [Obsolete("Log is obsolete. Please use Msg instead.")] public static void Log(ConsoleColor color, string txt) => Msg(color, txt); + [Obsolete("Log is obsolete. Please use Msg instead.")] public static void Log(ConsoleColor color, string txt, params object[] args) => Msg(color, txt, args); + [Obsolete("Log is obsolete. Please use Msg instead.")] public static void Log(ConsoleColor color, object obj) => Msg(color, obj); + [Obsolete("LogWarning is obsolete. Please use Warning instead.")] public static void LogWarning(string txt) => Warning(txt); + [Obsolete("LogWarning is obsolete. Please use Warning instead.")] public static void LogWarning(string txt, params object[] args) => Warning(txt, args); + [Obsolete("LogError is obsolete. Please use Error instead.")] public static void LogError(string txt) => Error(txt); + [Obsolete("LogError is obsolete. Please use Error instead.")] public static void LogError(string txt, params object[] args) => Error(txt, args); } diff --git a/README.md b/README.md index 7cb122f5..f8b4b8f7 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ Third-party Libraries used as Source Code and/or bundled in Binary Form: - [ValueTupleBridge](https://github.com/OrangeCube/MinimumAsyncBridge) is licensed under the MIT License. See [LICENSE](https://github.com/OrangeCube/MinimumAsyncBridge/blob/master/LICENSE) for the full License. - [WebSocketDotNet](https://github.com/SamboyCoding/WebSocketDotNet) is licensed under the MIT License. See [LICENSE](https://github.com/SamboyCoding/WebSocketDotNet/blob/master/LICENSE) for the full License. - [Pastel](https://github.com/silkfire/Pastel) is licensed under the MIT License. See [LICENSE](https://github.com/silkfire/Pastel/blob/master/LICENSE) for the full License. -- [Il2CppInterop](https://github.com/ds5678/Il2CppInterop) is licensed under LGPLv3 License. See [LICENSE](https://github.com/ds5678/Il2CppInterop/blob/master/LICENSE) for the full License. +- [Il2CppInterop](https://github.com/BepInEx/Il2CppInterop) is licensed under LGPLv3 License. See [LICENSE](https://github.com/BepInEx/Il2CppInterop/blob/master/LICENSE) for the full License. External Libraries and Tools that are downloaded and used at Runtime: