Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Generics Unstripping #170

Merged
merged 3 commits into from
Sep 21, 2024
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
5 changes: 4 additions & 1 deletion Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,12 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth
internal static TypeSignature? ResolveTypeInNewAssembliesRaw(RewriteGlobalContext context, TypeSignature? unityType,
RuntimeAssemblyReferences imports)
{
if (unityType is null or GenericParameterSignature)
if (unityType is null)
return null;

if (unityType is GenericParameterSignature genericParameterSignature)
return new GenericParameterSignature(imports.Module, genericParameterSignature.ParameterType, genericParameterSignature.Index);

if (unityType is ByReferenceTypeSignature)
{
var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports);
Expand Down
96 changes: 46 additions & 50 deletions Il2CppInterop.Generator/Utils/UnstripTranslator.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Diagnostics;
using AsmResolver;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Collections;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
using Il2CppInterop.Generator.Contexts;
using Il2CppInterop.Generator.Extensions;
using Il2CppInterop.Generator.Passes;

namespace Il2CppInterop.Generator.Utils;
Expand Down Expand Up @@ -160,7 +160,7 @@ public static bool TranslateMethod(MethodDefinition original, MethodDefinition t
var methodDeclarer =
Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, methodArg.DeclaringType?.ToTypeSignature(), imports);
if (methodDeclarer == null)
return false; // todo: generic methods
return false;

var newReturnType =
Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, methodArg.Signature?.ReturnType, imports);
Expand All @@ -180,38 +180,46 @@ public static bool TranslateMethod(MethodDefinition original, MethodDefinition t
newMethodSignature.ParameterTypes.Add(newParamType);
}

var newMethod = new MemberReference(methodDeclarer.ToTypeDefOrRef(), methodArg.Name, newMethodSignature);
var memberReference = new MemberReference(methodDeclarer.ToTypeDefOrRef(), methodArg.Name, newMethodSignature);

var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, imports.Module.DefaultImporter.ImportMethod(newMethod));
instructionMap.Add(bodyInstruction, newInstruction);
}
else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineType)
{
var targetType = ((ITypeDefOrRef)bodyInstruction.Operand).ToTypeSignature();
if (targetType is GenericParameterSignature genericParam)
IMethodDescriptor newMethod;
if (methodArg is MethodSpecification genericMethod)
{
if (genericParam.ParameterType is GenericParameterType.Type)
if (genericMethod.Signature is null)
return false;

TypeSignature[] typeArguments = new TypeSignature[genericMethod.Signature.TypeArguments.Count];
for (var i = 0; i < genericMethod.Signature.TypeArguments.Count; i++)
{
var newTypeOwner =
Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, original.DeclaringType?.ToTypeSignature(), imports)?.Resolve();
if (newTypeOwner == null)
var newTypeArgument = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, genericMethod.Signature.TypeArguments[i], imports);
if (newTypeArgument == null)
return false;
targetType = newTypeOwner.GenericParameters.Single(it => it.Name == targetType.Name).ToTypeSignature();
}
else
{
targetType = target.GenericParameters.Single(it => it.Name == targetType.Name).ToTypeSignature();

typeArguments[i] = newTypeArgument;
}

newMethod = memberReference.MakeGenericInstanceMethod(typeArguments);
}
else
{
targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, targetType, imports);
if (targetType == null)
return false;
newMethod = memberReference;
}

if (bodyInstruction.OpCode == OpCodes.Castclass && !targetType.IsValueType)
var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, imports.Module.DefaultImporter.ImportMethod(newMethod));
instructionMap.Add(bodyInstruction, newInstruction);
}
else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineType)
{
var targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, ((ITypeDefOrRef)bodyInstruction.Operand).ToTypeSignature(), imports);
if (targetType == null)
return false;

if ((bodyInstruction.OpCode == OpCodes.Castclass && !targetType.IsValueType) ||
(bodyInstruction.OpCode == OpCodes.Unbox_Any && targetType is GenericParameterSignature))
{
// Compilers use unbox.any for casting to generic parameter types.
// Castclass is only used for reference types.
// Both can be translated to Il2CppObjectBase.Cast<T>().
var newInstruction = targetBuilder.Add(OpCodes.Call,
imports.Module.DefaultImporter.ImportMethod(imports.Il2CppObjectBase_Cast.Value.MakeGenericInstanceMethod(targetType)));
instructionMap.Add(bodyInstruction, newInstruction);
Expand Down Expand Up @@ -256,36 +264,24 @@ public static bool TranslateMethod(MethodDefinition original, MethodDefinition t
}
else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineTok)
{
var targetTok = (bodyInstruction.Operand as ITypeDefOrRef)?.ToTypeSignature();
if (targetTok == null)
return false;
if (targetTok is GenericParameterSignature genericParam)
Debug.Assert(bodyInstruction.OpCode.Code is CilCode.Ldtoken);
switch (bodyInstruction.Operand)
{
if (genericParam.ParameterType is GenericParameterType.Type)
{
var newTypeOwner =
Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, original.DeclaringType?.ToTypeSignature(), imports)?.Resolve();
if (newTypeOwner == null)
return false;
var name = original.DeclaringType!.GenericParameters[genericParam.Index].Name;
targetTok = newTypeOwner.GenericParameters.Single(it => it.Name == name).ToTypeSignature();
}
else
{
var name = original.GenericParameters[genericParam.Index].Name;
targetTok = target.GenericParameters.Single(it => it.Name == name).ToTypeSignature();
}
}
else
{
targetTok = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, targetTok, imports);
if (targetTok == null)
case ITypeDefOrRef typeDefOrRef:
{
var targetTok = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, typeDefOrRef.ToTypeSignature(), imports);
if (targetTok == null)
return false;

var newInstruction = targetBuilder.Add(OpCodes.Call,
imports.Module.DefaultImporter.ImportMethod(imports.Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle.Value.MakeGenericInstanceMethod(targetTok)));
instructionMap.Add(bodyInstruction, newInstruction);
}
break;
default:
// Ldtoken is also used for members, which is not implemented.
return false;
}

var newInstruction = targetBuilder.Add(OpCodes.Call,
imports.Module.DefaultImporter.ImportMethod(imports.Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle.Value.MakeGenericInstanceMethod(targetTok)));
instructionMap.Add(bodyInstruction, newInstruction);
}
else if (bodyInstruction.OpCode.OperandType is CilOperandType.InlineSwitch && bodyInstruction.Operand is IReadOnlyList<ICilLabel> labels)
{
Expand Down
Loading