Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.4.6</VersionPrefix>
<VersionPrefix>1.5.1</VersionPrefix>
<Authors>BepInEx</Authors>
<PackageOutputPath>../bin/NuGet</PackageOutputPath>
<OutputPath Condition="'$(Configuration)' == 'Release'">../bin/$(MSBuildProjectName)</OutputPath>
Expand Down
2 changes: 1 addition & 1 deletion Il2CppInterop.Common/Il2CppInterop.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Disarm" Version="2022.1.0-master.36" />
<PackageReference Include="Disarm" Version="2022.1.0-master.57" />
<PackageReference Include="Iced" Version="1.17.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
Expand Down
11 changes: 7 additions & 4 deletions Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ public static IEnumerable<IntPtr> 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;
Expand Down
5 changes: 1 addition & 4 deletions Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ namespace Il2CppInterop.Generator.Contexts;
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public class AssemblyRewriteContext
{
// TODO: Dispose
private static readonly Dictionary<ModuleDefinition, RuntimeAssemblyReferences> ImportsMap = new();

public readonly RewriteGlobalContext GlobalContext;

public readonly RuntimeAssemblyReferences Imports;
Expand All @@ -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));
}

Expand Down
3 changes: 3 additions & 0 deletions Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class RewriteGlobalContext : IDisposable

internal readonly Dictionary<(object?, string, int), List<TypeDefinition>> RenameGroups = new();

internal readonly Dictionary<ModuleDefinition, RuntimeAssemblyReferences> ImportsMap = new();

public RewriteGlobalContext(GeneratorOptions options, IIl2CppMetadataAccess gameAssemblies,
IMetadataAccess unityAssemblies)
{
Expand Down Expand Up @@ -53,6 +55,7 @@ public RewriteGlobalContext(GeneratorOptions options, IIl2CppMetadataAccess game
public IMetadataAccess UnityAssemblies { get; }

public IEnumerable<AssemblyRewriteContext> Assemblies => myAssemblies.Values;
public AssemblyRewriteContext CorLib => myAssemblies["mscorlib"];

internal bool HasGcWbarrierFieldWrite { get; set; }

Expand Down
2 changes: 1 addition & 1 deletion Il2CppInterop.Generator/Il2CppInterop.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AsmResolver.DotNet" Version="6.0.0-beta.1" />
<PackageReference Include="AsmResolver.DotNet" Version="6.0.0-beta.2" />
<PackageReference Include="MonoMod.Backports" Version="1.1.2">
<Aliases>MonoModBackports</Aliases><!-- Transitive dependency from AsmResolver. Extern alias prevents it from affecting us. -->
</PackageReference>
Expand Down
4 changes: 2 additions & 2 deletions Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ namespace Il2CppInterop.Generator.Passes;

public static class Pass16ScanMethodRefs
{
public static readonly HashSet<long> NonDeadMethods = new();
public static IDictionary<long, List<XrefInstance>> MapOfCallers = new Dictionary<long, List<XrefInstance>>();
internal static HashSet<long> NonDeadMethods = new();
internal static IDictionary<long, List<XrefInstance>> MapOfCallers = new Dictionary<long, List<XrefInstance>>();

public static void DoPass(RewriteGlobalContext context, GeneratorOptions options)
{
Expand Down
94 changes: 94 additions & 0 deletions Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs
Original file line number Diff line number Diff line change
@@ -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<ITypeDefOrRef> actionUntypedRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(actionConversion.Parameters[0].ParameterType.ToTypeDefOrRef())!);
Lazy<IMethodDefOrRef> actionConversionRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(actionConversion));
Lazy<ITypeDefOrRef> 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<T>.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);
}
}
}
}
23 changes: 17 additions & 6 deletions Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand All @@ -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,
Expand Down
9 changes: 6 additions & 3 deletions Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public static void DoPass(RewriteGlobalContext context)
}
}

StuffToProcess.Clear();
StuffToProcess.Capacity = 0;

Logger.Instance.LogInformation("IL unstrip statistics: {MethodsSucceeded} successful, {MethodsFailed} failed", methodsSucceeded,
methodsFailed);
}
Expand Down
12 changes: 12 additions & 0 deletions Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Il2CppInterop.Common;
using Il2CppInterop.Common.XrefScans;
using Il2CppInterop.Generator.Contexts;
using Il2CppInterop.Generator.MetadataAccess;
using Il2CppInterop.Generator.Passes;
Expand Down Expand Up @@ -148,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);
Expand Down Expand Up @@ -201,6 +207,12 @@ public void Run(GeneratorOptions options)
Pass91GenerateMethodPointerMap.DoPass(rewriteContext, options);
}

using (new TimingCookie("Clearing static data"))
{
Pass16ScanMethodRefs.MapOfCallers = new Dictionary<long, List<XrefInstance>>();
Pass16ScanMethodRefs.NonDeadMethods = [];
}

Logger.Instance.LogInformation("Done!");

rewriteContext.Dispose();
Expand Down
2 changes: 1 addition & 1 deletion Il2CppInterop.Runtime/Injection/ClassInjector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
10 changes: 10 additions & 0 deletions Il2CppInterop.Runtime/Injection/InjectorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
8 changes: 4 additions & 4 deletions Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public T Unbox<T>() 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<T>
{
Expand All @@ -112,7 +112,7 @@ private static Func<IntPtr, T> 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);
Expand All @@ -126,7 +126,7 @@ private static Func<IntPtr, T> 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)
Expand Down