From f2b2e69519312331ee8a8ac35d88ebecd4906958 Mon Sep 17 00:00:00 2001 From: slxdy Date: Sat, 14 Dec 2024 02:03:57 +0100 Subject: [PATCH 01/13] Fix generator memory leak (#193) --- Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs | 5 +---- Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs | 2 ++ Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs | 4 ++-- .../Passes/Pass81FillUnstrippedMethodBodies.cs | 3 +++ .../Runners/InteropAssemblyGenerator.cs | 7 +++++++ 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs index 01a714cf..057a2ac3 100644 --- a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs @@ -9,9 +9,6 @@ namespace Il2CppInterop.Generator.Contexts; [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] public class AssemblyRewriteContext { - // TODO: Dispose - private static readonly Dictionary ImportsMap = new(); - public readonly RewriteGlobalContext GlobalContext; public readonly RuntimeAssemblyReferences Imports; @@ -30,7 +27,7 @@ public AssemblyRewriteContext(RewriteGlobalContext globalContext, AssemblyDefini NewAssembly = newAssembly; GlobalContext = globalContext; - Imports = ImportsMap.GetOrCreate(newAssembly.ManifestModule!, + Imports = globalContext.ImportsMap.GetOrCreate(newAssembly.ManifestModule!, mod => new RuntimeAssemblyReferences(mod, globalContext)); } diff --git a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs b/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs index a9f390f1..454a926f 100644 --- a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs +++ b/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs @@ -19,6 +19,8 @@ public class RewriteGlobalContext : IDisposable internal readonly Dictionary<(object?, string, int), List> RenameGroups = new(); + internal readonly Dictionary ImportsMap = new(); + public RewriteGlobalContext(GeneratorOptions options, IIl2CppMetadataAccess gameAssemblies, IMetadataAccess unityAssemblies) { diff --git a/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs b/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs index 9dea948c..f16a3541 100644 --- a/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs +++ b/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs @@ -10,8 +10,8 @@ namespace Il2CppInterop.Generator.Passes; public static class Pass16ScanMethodRefs { - public static readonly HashSet NonDeadMethods = new(); - public static IDictionary> MapOfCallers = new Dictionary>(); + internal static HashSet NonDeadMethods = new(); + internal static IDictionary> MapOfCallers = new Dictionary>(); public static void DoPass(RewriteGlobalContext context, GeneratorOptions options) { diff --git a/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs b/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs index ffcef3a1..705e419a 100644 --- a/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs +++ b/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs @@ -32,6 +32,9 @@ public static void DoPass(RewriteGlobalContext context) } } + StuffToProcess.Clear(); + StuffToProcess.Capacity = 0; + Logger.Instance.LogInformation("IL unstrip statistics: {MethodsSucceeded} successful, {MethodsFailed} failed", methodsSucceeded, methodsFailed); } diff --git a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs index b1c66b7c..f3f3efe7 100644 --- a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs +++ b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs @@ -1,4 +1,5 @@ using Il2CppInterop.Common; +using Il2CppInterop.Common.XrefScans; using Il2CppInterop.Generator.Contexts; using Il2CppInterop.Generator.MetadataAccess; using Il2CppInterop.Generator.Passes; @@ -201,6 +202,12 @@ public void Run(GeneratorOptions options) Pass91GenerateMethodPointerMap.DoPass(rewriteContext, options); } + using (new TimingCookie("Clearing static data")) + { + Pass16ScanMethodRefs.MapOfCallers = new Dictionary>(); + Pass16ScanMethodRefs.NonDeadMethods = []; + } + Logger.Instance.LogInformation("Done!"); rewriteContext.Dispose(); From ba67c11c95dd2b8b67daf5704a8453837dbe2281 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:53:13 -0800 Subject: [PATCH 02/13] Switch from using `AttributeAttribute::Name` to `AttributeAttribute::Type` (#194) * Switch from using `AttributeAttribute::Name` to `AttributeAttribute::Type` * Support backwards compatibility --- .../Passes/Pass70GenerateProperties.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs b/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs index 921075f4..e80e7911 100644 --- a/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs +++ b/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs @@ -42,17 +42,13 @@ public static void DoPass(RewriteGlobalContext context) } string? defaultMemberName = null; - var defaultMemberAttributeAttribute = type.CustomAttributes.FirstOrDefault(it => - it.AttributeType()?.Name == "AttributeAttribute" && it.Signature!.NamedArguments.Any(it => - it.MemberName == "Name" && it.Argument.GetElementAsString() == nameof(DefaultMemberAttribute))); - if (defaultMemberAttributeAttribute != null) + if (type.CustomAttributes.FirstOrDefault(IsDefaultMemberAttributeFake) != null) { defaultMemberName = "Item"; } else { - var realDefaultMemberAttribute = - type.CustomAttributes.FirstOrDefault(it => it.AttributeType()?.Name == nameof(DefaultMemberAttribute)); + var realDefaultMemberAttribute = type.CustomAttributes.FirstOrDefault(IsDefaultMemberAttributeReal); if (realDefaultMemberAttribute != null) defaultMemberName = realDefaultMemberAttribute.Signature?.FixedArguments[0].Element?.ToString() ?? "Item"; } @@ -63,6 +59,21 @@ public static void DoPass(RewriteGlobalContext context) assemblyContext.Imports.Module.DefaultMemberAttribute().ToTypeDefOrRef(), assemblyContext.Imports.Module.String()), new CustomAttributeSignature(new CustomAttributeArgument(assemblyContext.Imports.Module.String(), defaultMemberName)))); } + + static bool IsDefaultMemberAttributeFake(CustomAttribute attribute) + { + return attribute.AttributeType()?.Name == "AttributeAttribute" && attribute.Signature!.NamedArguments.Any(it => + { + // Name support is for backwards compatibility. + return (it.MemberName == "Type" && it.Argument.Element is ITypeDescriptor { Namespace: "System.Reflection", Name: nameof(DefaultMemberAttribute) }) + || (it.MemberName == "Name" && it.Argument.GetElementAsString() == nameof(DefaultMemberAttribute)); + }); + } + + static bool IsDefaultMemberAttributeReal(CustomAttribute attribute) + { + return attribute.AttributeType() is { Namespace.Value: "System.Reflection", Name.Value: nameof(DefaultMemberAttribute) }; + } } private static string UnmanglePropertyName(AssemblyRewriteContext assemblyContext, PropertyDefinition prop, From 3f979337b0335dd05a603508e23e80a7a12d3ce3 Mon Sep 17 00:00:00 2001 From: Artem Abramov Date: Sun, 5 Jan 2025 00:56:14 +0400 Subject: [PATCH 03/13] Fix issue #140 (AssetBundles cannot be loaded at runtime) (#197) * Fix issue #140 (AssetBundles cannot be loaded at runtime) * Revert Il2CppObjectPool.cs and change incorrect typeof * Revert typeof changes and correction IL code comment --- Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs b/Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs index ef7c40e4..08e09523 100644 --- a/Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs +++ b/Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs @@ -84,8 +84,8 @@ public T Unbox() where T : unmanaged private static readonly Type[] _intPtrTypeArray = { typeof(IntPtr) }; private static readonly MethodInfo _getUninitializedObject = typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.GetUninitializedObject))!; private static readonly MethodInfo _getTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!; - private static readonly MethodInfo _createGCHandle = typeof(Il2CppObjectBase).GetMethod(nameof(CreateGCHandle))!; - private static readonly FieldInfo _isWrapped = typeof(Il2CppObjectBase).GetField(nameof(isWrapped))!; + private static readonly MethodInfo _createGCHandle = typeof(Il2CppObjectBase).GetMethod(nameof(CreateGCHandle), BindingFlags.Instance | BindingFlags.NonPublic)!; + private static readonly FieldInfo _isWrapped = typeof(Il2CppObjectBase).GetField(nameof(isWrapped), BindingFlags.Instance | BindingFlags.NonPublic)!; internal static class InitializerStore { @@ -112,7 +112,7 @@ private static Func Create() // However, it could be be user-made or implicit // In that case we set the GCHandle and then call the ctor and let GC destroy any objects created by DerivedConstructorPointer - // var obj = (T)FormatterServices.GetUninitializedObject(type); + // var obj = (T)RuntimeHelpers.GetUninitializedObject(type); il.Emit(OpCodes.Ldtoken, type); il.Emit(OpCodes.Call, _getTypeFromHandle); il.Emit(OpCodes.Call, _getUninitializedObject); @@ -126,7 +126,7 @@ private static Func Create() // obj.isWrapped = true; il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Stsfld, _isWrapped); + il.Emit(OpCodes.Stfld, _isWrapped); var parameterlessConstructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.EmptyTypes); if (parameterlessConstructor != null) From a096080da8a80d4d9b6db4a804c6db90b6a4b509 Mon Sep 17 00:00:00 2001 From: y0soro Date: Sat, 11 Jan 2025 22:36:26 +0800 Subject: [PATCH 04/13] Add a more specific RunFinalizer signature for Summer Vacation! Scramble (#199) The "Test Game" signature is a bit general that it matched `MS.Internal.Xml.Cache.XPathDocumentNavigator$$MoveToId` in Summer Vacation! Scramble(SVS) assembly, causing NULL finalier access crashes due to HasFinalize not being handled. The one actually matched RunFinalizer in SVS is "V Rising" signature, though it's listed after the "Test Game" signature. Although a simple reorder can fix the issue for SVS, both signatures mentioned are quiet general so this could breaks other games relying on this matching order, otherwise the same crash can happen due to hooking on the wrong location. Thus I instead add a more specific signature that should solely matches RunFinalizer on SVS or other games with similar compilation options before the too general one. --- .../Injection/Hooks/GarbageCollector_RunFinalizer_Patch.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Il2CppInterop.Runtime/Injection/Hooks/GarbageCollector_RunFinalizer_Patch.cs b/Il2CppInterop.Runtime/Injection/Hooks/GarbageCollector_RunFinalizer_Patch.cs index caa46ea1..34aa2a13 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/GarbageCollector_RunFinalizer_Patch.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/GarbageCollector_RunFinalizer_Patch.cs @@ -38,6 +38,13 @@ private void Hook(IntPtr obj, IntPtr data) xref = false }, new() + { + // Summer Vacation! Scramble - 2021.3.33 (x64) + pattern = "\x48\x89\x5c\x24\x10\x48\x89\x74\x24\x18\x57\x48\x83\xec\x20\x48\x8b\x19\x33\xff\x48\x89\x7c\x24\x30\x48\x8b\xf1\xf6\x83\x32\x01\x00\x00\x02\x75\x08\x48\x8b\xcb\xe8", + mask = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + xref = false, + }, + new() { // Test Game - 2021.3.22 (x64) pattern = "\x40\x53\x48\x83\xEC\x20\x48\x8B\xD9\x48\xC7\x44\x24\x30\x00\x00\x00\x00\x48\x8B", From 2c4520f3d530bfcb02212664234f1adb6d42c6b8 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:44:52 -0500 Subject: [PATCH 05/13] update disarm --- Il2CppInterop.Common/Il2CppInterop.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Il2CppInterop.Common/Il2CppInterop.Common.csproj b/Il2CppInterop.Common/Il2CppInterop.Common.csproj index 671f18c2..c3c7b441 100644 --- a/Il2CppInterop.Common/Il2CppInterop.Common.csproj +++ b/Il2CppInterop.Common/Il2CppInterop.Common.csproj @@ -8,7 +8,7 @@ - + From 4201b30bde9b219cd7c660c3b84338edbef97575 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:18:07 -0500 Subject: [PATCH 06/13] use BNM class init method --- Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs | 11 +++++++---- Il2CppInterop.Runtime/Injection/InjectorHelpers.cs | 10 ++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs b/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs index 17e5eda6..535734eb 100644 --- a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs +++ b/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs @@ -13,10 +13,13 @@ public static IEnumerable JumpTargets(IntPtr codeStart, bool ignoreRetn if (instruction.Mnemonic == Arm64Mnemonic.RET && !ignoreRetn) yield break; - var jump = instruction.Mnemonic is Arm64Mnemonic.B or Arm64Mnemonic.BC or Arm64Mnemonic.BR; - var call = instruction.Mnemonic is Arm64Mnemonic.BL or Arm64Mnemonic.BLR; - - if ((jump || call) && instruction.MnemonicConditionCode == Arm64ConditionCode.NONE && instruction.FinalOpConditionCode == Arm64ConditionCode.NONE) + if (instruction is + { + // Check if jump or call instruction + Mnemonic: Arm64Mnemonic.B or Arm64Mnemonic.BC or Arm64Mnemonic.BR or Arm64Mnemonic.BL or Arm64Mnemonic.BLR, + MnemonicConditionCode: Arm64ConditionCode.NONE, + FinalOpConditionCode: Arm64ConditionCode.NONE + }) { var target = XrefScanUtilFinder.ExtractTargetAddress(instruction); yield return (IntPtr)target; diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs index 9bed22f2..485f9b11 100644 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs @@ -149,6 +149,16 @@ private static d_ClassInit FindClassInit() { static nint GetClassInitSubstitute() { + if (TryGetIl2CppExport(nameof(IL2CPP.il2cpp_array_new_specific), out nint arrayNewSpecific)) + { + // https://github.com/ByNameModding/BNM-Android/blob/3edeec43d74fc4392ba1b1eb9d5002e1b2ef2a67/src/Loading.cpp#L296 + var bnmClassInit = XrefScannerLowLevel.JumpTargets(XrefScannerLowLevel.JumpTargets(arrayNewSpecific).First()).First(); + if (bnmClassInit != IntPtr.Zero) + { + Logger.Instance.LogTrace("Used BNM Method to find Class::Init."); + return bnmClassInit; + } + } if (TryGetIl2CppExport("mono_class_instance_size", out nint classInit)) { Logger.Instance.LogTrace("Picked mono_class_instance_size as a Class::Init substitute"); From 2143dba2c6a706dcffd66d01d2e3df612cfb73f0 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sat, 8 Feb 2025 21:05:57 -0500 Subject: [PATCH 07/13] Revert "use BNM class init method" This reverts commit f2ff0b2c534ed3102037ef0ca137ea23f0cc9450. --- Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs | 11 ++++------- Il2CppInterop.Runtime/Injection/InjectorHelpers.cs | 10 ---------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs b/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs index 535734eb..17e5eda6 100644 --- a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs +++ b/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs @@ -13,13 +13,10 @@ public static IEnumerable JumpTargets(IntPtr codeStart, bool ignoreRetn if (instruction.Mnemonic == Arm64Mnemonic.RET && !ignoreRetn) yield break; - if (instruction is - { - // Check if jump or call instruction - Mnemonic: Arm64Mnemonic.B or Arm64Mnemonic.BC or Arm64Mnemonic.BR or Arm64Mnemonic.BL or Arm64Mnemonic.BLR, - MnemonicConditionCode: Arm64ConditionCode.NONE, - FinalOpConditionCode: Arm64ConditionCode.NONE - }) + var jump = instruction.Mnemonic is Arm64Mnemonic.B or Arm64Mnemonic.BC or Arm64Mnemonic.BR; + var call = instruction.Mnemonic is Arm64Mnemonic.BL or Arm64Mnemonic.BLR; + + if ((jump || call) && instruction.MnemonicConditionCode == Arm64ConditionCode.NONE && instruction.FinalOpConditionCode == Arm64ConditionCode.NONE) { var target = XrefScanUtilFinder.ExtractTargetAddress(instruction); yield return (IntPtr)target; diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs index 485f9b11..9bed22f2 100644 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs @@ -149,16 +149,6 @@ private static d_ClassInit FindClassInit() { static nint GetClassInitSubstitute() { - if (TryGetIl2CppExport(nameof(IL2CPP.il2cpp_array_new_specific), out nint arrayNewSpecific)) - { - // https://github.com/ByNameModding/BNM-Android/blob/3edeec43d74fc4392ba1b1eb9d5002e1b2ef2a67/src/Loading.cpp#L296 - var bnmClassInit = XrefScannerLowLevel.JumpTargets(XrefScannerLowLevel.JumpTargets(arrayNewSpecific).First()).First(); - if (bnmClassInit != IntPtr.Zero) - { - Logger.Instance.LogTrace("Used BNM Method to find Class::Init."); - return bnmClassInit; - } - } if (TryGetIl2CppExport("mono_class_instance_size", out nint classInit)) { Logger.Instance.LogTrace("Picked mono_class_instance_size as a Class::Init substitute"); From 96669ecd4bd1306c5221d262fffbe7ad14bffaa1 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:38:23 -0500 Subject: [PATCH 08/13] Reapply "use BNM class init method" This reverts commit 2143dba2c6a706dcffd66d01d2e3df612cfb73f0. --- Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs | 11 +++++++---- Il2CppInterop.Runtime/Injection/InjectorHelpers.cs | 10 ++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs b/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs index 17e5eda6..535734eb 100644 --- a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs +++ b/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs @@ -13,10 +13,13 @@ public static IEnumerable JumpTargets(IntPtr codeStart, bool ignoreRetn if (instruction.Mnemonic == Arm64Mnemonic.RET && !ignoreRetn) yield break; - var jump = instruction.Mnemonic is Arm64Mnemonic.B or Arm64Mnemonic.BC or Arm64Mnemonic.BR; - var call = instruction.Mnemonic is Arm64Mnemonic.BL or Arm64Mnemonic.BLR; - - if ((jump || call) && instruction.MnemonicConditionCode == Arm64ConditionCode.NONE && instruction.FinalOpConditionCode == Arm64ConditionCode.NONE) + if (instruction is + { + // Check if jump or call instruction + Mnemonic: Arm64Mnemonic.B or Arm64Mnemonic.BC or Arm64Mnemonic.BR or Arm64Mnemonic.BL or Arm64Mnemonic.BLR, + MnemonicConditionCode: Arm64ConditionCode.NONE, + FinalOpConditionCode: Arm64ConditionCode.NONE + }) { var target = XrefScanUtilFinder.ExtractTargetAddress(instruction); yield return (IntPtr)target; diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs index 9bed22f2..485f9b11 100644 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs @@ -149,6 +149,16 @@ private static d_ClassInit FindClassInit() { static nint GetClassInitSubstitute() { + if (TryGetIl2CppExport(nameof(IL2CPP.il2cpp_array_new_specific), out nint arrayNewSpecific)) + { + // https://github.com/ByNameModding/BNM-Android/blob/3edeec43d74fc4392ba1b1eb9d5002e1b2ef2a67/src/Loading.cpp#L296 + var bnmClassInit = XrefScannerLowLevel.JumpTargets(XrefScannerLowLevel.JumpTargets(arrayNewSpecific).First()).First(); + if (bnmClassInit != IntPtr.Zero) + { + Logger.Instance.LogTrace("Used BNM Method to find Class::Init."); + return bnmClassInit; + } + } if (TryGetIl2CppExport("mono_class_instance_size", out nint classInit)) { Logger.Instance.LogTrace("Picked mono_class_instance_size as a Class::Init substitute"); From 2ab568e28cf96a65bf68fa4f7cd243d341523440 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 9 Feb 2025 12:21:23 -0500 Subject: [PATCH 09/13] fix very weird class injector issue thati dont understand --- Il2CppInterop.Runtime/Injection/ClassInjector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Il2CppInterop.Runtime/Injection/ClassInjector.cs b/Il2CppInterop.Runtime/Injection/ClassInjector.cs index 6db08110..befdab6d 100644 --- a/Il2CppInterop.Runtime/Injection/ClassInjector.cs +++ b/Il2CppInterop.Runtime/Injection/ClassInjector.cs @@ -843,7 +843,7 @@ private static Delegate CreateInvoker(MethodInfo monoMethod) body.Emit(OpCodes.Add_Ovf_Un); var nativeType = parameterInfo.ParameterType.NativeType(); body.Emit(OpCodes.Ldobj, typeof(IntPtr)); - if (nativeType != typeof(IntPtr)) + if (nativeType != typeof(IntPtr) && !nativeType.IsByRef) // if it's a byref, we already have the pointer i think? body.Emit(OpCodes.Ldobj, nativeType); } From 52c3d3af1a21f9e49daff0e44ed1c9309ca3f48e Mon Sep 17 00:00:00 2001 From: extraes <52384576+extraes@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:04:05 -0800 Subject: [PATCH 10/13] Add awaiter implementation (#133) * Add awaiter implementation * Modder commits "worst code ever", asked to leave library Paste from ML plugin, will be changed to remove unnecessary code/comments & improve generic handling * Clean up code & remove vestiges of ML/Cecil * Fix import ordering & remove an unused `using` * Add "CorLib" reference for mscorlib to global context * Changes for PR review - Use CorLibTypeFactory.Void for void reference - Added a couple more early-outs before LINQ - Use .Single - Use MemberCloner (and define a nested type for that) - Check method signature & forward to methods that may have been wonkily unhollowed * Remove commented vestigial code * Remove nullability shut-ups from CreateMemberReference * Changes for PR review - Use .Single for action conversion - Match interface using namespace (in addition to name) - Match method using .OriginalMethod.Name - Use SignatureComparer & change comment to reflect that - Make sure method is instanced - Remove nop opcodes * Bump referenced AsmResolver version * Use explicit method creation Instead of MemberCloner * Remove vestigial code from using MemberCloner was useless anyway, just avoided a compiler warn, lol --- .../Contexts/RewriteGlobalContext.cs | 1 + .../Il2CppInterop.Generator.csproj | 2 +- .../Passes/Pass61ImplementAwaiters.cs | 94 +++++++++++++++++++ .../Runners/InteropAssemblyGenerator.cs | 5 + 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs diff --git a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs b/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs index 454a926f..bab812ea 100644 --- a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs +++ b/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs @@ -55,6 +55,7 @@ public RewriteGlobalContext(GeneratorOptions options, IIl2CppMetadataAccess game public IMetadataAccess UnityAssemblies { get; } public IEnumerable Assemblies => myAssemblies.Values; + public AssemblyRewriteContext CorLib => myAssemblies["mscorlib"]; internal bool HasGcWbarrierFieldWrite { get; set; } diff --git a/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj b/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj index 5e0ac19b..215f95d6 100644 --- a/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj +++ b/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj @@ -13,7 +13,7 @@ - + MonoModBackports diff --git a/Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs b/Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs new file mode 100644 index 00000000..037cdcac --- /dev/null +++ b/Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs @@ -0,0 +1,94 @@ +using System.Runtime.CompilerServices; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Cloning; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Contexts; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Generator.Passes; + +public static class Pass61ImplementAwaiters +{ + public static void DoPass(RewriteGlobalContext context) + { + var corlib = context.CorLib; + + var actionUntyped = corlib.GetTypeByName("System.Action"); + + var actionConversion = actionUntyped.NewType.Methods.Single(m => m.Name == "op_Implicit"); + + foreach (var assemblyContext in context.Assemblies) + { + // Use Lazy as a lazy way to not actually import the references until they're needed + + Lazy actionUntypedRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(actionConversion.Parameters[0].ParameterType.ToTypeDefOrRef())!); + Lazy actionConversionRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(actionConversion)); + Lazy notifyCompletionRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(typeof(INotifyCompletion))); + var voidRef = assemblyContext.NewAssembly.ManifestModule!.CorLibTypeFactory.Void; + + foreach (var typeContext in assemblyContext.Types) + { + // Odds are a majority of types won't implement any interfaces. Skip them to save time. + if (typeContext.OriginalType.IsInterface || typeContext.OriginalType.Interfaces.Count == 0) + continue; + + var iNotifyCompletion = typeof(INotifyCompletion); + var interfaceImplementation = typeContext.OriginalType.Interfaces.SingleOrDefault(interfaceImpl => interfaceImpl.Interface?.Namespace == iNotifyCompletion.Namespace && interfaceImpl.Interface?.Name == iNotifyCompletion.Name); + if (interfaceImplementation is null) + continue; + + var allOnCompleted = typeContext.Methods.Where(m => m.OriginalMethod.Name == nameof(INotifyCompletion.OnCompleted)).Select(mc => mc.NewMethod).ToArray(); + + // Conversion spits out an Il2CppSystem.Action, so look for methods that take that (and only that) in & return void, so the stack is balanced + // And use SignatureComparer because otherwise equality checks would fail due to the TypeSignatures being different references + var interopOnCompleted = allOnCompleted.FirstOrDefault(m => !m.IsStatic && m.Parameters.Count == 1 && m.Signature is not null && SignatureComparer.Default.Equals(m.Signature.ReturnType, voidRef) && SignatureComparer.Default.Equals(m.Signature.ParameterTypes[0], actionConversion.Signature?.ReturnType)); + + if (interopOnCompleted is null) + { + var typeName = typeContext.OriginalType.FullName; + var foundMethodCount = allOnCompleted.Length; + Logger.Instance.LogInformation("Type {typeName} was found to implement INotifyCompletion, but no suitable method was found. {foundMethodCount} method(s) were found with the required name.", typeName, foundMethodCount); + continue; + } + + var onCompletedAttr = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual; + var sig = MethodSignature.CreateInstance(voidRef, [actionUntypedRef.Value.ToTypeSignature()]); + + var proxyOnCompleted = new MethodDefinition(nameof(INotifyCompletion.OnCompleted), onCompletedAttr, sig); + var parameter = proxyOnCompleted.Parameters[0].GetOrCreateDefinition(); + parameter.Name = "continuation"; + + var body = proxyOnCompleted.CilMethodBody ??= new(proxyOnCompleted); + + typeContext.NewType.Interfaces.Add(new(notifyCompletionRef.Value)); + typeContext.NewType.Methods.Add(proxyOnCompleted); + + var instructions = body.Instructions; + instructions.Add(CilOpCodes.Ldarg_0); // load "this" + instructions.Add(CilOpCodes.Ldarg_1); // not static, so ldarg1 loads "continuation" + instructions.Add(CilOpCodes.Call, actionConversionRef.Value); + + // The titular jump to the interop method -- it's gotta reference the method on the right type, so we need to handle generic parameters + // Without this, awaiters declared in generic types like UniTask.Awaiter would effectively try to cast themselves to their untyped versions (UniTask<>.Awaiter in this case, which isn't a thing) + var genericParameterCount = typeContext.NewType.GenericParameters.Count; + if (genericParameterCount > 0) + { + var typeArguments = Enumerable.Range(0, genericParameterCount).Select(i => new GenericParameterSignature(GenericParameterType.Type, i)).ToArray(); + var interopOnCompleteGeneric = typeContext.NewType.MakeGenericInstanceType(typeArguments) + .ToTypeDefOrRef() + .CreateMemberReference(interopOnCompleted.Name, interopOnCompleted.Signature); + instructions.Add(CilOpCodes.Call, interopOnCompleteGeneric); + } + else + { + instructions.Add(CilOpCodes.Call, interopOnCompleted); + } + + instructions.Add(CilOpCodes.Ret); + } + } + } +} diff --git a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs index f3f3efe7..72d46862 100644 --- a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs +++ b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs @@ -149,6 +149,11 @@ public void Run(GeneratorOptions options) Pass60AddImplicitConversions.DoPass(rewriteContext); } + using (new TimingCookie("Implementing awaiters")) + { + Pass61ImplementAwaiters.DoPass(rewriteContext); + } + using (new TimingCookie("Creating properties")) { Pass70GenerateProperties.DoPass(rewriteContext); From 69695571b58e4c4d0a9b79aaf612e71443985d15 Mon Sep 17 00:00:00 2001 From: Dustin Bryant Date: Fri, 28 Feb 2025 22:02:35 -0700 Subject: [PATCH 11/13] ArrayBaseType handle GenericParameterSignatures --- Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs index 11165cc2..9617dbfd 100644 --- a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs +++ b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs @@ -200,9 +200,12 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if (resolvedElementType == null) return null; if (resolvedElementType.FullName == "System.String") return imports.Il2CppStringArray; - var genericBase = resolvedElementType.IsValueType - ? imports.Il2CppStructArray - : imports.Il2CppReferenceArray; + var genericBase = resolvedElementType switch + { + GenericParameterSignature => imports.Il2CppArrayBase, + { IsValueType: true } => imports.Il2CppStructArray, + _ => imports.Il2CppReferenceArray + }; return new GenericInstanceTypeSignature(genericBase.ToTypeDefOrRef(), false, resolvedElementType); } From eb249278d4e58617ab0d916013ea837e1f56f6a9 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 5 May 2025 18:50:46 -0400 Subject: [PATCH 12/13] bump version --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 42589063..eeb93831 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.4.6 + 1.4.7 BepInEx ../bin/NuGet ../bin/$(MSBuildProjectName) From 763f4279f456feac12c44cd2679e56f12d193610 Mon Sep 17 00:00:00 2001 From: ManlyMarco <39247311+ManlyMarco@users.noreply.github.com> Date: Mon, 12 May 2025 22:33:23 +0200 Subject: [PATCH 13/13] Bump version --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 42589063..57c4ede1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.4.6 + 1.5.0 BepInEx ../bin/NuGet ../bin/$(MSBuildProjectName)