diff --git a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs index a2b8caac..f4b8514a 100644 --- a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; using Il2CppInterop.Generator.Extensions; using Il2CppInterop.Generator.Utils; using Mono.Cecil; +using Mono.Cecil.Cil; namespace Il2CppInterop.Generator.Contexts; @@ -15,6 +15,7 @@ public class AssemblyRewriteContext public readonly RuntimeAssemblyReferences Imports; private readonly Dictionary myNameTypeMap = new(); private readonly Dictionary myNewTypeMap = new(); + private TypeDefinition isUnmanagedAttributeType; private readonly Dictionary myOldTypeMap = new(); public readonly AssemblyDefinition NewAssembly; @@ -32,7 +33,8 @@ public AssemblyRewriteContext(RewriteGlobalContext globalContext, AssemblyDefini mod => new RuntimeAssemblyReferences(mod, globalContext)); } - public IEnumerable Types => myOldTypeMap.Values; + public IEnumerable Types => myNewTypeMap.Values; + public IEnumerable OriginalTypes => myOldTypeMap.Values; public TypeRewriteContext GetContextForOriginalType(TypeDefinition type) { @@ -49,6 +51,11 @@ public TypeRewriteContext GetContextForNewType(TypeDefinition type) return myNewTypeMap[type]; } + public void RegisterTypeContext(TypeRewriteContext context) + { + myNewTypeMap.Add(context.NewType, context); + } + public void RegisterTypeRewrite(TypeRewriteContext context) { if (context.OriginalType != null) @@ -63,7 +70,7 @@ public MethodReference RewriteMethodRef(MethodReference methodRef) return newType.GetMethodByOldMethod(methodRef.Resolve()).NewMethod; } - public TypeReference RewriteTypeRef(TypeReference? typeRef) + public TypeReference RewriteTypeRef(TypeReference? typeRef, bool typeIsBoxed) { if (typeRef == null) return Imports.Il2CppObjectBase; @@ -78,7 +85,7 @@ public TypeReference RewriteTypeRef(TypeReference? typeRef) if (elementType.FullName == "System.String") return Imports.Il2CppStringArray; - var convertedElementType = RewriteTypeRef(elementType); + var convertedElementType = RewriteTypeRef(elementType, typeIsBoxed); if (elementType.IsGenericParameter) return new GenericInstanceType(Imports.Il2CppArrayBase) { GenericArguments = { convertedElementType } }; @@ -92,24 +99,36 @@ public TypeReference RewriteTypeRef(TypeReference? typeRef) { var genericParameterDeclaringType = genericParameter.DeclaringType; if (genericParameterDeclaringType != null) - return RewriteTypeRef(genericParameterDeclaringType).GenericParameters[genericParameter.Position]; + return RewriteTypeRef(genericParameterDeclaringType, typeIsBoxed).GenericParameters[genericParameter.Position]; return RewriteMethodRef(genericParameter.DeclaringMethod).GenericParameters[genericParameter.Position]; } if (typeRef is ByReferenceType byRef) - return new ByReferenceType(RewriteTypeRef(byRef.ElementType)); + return new ByReferenceType(RewriteTypeRef(byRef.ElementType, typeIsBoxed)); if (typeRef is PointerType pointerType) - return new PointerType(RewriteTypeRef(pointerType.ElementType)); + return new PointerType(RewriteTypeRef(pointerType.ElementType, typeIsBoxed)); if (typeRef is GenericInstanceType genericInstance) { - var newRef = new GenericInstanceType(RewriteTypeRef(genericInstance.ElementType)); - foreach (var originalParameter in genericInstance.GenericArguments) - newRef.GenericArguments.Add(RewriteTypeRef(originalParameter)); - - return newRef; + var genericTypeContext = GetTypeContext(genericInstance); + if (genericTypeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct && !IsUnmanaged(typeRef, typeIsBoxed)) + { + var newRef = new GenericInstanceType(sourceModule.ImportReference(genericTypeContext.BoxedTypeContext.NewType)); + foreach (var originalParameter in genericInstance.GenericArguments) + newRef.GenericArguments.Add(RewriteTypeRef(originalParameter, typeIsBoxed)); + + return newRef; + } + else + { + var newRef = new GenericInstanceType(RewriteTypeRef(genericInstance.ElementType, typeIsBoxed)); + foreach (var originalParameter in genericInstance.GenericArguments) + newRef.GenericArguments.Add(RewriteTypeRef(originalParameter, typeIsBoxed)); + + return newRef; + } } if (typeRef.IsPrimitive || typeRef.FullName == "System.TypedReference") @@ -129,11 +148,57 @@ public TypeReference RewriteTypeRef(TypeReference? typeRef) return sourceModule.ImportReference(GlobalContext.GetAssemblyByName("mscorlib") .GetTypeByName("System.Attribute").NewType); - var originalTypeDef = typeRef.Resolve(); - var targetAssembly = GlobalContext.GetNewAssemblyForOriginal(originalTypeDef.Module.Assembly); - var target = targetAssembly.GetContextForOriginalType(originalTypeDef).NewType; + var target = GetTypeContext(typeRef); + + if (typeIsBoxed && target.BoxedTypeContext != null) + { + target = target.BoxedTypeContext; + } + + return sourceModule.ImportReference(target.NewType); + } + + private TypeRewriteContext GetTypeContext(TypeReference typeRef) + { + return GlobalContext.GetNewTypeForOriginal(typeRef.Resolve()); + } + + private bool IsUnmanaged(TypeReference originalType, bool typeIsBoxed) + { + if (originalType is GenericParameter genericParameter) + { + GenericParameter newGenericParameter = (GenericParameter)RewriteTypeRef(genericParameter, typeIsBoxed); + return newGenericParameter.CustomAttributes.Any(attribute => attribute.AttributeType.Name.Equals("IsUnmanagedAttribute")); + } - return sourceModule.ImportReference(target); + if (originalType is GenericInstanceType genericInstanceType) + { + foreach (TypeReference genericArgument in genericInstanceType.GenericArguments) + { + if (!IsUnmanaged(genericArgument, typeIsBoxed)) + return false; + } + } + + var paramTypeContext = GetTypeContext(originalType); + return paramTypeContext.ComputedTypeSpecifics.IsBlittable(); + } + + public TypeDefinition GetOrInjectIsUnmanagedAttribute() + { + if (isUnmanagedAttributeType != null) + return isUnmanagedAttributeType; + + isUnmanagedAttributeType = new TypeDefinition("System.Runtime.CompilerServices", "IsUnmanagedAttribute", TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Public | TypeAttributes.Sealed, NewAssembly.MainModule.ImportReference(typeof(Attribute))); + NewAssembly.MainModule.Types.Add(isUnmanagedAttributeType); + + var attributeCctr = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName, NewAssembly.MainModule.TypeSystem.Void); + isUnmanagedAttributeType.Methods.Add(attributeCctr); + var ilProcessor = attributeCctr.Body.GetILProcessor(); + ilProcessor.Emit(OpCodes.Ldarg_0); + ilProcessor.Emit(OpCodes.Call, NewAssembly.MainModule.ImportReference(isUnmanagedAttributeType.BaseType.DefaultCtorFor())); + ilProcessor.Emit(OpCodes.Ret); + return isUnmanagedAttributeType; } public TypeRewriteContext GetTypeByName(string name) diff --git a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs b/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs index 12a5657f..61ea39e0 100644 --- a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs @@ -48,7 +48,7 @@ private string UnmangleFieldNameBase(FieldDefinition field, GeneratorOptions opt var accessModString = MethodAccessTypeLabels[(int)(field.Attributes & FieldAttributes.FieldAccessMask)]; var staticString = field.IsStatic ? "_Static" : ""; return "field_" + accessModString + staticString + "_" + - DeclaringType.AssemblyContext.RewriteTypeRef(field.FieldType).GetUnmangledName(); + DeclaringType.AssemblyContext.RewriteTypeRef(field.FieldType, false).GetUnmangledName(); } private string UnmangleFieldName(FieldDefinition field, GeneratorOptions options, diff --git a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs b/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs index 83f63f42..6c33670c 100644 --- a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Text; using Il2CppInterop.Common.XrefScans; @@ -69,14 +67,23 @@ public MethodRewriteContext(TypeRewriteContext declaringType, MethodDefinition o foreach (var oldParameter in genericParams) { var genericParameter = new GenericParameter(oldParameter.Name, newMethod); - genericParameter.Attributes = oldParameter.Attributes.StripValueTypeConstraint(); + if (ShouldParameterBeBlittable(originalMethod, oldParameter)) + { + genericParameter.Attributes = oldParameter.Attributes; + genericParameter.MakeUnmanaged(DeclaringType.AssemblyContext); + } + else + { + genericParameter.Attributes = oldParameter.Attributes.StripValueTypeConstraint(); + } + newMethod.GenericParameters.Add(genericParameter); } } - if (!Pass15GenerateMemberContexts.HasObfuscatedMethods && !passthroughNames && + if (!Pass16GenerateMemberContexts.HasObfuscatedMethods && !passthroughNames && originalMethod.Name.IsObfuscated(declaringType.AssemblyContext.GlobalContext.Options)) - Pass15GenerateMemberContexts.HasObfuscatedMethods = true; + Pass16GenerateMemberContexts.HasObfuscatedMethods = true; FileOffset = originalMethod.ExtractOffset(); // Workaround for garbage file offsets passed by Cpp2IL @@ -86,6 +93,38 @@ public MethodRewriteContext(TypeRewriteContext declaringType, MethodDefinition o declaringType.AssemblyContext.GlobalContext.MethodStartAddresses.Add(FileOffset); } + private bool ShouldParameterBeBlittable(MethodDefinition method, GenericParameter genericParameter) + { + if (HasGenericParameter(method.ReturnType, genericParameter, out GenericParameter parameter)) + { + return parameter.IsUnmanaged(); + } + + foreach (ParameterDefinition methodParameter in method.Parameters) + { + if (HasGenericParameter(methodParameter.ParameterType, genericParameter, out parameter)) + { + return parameter.IsUnmanaged(); + } + } + + return false; + } + + private bool HasGenericParameter(TypeReference typeReference, GenericParameter inputGenericParameter, out GenericParameter typeGenericParameter) + { + typeGenericParameter = null; + if (typeReference is not GenericInstanceType genericInstance) return false; + + var index = genericInstance.GenericArguments.IndexOf(inputGenericParameter); + if (index < 0) return false; + + var globalContext = DeclaringType.AssemblyContext.GlobalContext; + var returnTypeContext = globalContext.GetNewTypeForOriginal(typeReference.Resolve()); + typeGenericParameter = returnTypeContext.NewType.GenericParameters[index]; + return true; + } + public string UnmangledName { get; private set; } public string UnmangledNameWithSignature { get; private set; } @@ -102,7 +141,7 @@ public void CtorPhase2() UnmangledNameWithSignature = UnmangleMethodNameWithSignature(); NewMethod.Name = UnmangledName; - NewMethod.ReturnType = DeclaringType.AssemblyContext.RewriteTypeRef(OriginalMethod.ReturnType); + NewMethod.ReturnType = DeclaringType.AssemblyContext.RewriteTypeRef(OriginalMethod.ReturnType, DeclaringType.isBoxedTypeVariant); var nonGenericMethodInfoPointerField = new FieldDefinition( "NativeMethodInfoPtr_" + UnmangledNameWithSignature, @@ -142,7 +181,7 @@ public void CtorPhase2() oldConstraint.ConstraintType.Resolve()?.IsInterface == true) continue; newParameter.Constraints.Add(new GenericParameterConstraint( - DeclaringType.AssemblyContext.RewriteTypeRef(oldConstraint.ConstraintType))); + DeclaringType.AssemblyContext.RewriteTypeRef(oldConstraint.ConstraintType, DeclaringType.isBoxedTypeVariant))); } } @@ -222,17 +261,17 @@ private string ProduceMethodSignatureBase() builder.Append(str); builder.Append('_'); - builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(method.ReturnType).GetUnmangledName()); + builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(method.ReturnType, DeclaringType.isBoxedTypeVariant).GetUnmangledName()); foreach (var param in method.Parameters) { builder.Append('_'); - builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(param.ParameterType).GetUnmangledName()); + builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(param.ParameterType, DeclaringType.isBoxedTypeVariant).GetUnmangledName()); } var address = Rva; - if (address != 0 && Pass15GenerateMemberContexts.HasObfuscatedMethods && - !Pass16ScanMethodRefs.NonDeadMethods.Contains(address)) builder.Append("_PDM"); + if (address != 0 && Pass16GenerateMemberContexts.HasObfuscatedMethods && + !Pass17ScanMethodRefs.NonDeadMethods.Contains(address)) builder.Append("_PDM"); return builder.ToString(); } @@ -278,13 +317,13 @@ private bool ParameterSignatureMatchesThis(MethodRewriteContext otherRewriteCont if (a[i].ParameterType.FullName != b[i].ParameterType.FullName) return false; - if (Pass15GenerateMemberContexts.HasObfuscatedMethods) + if (Pass16GenerateMemberContexts.HasObfuscatedMethods) { var addressA = otherRewriteContext.Rva; var addressB = Rva; if (addressA != 0 && addressB != 0) - if (Pass16ScanMethodRefs.NonDeadMethods.Contains(addressA) != - Pass16ScanMethodRefs.NonDeadMethods.Contains(addressB)) + if (Pass17ScanMethodRefs.NonDeadMethods.Contains(addressA) != + Pass17ScanMethodRefs.NonDeadMethods.Contains(addressB)) return false; } diff --git a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs b/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs index 2d7941d3..5b1b9a97 100644 --- a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Il2CppInterop.Generator.Passes; using Il2CppInterop.Generator.Utils; using Mono.Cecil; @@ -13,20 +14,33 @@ public enum TypeSpecifics Computing, ReferenceType, BlittableStruct, + GenericBlittableStruct, NonBlittableStruct } + public enum GenericParameterSpecifics + { + Unused, + Relaxed, + AffectsBlittability, + Strict, + } + public readonly AssemblyRewriteContext AssemblyContext; private readonly Dictionary myFieldContexts = new(); private readonly Dictionary myMethodContexts = new(); private readonly Dictionary myMethodContextsByName = new(); public readonly TypeDefinition NewType; + public TypeRewriteContext BoxedTypeContext; + public bool isBoxedTypeVariant; public readonly bool OriginalNameWasObfuscated; public readonly TypeDefinition OriginalType; public TypeSpecifics ComputedTypeSpecifics; + public GenericParameterSpecifics[] genericParameterUsage; + public bool genericParameterUsageComputed; public TypeRewriteContext(AssemblyRewriteContext assemblyContext, TypeDefinition originalType, TypeDefinition newType) @@ -37,7 +51,9 @@ public TypeRewriteContext(AssemblyRewriteContext assemblyContext, TypeDefinition if (OriginalType == null) return; - OriginalNameWasObfuscated = OriginalType.Name != NewType.Name; + genericParameterUsage = new GenericParameterSpecifics[OriginalType.GenericParameters.Count]; + OriginalNameWasObfuscated = OriginalType.Name != NewType.Name && + Pass13CreateGenericNonBlittableTypes.GetUnboxedName(originalType.Name) != NewType.Name; if (OriginalNameWasObfuscated) NewType.CustomAttributes.Add(new CustomAttribute(assemblyContext.Imports.ObfuscatedNameAttributector.Value) { @@ -49,8 +65,6 @@ public TypeRewriteContext(AssemblyRewriteContext assemblyContext, TypeDefinition ComputedTypeSpecifics = TypeSpecifics.ReferenceType; else if (OriginalType.IsEnum) ComputedTypeSpecifics = TypeSpecifics.BlittableStruct; - else if (OriginalType.HasGenericParameters) - ComputedTypeSpecifics = TypeSpecifics.NonBlittableStruct; // not reference type, covered by first if } public FieldReference ClassPointerFieldRef { get; private set; } @@ -170,4 +184,47 @@ public MethodRewriteContext GetMethodByOldMethod(MethodDefinition method) return null; } + + public void SetGenericParameterUsageSpecifics(int position, GenericParameterSpecifics specifics) + { + if (position >= 0 && position < genericParameterUsage.Length) + { + var genericParameter = OriginalType.GenericParameters[position]; + SetGenericParameterSpecificsDown(genericParameter, specifics); + } + } + + private void SetGenericParameterSpecificsDown(GenericParameter parameter, GenericParameterSpecifics specifics) + { + if (OriginalType.DeclaringType != null) + { + var declaringContext = AssemblyContext.GlobalContext.GetNewTypeForOriginal(OriginalType.DeclaringType); + var declaringTypeParameter = OriginalType.DeclaringType.GenericParameters + .FirstOrDefault(param => param.Name.Equals(parameter.Name)); + + if (declaringTypeParameter != null) + { + declaringContext.SetGenericParameterSpecificsDown(declaringTypeParameter, specifics); + return; + } + } + + SetGenericParameterSpecificsUp(parameter, specifics); + } + + private void SetGenericParameterSpecificsUp(GenericParameter parameter, GenericParameterSpecifics specifics) + { + if (specifics > genericParameterUsage[parameter.Position]) + { + genericParameterUsage[parameter.Position] = specifics; + foreach (TypeDefinition nestedType in OriginalType.NestedTypes) + { + var nestedContext = AssemblyContext.GlobalContext.GetNewTypeForOriginal(nestedType); + var nestedTypeParameter = nestedType.GenericParameters + .FirstOrDefault(param => param.Name.Equals(parameter.Name)); + if (nestedTypeParameter != null) + nestedContext.SetGenericParameterSpecificsUp(nestedTypeParameter, specifics); + } + } + } } diff --git a/Il2CppInterop.Generator/Extensions/EnumEx.cs b/Il2CppInterop.Generator/Extensions/EnumEx.cs index e6bb058e..ee3bad01 100644 --- a/Il2CppInterop.Generator/Extensions/EnumEx.cs +++ b/Il2CppInterop.Generator/Extensions/EnumEx.cs @@ -1,3 +1,4 @@ +using Il2CppInterop.Generator.Contexts; using Mono.Cecil; namespace Il2CppInterop.Generator.Extensions; @@ -14,6 +15,14 @@ public static GenericParameterAttributes StripValueTypeConstraint( this GenericParameterAttributes parameterAttributes) { return parameterAttributes & ~(GenericParameterAttributes.NotNullableValueTypeConstraint | - GenericParameterAttributes.VarianceMask); + GenericParameterAttributes.VarianceMask | + GenericParameterAttributes.DefaultConstructorConstraint); } + + public static bool IsBlittable(this TypeRewriteContext.TypeSpecifics typeSpecifics) + { + return typeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct || + typeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct; + } + } diff --git a/Il2CppInterop.Generator/Extensions/GenericParameterEx.cs b/Il2CppInterop.Generator/Extensions/GenericParameterEx.cs new file mode 100644 index 00000000..2c709844 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/GenericParameterEx.cs @@ -0,0 +1,28 @@ +using Il2CppInterop.Generator.Contexts; +using Mono.Cecil; +using Mono.Cecil.Rocks; + +namespace Il2CppInterop.Generator.Extensions +{ + public static class GenericParameterEx + { + public static void MakeUnmanaged(this GenericParameter genericParameter, AssemblyRewriteContext assemblyContext) + { + var isUnmanagedAttribute = assemblyContext.GetOrInjectIsUnmanagedAttribute(); + genericParameter.HasDefaultConstructorConstraint = true; + genericParameter.Constraints.Add( + new GenericParameterConstraint(assemblyContext.Imports.Module.ImportReference(typeof(ValueType)) + .MakeRequiredModifierType( + assemblyContext.Imports.Module.ImportReference(typeof(System.Runtime.InteropServices.UnmanagedType))))); + + genericParameter.CustomAttributes.Add(new CustomAttribute(isUnmanagedAttribute.Methods[0])); + genericParameter.HasNotNullableValueTypeConstraint = true; + } + + public static bool IsUnmanaged(this GenericParameter genericParameter) + { + return genericParameter.CustomAttributes.Any(attribute => attribute.AttributeType.Name.Equals("IsUnmanagedAttribute")); + } + + } +} diff --git a/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs b/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs index b0fb265a..ca0a3fe0 100644 --- a/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs +++ b/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs @@ -223,6 +223,12 @@ public static void EmitObjectToPointer(this ILProcessor body, TypeReference orig body.Emit(OpCodes.Ldarg, argumentIndex); body.Emit(OpCodes.Call, imports.IL2CPP_ManagedStringToIl2Cpp.Value); } + else if (originalType.IsPointer) + { + body.Emit(OpCodes.Ldarg, argumentIndex); + body.Emit(OpCodes.Call, new MethodReference("op_Explicit", imports.Module.IntPtr(), imports.Module.IntPtr()) + { Parameters = { new ParameterDefinition(imports.Module.ImportReference(typeof(void*))) } }); + } else { body.Emit(OpCodes.Ldarg, argumentIndex); @@ -352,6 +358,13 @@ public static void EmitPointerToObject(this ILProcessor body, TypeReference orig { HasThis = false, Parameters = { new ParameterDefinition(imports.Module.IntPtr()) } }; body.Emit(OpCodes.Call, methodRef); } + else if (originalReturnType.IsPointer) + { + body.Append(loadPointer); + body.Emit(OpCodes.Call, + new MethodReference("op_Explicit", imports.Module.ImportReference(typeof(void*)), imports.Module.IntPtr()) + { Parameters = { new ParameterDefinition(imports.Module.IntPtr()) } }); + } else { var createRealObject = body.Create(OpCodes.Newobj, @@ -383,6 +396,7 @@ private static void EmitPointerToObjectGeneric(ILProcessor body, TypeReference o body.Emit(extraDerefForNonValueTypes ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); body.Emit(unboxValueType ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + body.Emit(OpCodes.Call, imports.Module.ImportReference(new GenericInstanceMethod(imports.IL2CPP_PointerToValueGeneric.Value) { GenericArguments = { newReturnType } })); diff --git a/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs b/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs index 63faad93..eea07655 100644 --- a/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs +++ b/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs @@ -47,4 +47,17 @@ public static string GetNamespacePrefix(this TypeReference type) return type.Namespace; } + + public static MethodReference DefaultCtorFor(this TypeReference type) + { + var resolved = type.Resolve(); + if (resolved == null) + return null; + + var ctor = resolved.Methods.SingleOrDefault(m => m.IsConstructor && m.Parameters.Count == 0 && !m.IsStatic); + if (ctor == null) + return DefaultCtorFor(resolved.BaseType); + + return new MethodReference(".ctor", type.Module.TypeSystem.Void, type) { HasThis = true }; + } } diff --git a/Il2CppInterop.Generator/Passes/Pass11ComputeGenericParameterSpecifics.cs b/Il2CppInterop.Generator/Passes/Pass11ComputeGenericParameterSpecifics.cs new file mode 100644 index 00000000..88c5ece5 --- /dev/null +++ b/Il2CppInterop.Generator/Passes/Pass11ComputeGenericParameterSpecifics.cs @@ -0,0 +1,126 @@ +using Il2CppInterop.Generator.Contexts; +using Il2CppInterop.Generator.Extensions; +using Mono.Cecil; + +namespace Il2CppInterop.Generator.Passes +{ + public static class Pass11ComputeGenericParameterSpecifics + { + private static RewriteGlobalContext globalContext; + + public static void DoPass(RewriteGlobalContext context) + { + globalContext = context; + + foreach (var assemblyContext in context.Assemblies) + foreach (var typeContext in assemblyContext.Types) + ComputeGenericParameterUsageSpecifics(typeContext); + } + + private static void ComputeGenericParameterUsageSpecifics(TypeRewriteContext typeContext) + { + if (typeContext.genericParameterUsageComputed) return; + typeContext.genericParameterUsageComputed = true; + + var originalType = typeContext.OriginalType; + if (originalType.GenericParameters.Count == 0) return; + + void OnResult(GenericParameter parameter, TypeRewriteContext.GenericParameterSpecifics specific) + { + if (parameter.DeclaringType != originalType) return; + + typeContext.SetGenericParameterUsageSpecifics(parameter.Position, specific); + } + + foreach (var originalField in originalType.Fields) + { + // Sometimes il2cpp metadata has invalid field offsets for some reason (https://github.com/SamboyCoding/Cpp2IL/issues/167) + if (originalField.ExtractFieldOffset() >= 0x8000000) continue; + if (originalField.IsStatic) continue; + + FindTypeGenericParameters(originalField.FieldType, + TypeRewriteContext.GenericParameterSpecifics.AffectsBlittability, OnResult); + } + + foreach (var originalField in originalType.Fields) + { + // Sometimes il2cpp metadata has invalid field offsets for some reason (https://github.com/SamboyCoding/Cpp2IL/issues/167) + if (originalField.ExtractFieldOffset() >= 0x8000000) continue; + if (!originalField.IsStatic) continue; + + FindTypeGenericParameters(originalField.FieldType, + TypeRewriteContext.GenericParameterSpecifics.Relaxed, OnResult); + } + + foreach (var originalMethod in originalType.Methods) + { + FindTypeGenericParameters(originalMethod.ReturnType, + TypeRewriteContext.GenericParameterSpecifics.Relaxed, OnResult); + + foreach (ParameterDefinition parameter in originalMethod.Parameters) + { + FindTypeGenericParameters(parameter.ParameterType, + TypeRewriteContext.GenericParameterSpecifics.Relaxed, OnResult); + } + } + + foreach (TypeDefinition nestedType in originalType.NestedTypes) + { + var nestedContext = globalContext.GetNewTypeForOriginal(nestedType); + ComputeGenericParameterUsageSpecifics(nestedContext); + + foreach (var parameter in nestedType.GenericParameters) + { + var myParameter = originalType.GenericParameters + .FirstOrDefault(param => param.Name.Equals(parameter.Name)); + + if (myParameter == null) continue; + + var otherParameterSpecific = nestedContext.genericParameterUsage[parameter.Position]; + if (otherParameterSpecific == TypeRewriteContext.GenericParameterSpecifics.Strict) + typeContext.SetGenericParameterUsageSpecifics(myParameter.Position, otherParameterSpecific); + } + } + } + + private static void FindTypeGenericParameters(TypeReference reference, + TypeRewriteContext.GenericParameterSpecifics currentConstraint, + Action onFound) + { + if (reference is GenericParameter genericParameter) + { + onFound?.Invoke(genericParameter, currentConstraint); + return; + } + + if (reference.IsPointer) + { + FindTypeGenericParameters(reference.GetElementType(), + TypeRewriteContext.GenericParameterSpecifics.Strict, onFound); + return; + } + + if (reference.IsArray || reference.IsByReference) + { + FindTypeGenericParameters(reference.GetElementType(), + currentConstraint, onFound); + return; + } + + if (reference is GenericInstanceType genericInstance) + { + var typeContext = globalContext.GetNewTypeForOriginal(reference.Resolve()); + ComputeGenericParameterUsageSpecifics(typeContext); + for (var i = 0; i < genericInstance.GenericArguments.Count; i++) + { + var myConstraint = typeContext.genericParameterUsage[i]; + if (myConstraint == TypeRewriteContext.GenericParameterSpecifics.AffectsBlittability) + myConstraint = currentConstraint; + + var genericArgument = genericInstance.GenericArguments[i]; + FindTypeGenericParameters(genericArgument, myConstraint, onFound); + } + } + } + } +} diff --git a/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs b/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs deleted file mode 100644 index 43730192..00000000 --- a/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass11ComputeTypeSpecifics -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - ComputeSpecifics(typeContext); - } - - private static void ComputeSpecifics(TypeRewriteContext typeContext) - { - if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.NotComputed) return; - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.Computing; - - foreach (var originalField in typeContext.OriginalType.Fields) - { - // Sometimes il2cpp metadata has invalid field offsets for some reason (https://github.com/SamboyCoding/Cpp2IL/issues/167) - if (originalField.ExtractFieldOffset() >= 0x8000000) - { - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; - return; - } - - if (originalField.IsStatic) continue; - - var fieldType = originalField.FieldType; - if (fieldType.IsPrimitive || fieldType.IsPointer) continue; - if (fieldType.FullName == "System.String" || fieldType.FullName == "System.Object" || fieldType.IsArray || - fieldType.IsByReference || fieldType.IsGenericParameter || fieldType.IsGenericInstance) - { - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; - return; - } - - var fieldTypeContext = typeContext.AssemblyContext.GlobalContext.GetNewTypeForOriginal(fieldType.Resolve()); - ComputeSpecifics(fieldTypeContext); - if (fieldTypeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct) - { - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; - return; - } - } - - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.BlittableStruct; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass12ComputeTypeSpecifics.cs b/Il2CppInterop.Generator/Passes/Pass12ComputeTypeSpecifics.cs new file mode 100644 index 00000000..9aafc828 --- /dev/null +++ b/Il2CppInterop.Generator/Passes/Pass12ComputeTypeSpecifics.cs @@ -0,0 +1,269 @@ +using System.Diagnostics; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Contexts; +using Il2CppInterop.Generator.Extensions; +using Microsoft.Extensions.Logging; +using Mono.Cecil; + +namespace Il2CppInterop.Generator.Passes; + +public static class Pass12ComputeTypeSpecifics +{ + public static void DoPass(RewriteGlobalContext context) + { + typeUsageDictionary.Clear(); + + foreach (var assemblyContext in context.Assemblies) + foreach (var typeContext in assemblyContext.Types) + ScanTypeUsage(typeContext); + + foreach (var assemblyContext in context.Assemblies) + foreach (var typeContext in assemblyContext.Types) + ComputeSpecifics(typeContext); + + foreach (var assemblyContext in context.Assemblies) + foreach (var typeContext in assemblyContext.Types) + ComputeSpecificsPass2(typeContext); + } + + internal static Dictionary typeUsageDictionary = new Dictionary(new TypeComparer()); + + private static void ScanTypeUsage(TypeRewriteContext typeContext) + { + foreach (FieldDefinition fieldDefinition in typeContext.OriginalType.Fields) + { + ScanTypeUsage(fieldDefinition.FieldType); + } + + foreach (PropertyDefinition propertyDefinition in typeContext.OriginalType.Properties) + { + ScanTypeUsage(propertyDefinition.PropertyType); + } + + foreach (MethodDefinition methodDefinition in typeContext.OriginalType.Methods) + { + ScanTypeUsage(methodDefinition.ReturnType); + foreach (ParameterDefinition parameterDefinition in methodDefinition.Parameters) + { + ScanTypeUsage(parameterDefinition.ParameterType); + } + } + } + + private static void ScanTypeUsage(TypeReference fieldType) + { + while (fieldType.IsPointer) + { + fieldType = (fieldType as PointerType).ElementType; + } + + while (fieldType.IsArray) + { + fieldType = (fieldType as ArrayType).ElementType; + } + + if (fieldType is GenericInstanceType genericInstanceType) + { + foreach (TypeReference typeReference in genericInstanceType.GenericArguments) + { + ScanTypeUsage(typeReference); + } + + TypeDefinition typeDef = fieldType.Resolve(); + if (typeDef?.BaseType == null || !typeDef.BaseType.Name.Equals("ValueType")) return; + + + if (!typeUsageDictionary.TryGetValue(typeDef, out ParameterUsage usage)) + { + usage = new ParameterUsage(genericInstanceType.GenericArguments.Count); + typeUsageDictionary.Add(typeDef, usage); + } + + for (var i = 0; i < genericInstanceType.GenericArguments.Count; i++) + { + usage.AddUsage(i, genericInstanceType.GenericArguments[i]); + } + } + } + + private static bool IsValueTypeOnly(TypeRewriteContext typeContext, GenericParameter genericParameter) + { + if (genericParameter.Constraints.All(constraint => constraint.ConstraintType.FullName != "System.ValueType")) + return false; + + if (typeUsageDictionary.ContainsKey(typeContext.OriginalType)) + { + var usage = typeUsageDictionary[typeContext.OriginalType]; + return usage.IsBlittableParameter(typeContext.AssemblyContext.GlobalContext, genericParameter.Position); + } + + return true; + } + + private static void ComputeSpecifics(TypeRewriteContext typeContext) + { + if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.NotComputed) return; + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.Computing; + + foreach (var originalField in typeContext.OriginalType.Fields) + { + // Sometimes il2cpp metadata has invalid field offsets for some reason (https://github.com/SamboyCoding/Cpp2IL/issues/167) + if (originalField.ExtractFieldOffset() >= 0x8000000) + { + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; + return; + } + + if (originalField.IsStatic) continue; + + var fieldType = originalField.FieldType; + + if (fieldType.IsPrimitive || fieldType.IsPointer || fieldType.IsGenericParameter) continue; + + if (fieldType.FullName == "System.String" || fieldType.FullName == "System.Object" || fieldType.IsArray || + fieldType.IsByReference) + { + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; + return; + } + + var fieldTypeContext = typeContext.AssemblyContext.GlobalContext.GetNewTypeForOriginal(fieldType.Resolve()); + ComputeSpecifics(fieldTypeContext); + if (fieldTypeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) + { + var genericInstance = fieldType as GenericInstanceType; + for (var i = 0; i < genericInstance.GenericArguments.Count; i++) + { + var genericArgument = genericInstance.GenericArguments[i]; + if (genericArgument.IsGenericParameter) continue; + if (fieldTypeContext.genericParameterUsage[i] < TypeRewriteContext.GenericParameterSpecifics.AffectsBlittability) continue; + + var genericArgumentContext = typeContext.AssemblyContext.GlobalContext.GetNewTypeForOriginal(genericArgument.Resolve()); + ComputeSpecifics(genericArgumentContext); + if (genericArgumentContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.NonBlittableStruct || + genericArgumentContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.ReferenceType) + { + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; + return; + } + } + } + else if (fieldTypeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct) + { + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; + return; + } + } + + if (typeContext.OriginalType.GenericParameters.Count > 0) + { + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.GenericBlittableStruct; + return; + } + + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.BlittableStruct; + } + + private static void ComputeSpecificsPass2(TypeRewriteContext typeContext) + { + if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) return; + + foreach (var genericParameter in typeContext.OriginalType.GenericParameters) + { + if (typeContext.genericParameterUsage[genericParameter.Position] == TypeRewriteContext.GenericParameterSpecifics.AffectsBlittability || + typeContext.genericParameterUsage[genericParameter.Position] == TypeRewriteContext.GenericParameterSpecifics.Strict) + + { + if (IsValueTypeOnly(typeContext, genericParameter)) + { + typeContext.SetGenericParameterUsageSpecifics(genericParameter.Position, TypeRewriteContext.GenericParameterSpecifics.Strict); + continue; + } + + return; + } + } + + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.BlittableStruct; + } + + internal class ParameterUsage + { + public List[] usageData; + + public ParameterUsage(int paramCount) + { + usageData = new List[paramCount]; + for (var i = 0; i < paramCount; i++) + { + usageData[i] = new List(); + } + } + + public void AddUsage(int index, TypeReference type) + { + if (type is GenericParameter genericParameter) + { + var declaringName = GetDeclaringName(genericParameter); + + if (usageData[index].All(reference => reference is not GenericParameter parameter || !GetDeclaringName(parameter).Equals(declaringName))) + { + usageData[index].Add(type); + } + } + else if (usageData[index].All(reference => !reference.FullName.Equals(type.FullName))) + { + usageData[index].Add(type); + } + } + + private static string GetDeclaringName(GenericParameter genericParameter) + { + var declaringName = genericParameter.DeclaringMethod != null ? genericParameter.DeclaringMethod.FullName : genericParameter.DeclaringType.FullName; + declaringName += genericParameter.FullName; + return declaringName; + } + + public bool IsBlittableParameter(RewriteGlobalContext globalContext, int index) + { + var usages = usageData[index]; + + foreach (TypeReference reference in usages) + { + if (reference is GenericParameter genericParameter) + { + if (genericParameter.Constraints.All(constraint => constraint.ConstraintType.FullName != "System.ValueType")) + return false; + } + else + { + var typeDef = reference.Resolve(); + var fieldTypeContext = globalContext.GetNewTypeForOriginal(typeDef); + ComputeSpecifics(fieldTypeContext); + if (fieldTypeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.NonBlittableStruct) + return false; + } + } + + return true; + } + } + + internal sealed class TypeComparer : EqualityComparer + { + public override bool Equals(TypeDefinition x, TypeDefinition y) + { + if (x == null) + return y == null; + if (y == null) + return false; + + return x.FullName.Equals(y.FullName); + } + + public override int GetHashCode(TypeDefinition obj) + { + return obj.FullName.GetHashCode(); + } + } +} diff --git a/Il2CppInterop.Generator/Passes/Pass13CreateGenericNonBlittableTypes.cs b/Il2CppInterop.Generator/Passes/Pass13CreateGenericNonBlittableTypes.cs new file mode 100644 index 00000000..d435dd36 --- /dev/null +++ b/Il2CppInterop.Generator/Passes/Pass13CreateGenericNonBlittableTypes.cs @@ -0,0 +1,64 @@ +using Il2CppInterop.Generator.Contexts; +using Mono.Cecil; + +namespace Il2CppInterop.Generator.Passes; + +public static class Pass13CreateGenericNonBlittableTypes +{ + public static void DoPass(RewriteGlobalContext context) + { + foreach (var assemblyContext in context.Assemblies) + foreach (var typeContext in assemblyContext.OriginalTypes) + { + if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) + CreateBoxedType(typeContext); + } + } + + private static void CreateBoxedType(TypeRewriteContext typeContext, TypeDefinition parentType = null) + { + AssemblyRewriteContext assemblyContext = typeContext.AssemblyContext; + var typeName = typeContext.NewType.Name; + // Append _unboxed to blittable type for compatibility + typeContext.NewType.Name = GetUnboxedName(typeName); + + + TypeDefinition newBoxedType = new TypeDefinition( + typeContext.NewType.Namespace, + typeName, + typeContext.NewType.Attributes); + + var declaringType = parentType ?? typeContext.NewType.DeclaringType; + if (declaringType == null) + { + assemblyContext.NewAssembly.MainModule.Types.Add(newBoxedType); + } + else + { + declaringType.NestedTypes.Add(newBoxedType); + newBoxedType.DeclaringType = declaringType; + } + + TypeRewriteContext boxedTypeContext = new TypeRewriteContext(assemblyContext, typeContext.OriginalType, newBoxedType); + boxedTypeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; + boxedTypeContext.isBoxedTypeVariant = true; + typeContext.BoxedTypeContext = boxedTypeContext; + assemblyContext.RegisterTypeContext(boxedTypeContext); + + foreach (TypeDefinition nestedType in typeContext.OriginalType.NestedTypes) + { + var nestedContext = assemblyContext.GetContextForOriginalType(nestedType); + CreateBoxedType(nestedContext, newBoxedType); + } + } + + internal static string GetUnboxedName(string originalName) + { + var parts = originalName.Split('`'); + + if (parts.Length == 2) + return $"{parts[0]}_Unboxed`{parts[1]}"; + + return $"{parts[0]}_Unboxed"; + } +} diff --git a/Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs b/Il2CppInterop.Generator/Passes/Pass14FillTypedefs.cs similarity index 54% rename from Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs rename to Il2CppInterop.Generator/Passes/Pass14FillTypedefs.cs index 57cd9f7a..196e9696 100644 --- a/Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs +++ b/Il2CppInterop.Generator/Passes/Pass14FillTypedefs.cs @@ -5,31 +5,42 @@ namespace Il2CppInterop.Generator.Passes; -public static class Pass12FillTypedefs +public static class Pass14FillTypedefs { public static void DoPass(RewriteGlobalContext context) { foreach (var assemblyContext in context.Assemblies) + { foreach (var typeContext in assemblyContext.Types) { foreach (var originalParameter in typeContext.OriginalType.GenericParameters) { var newParameter = new GenericParameter(originalParameter.Name, typeContext.NewType); typeContext.NewType.GenericParameters.Add(newParameter); - newParameter.Attributes = originalParameter.Attributes.StripValueTypeConstraint(); + + var parameterSpecifics = typeContext.genericParameterUsage[originalParameter.Position]; + if (parameterSpecifics == TypeRewriteContext.GenericParameterSpecifics.Strict || + (parameterSpecifics == TypeRewriteContext.GenericParameterSpecifics.AffectsBlittability && + typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct)) + { + newParameter.Attributes = originalParameter.Attributes; + newParameter.MakeUnmanaged(assemblyContext); + } + else + newParameter.Attributes = originalParameter.Attributes.StripValueTypeConstraint(); } if (typeContext.OriginalType.IsEnum) typeContext.NewType.BaseType = assemblyContext.Imports.Module.Enum(); - else if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct) + else if (typeContext.ComputedTypeSpecifics.IsBlittable()) typeContext.NewType.BaseType = assemblyContext.Imports.Module.ValueType(); } + } // Second pass is explicitly done after first to account for rewriting of generic base types - value-typeness is important there foreach (var assemblyContext in context.Assemblies) foreach (var typeContext in assemblyContext.Types) - if (!typeContext.OriginalType.IsEnum && typeContext.ComputedTypeSpecifics != - TypeRewriteContext.TypeSpecifics.BlittableStruct) - typeContext.NewType.BaseType = assemblyContext.RewriteTypeRef(typeContext.OriginalType.BaseType); + if (!typeContext.OriginalType.IsEnum && !typeContext.ComputedTypeSpecifics.IsBlittable()) + typeContext.NewType.BaseType = assemblyContext.RewriteTypeRef(typeContext.OriginalType.BaseType, false); } } diff --git a/Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs b/Il2CppInterop.Generator/Passes/Pass15FillGenericConstraints.cs similarity index 92% rename from Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs rename to Il2CppInterop.Generator/Passes/Pass15FillGenericConstraints.cs index 0515ed18..28bcf468 100644 --- a/Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs +++ b/Il2CppInterop.Generator/Passes/Pass15FillGenericConstraints.cs @@ -3,7 +3,7 @@ namespace Il2CppInterop.Generator.Passes; -public static class Pass13FillGenericConstraints +public static class Pass15FillGenericConstraints { public static void DoPass(RewriteGlobalContext context) { @@ -20,7 +20,7 @@ public static void DoPass(RewriteGlobalContext context) newParameter.Constraints.Add( new GenericParameterConstraint( - assemblyContext.RewriteTypeRef(originalConstraint.ConstraintType))); + assemblyContext.RewriteTypeRef(originalConstraint.ConstraintType, false))); } } } diff --git a/Il2CppInterop.Generator/Passes/Pass15GenerateMemberContexts.cs b/Il2CppInterop.Generator/Passes/Pass16GenerateMemberContexts.cs similarity index 88% rename from Il2CppInterop.Generator/Passes/Pass15GenerateMemberContexts.cs rename to Il2CppInterop.Generator/Passes/Pass16GenerateMemberContexts.cs index 49fcb6ff..61b31f8e 100644 --- a/Il2CppInterop.Generator/Passes/Pass15GenerateMemberContexts.cs +++ b/Il2CppInterop.Generator/Passes/Pass16GenerateMemberContexts.cs @@ -2,7 +2,7 @@ namespace Il2CppInterop.Generator.Passes; -public static class Pass15GenerateMemberContexts +public static class Pass16GenerateMemberContexts { public static bool HasObfuscatedMethods; diff --git a/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs b/Il2CppInterop.Generator/Passes/Pass17ScanMethodRefs.cs similarity index 96% rename from Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs rename to Il2CppInterop.Generator/Passes/Pass17ScanMethodRefs.cs index 96854b7b..947c8583 100644 --- a/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs +++ b/Il2CppInterop.Generator/Passes/Pass17ScanMethodRefs.cs @@ -13,7 +13,7 @@ namespace Il2CppInterop.Generator.Passes; -public static class Pass16ScanMethodRefs +public static class Pass17ScanMethodRefs { public static readonly HashSet NonDeadMethods = new(); public static IDictionary> MapOfCallers = new Dictionary>(); @@ -22,7 +22,7 @@ public static void DoPass(RewriteGlobalContext context, GeneratorOptions options { if (string.IsNullOrEmpty(options.GameAssemblyPath)) { - Pass15GenerateMemberContexts.HasObfuscatedMethods = false; + Pass16GenerateMemberContexts.HasObfuscatedMethods = false; return; } @@ -42,7 +42,7 @@ public static void DoPass(RewriteGlobalContext context, GeneratorOptions options context.HasGcWbarrierFieldWrite = FindByteSequence(gameAssemblyPtr, accessor.Capacity, "il2cpp_gc_wbarrier_set_field"); - if (!Pass15GenerateMemberContexts.HasObfuscatedMethods) return; + if (!Pass16GenerateMemberContexts.HasObfuscatedMethods) return; var methodToCallersMap = new ConcurrentDictionary>(); var methodToCalleesMap = new ConcurrentDictionary>(); diff --git a/Il2CppInterop.Generator/Passes/Pass18FinalizeMethodContexts.cs b/Il2CppInterop.Generator/Passes/Pass18FinalizeMethodContexts.cs index 1c7fe60c..333340c2 100644 --- a/Il2CppInterop.Generator/Passes/Pass18FinalizeMethodContexts.cs +++ b/Il2CppInterop.Generator/Passes/Pass18FinalizeMethodContexts.cs @@ -23,10 +23,10 @@ public static void DoPass(RewriteGlobalContext context) { methodContext.CtorPhase2(); - if (Pass15GenerateMemberContexts.HasObfuscatedMethods) + if (Pass16GenerateMemberContexts.HasObfuscatedMethods) { var callerCount = 0; - if (Pass16ScanMethodRefs.MapOfCallers.TryGetValue(methodContext.Rva, out var callers)) + if (Pass17ScanMethodRefs.MapOfCallers.TryGetValue(methodContext.Rva, out var callers)) callerCount = callers.Count; methodContext.NewMethod.CustomAttributes.Add( @@ -36,7 +36,7 @@ public static void DoPass(RewriteGlobalContext context) {new CustomAttributeArgument(assemblyContext.Imports.Module.Int(), callerCount)} }); - if (!Pass15GenerateMemberContexts.HasObfuscatedMethods) continue; + if (!Pass16GenerateMemberContexts.HasObfuscatedMethods) continue; if (!methodContext.UnmangledName.Contains("_PDM_")) continue; TotalPotentiallyDeadMethods++; @@ -58,7 +58,7 @@ public static void DoPass(RewriteGlobalContext context) } } - if (Pass15GenerateMemberContexts.HasObfuscatedMethods) + if (Pass16GenerateMemberContexts.HasObfuscatedMethods) { Logger.Instance.LogTrace("Dead method statistics: 0t={Top0Caller} mt={TopNZCaller} 0n={Nested0Caller} mn={NestedNZCaller}", pdmTop0Caller, pdmTopNZCaller, pdmNested0Caller, pdmNestedNZCaller); } diff --git a/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs b/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs index 200cded8..04280a43 100644 --- a/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs +++ b/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs @@ -23,7 +23,7 @@ public static void DoPass(RewriteGlobalContext context) var newParameter = new ParameterDefinition(newName, originalMethodParameter.Attributes & ~ParameterAttributes.HasFieldMarshal, - assemblyContext.RewriteTypeRef(originalMethodParameter.ParameterType)); + assemblyContext.RewriteTypeRef(originalMethodParameter.ParameterType, typeContext.isBoxedTypeVariant)); if (originalMethodParameter.IsParamsArray()) { @@ -37,7 +37,7 @@ public static void DoPass(RewriteGlobalContext context) } var paramsMethod = context.CreateParamsMethod(originalMethod, newMethod, assemblyContext.Imports, - type => assemblyContext.RewriteTypeRef(type)); + type => assemblyContext.RewriteTypeRef(type, typeContext.isBoxedTypeVariant)); if (paramsMethod != null) typeContext.NewType.Methods.Add(paramsMethod); } } diff --git a/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs b/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs index 1a81795b..b341da72 100644 --- a/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs +++ b/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs @@ -35,7 +35,7 @@ private static void GenerateStaticProxy(AssemblyRewriteContext assemblyContext, var ctorBuilder = staticCtorMethod.Body.GetILProcessor(); - if (newType.IsNested) + if (newType.IsNested && oldType.IsNested) { ctorBuilder.Emit(OpCodes.Ldsfld, assemblyContext.GlobalContext.GetNewTypeForOriginal(oldType.DeclaringType).ClassPointerFieldRef); diff --git a/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs b/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs index c7e0d6c5..e8a950ef 100644 --- a/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs +++ b/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Il2CppInterop.Generator.Contexts; using Il2CppInterop.Generator.Extensions; using Il2CppInterop.Generator.Utils; @@ -20,33 +18,63 @@ public static void DoPass(RewriteGlobalContext context) foreach (var typeContext in assemblyContext.Types) { - if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct || + if (!typeContext.ComputedTypeSpecifics.IsBlittable() || typeContext.OriginalType.IsEnum) continue; try { var newType = typeContext.NewType; - newType.Attributes = (newType.Attributes & ~TypeAttributes.LayoutMask) | - TypeAttributes.ExplicitLayout; - ILGeneratorEx.GenerateBoxMethod(assemblyContext.Imports, newType, typeContext.ClassPointerFieldRef, - il2CppSystemTypeRef); + if (!typeContext.OriginalType.HasGenericParameters) + newType.Attributes = (newType.Attributes & ~TypeAttributes.LayoutMask) | + TypeAttributes.ExplicitLayout; + else + newType.IsSequentialLayout = true; + + if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) + { + var boxedType = typeContext.BoxedTypeContext.NewType; + var genericBoxedType = new GenericInstanceType(assemblyContext.Imports.Module.ImportReference(boxedType)); + foreach (GenericParameter parameter in newType.GenericParameters) + genericBoxedType.GenericArguments.Add(parameter); + ILGeneratorEx.GenerateBoxMethod(assemblyContext.Imports, newType, typeContext.ClassPointerFieldRef, + genericBoxedType); + } + else + { + ILGeneratorEx.GenerateBoxMethod(assemblyContext.Imports, newType, typeContext.ClassPointerFieldRef, + il2CppSystemTypeRef); + } foreach (var fieldContext in typeContext.Fields) { var field = fieldContext.OriginalField; if (field.IsStatic) continue; - var newField = new FieldDefinition(fieldContext.UnmangledName, field.Attributes.ForcePublic(), - !field.FieldType.IsValueType - ? assemblyContext.Imports.Module.IntPtr() - : assemblyContext.RewriteTypeRef(field.FieldType)); + TypeReference rewriteTypeRef; + if (!field.FieldType.IsValueType && !field.FieldType.IsPointer && !field.FieldType.IsGenericParameter) + rewriteTypeRef = assemblyContext.Imports.Module.IntPtr(); + else + rewriteTypeRef = assemblyContext.RewriteTypeRef(field.FieldType, false); + + var newField = new FieldDefinition(fieldContext.UnmangledName, field.Attributes.ForcePublic(), rewriteTypeRef); + + if (!typeContext.OriginalType.HasGenericParameters) + newField.Offset = field.ExtractFieldOffset(); - newField.Offset = field.ExtractFieldOffset(); // Special case: bools in Il2Cpp are bytes if (newField.FieldType.FullName == "System.Boolean") - newField.MarshalInfo = new MarshalInfo(NativeType.U1); + { + if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) + { + newField.FieldType = assemblyContext.Imports.NativeBoolean; + } + else + { + newField.MarshalInfo = new MarshalInfo(NativeType.U1); + } + } newType.Fields.Add(newField); } diff --git a/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs b/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs index fedf3c59..33a6d5cd 100644 --- a/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs +++ b/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs @@ -45,7 +45,7 @@ public static void DoPass(RewriteGlobalContext context) fieldName = newName; var newDef = new FieldDefinition(fieldName, fieldDefinition.Attributes | FieldAttributes.HasDefault, - assemblyContext.RewriteTypeRef(fieldDefinition.FieldType)); + assemblyContext.RewriteTypeRef(fieldDefinition.FieldType, false)); newType.Fields.Add(newDef); newDef.Constant = fieldDefinition.Constant; diff --git a/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs b/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs index 568b1b64..a2e13613 100644 --- a/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs +++ b/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs @@ -1,4 +1,5 @@ using Il2CppInterop.Generator.Contexts; +using Il2CppInterop.Generator.Extensions; using Il2CppInterop.Generator.Utils; using Mono.Cecil; using Mono.Cecil.Cil; @@ -12,7 +13,7 @@ public static void DoPass(RewriteGlobalContext context) foreach (var assemblyContext in context.Assemblies) foreach (var typeContext in assemblyContext.Types) { - if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct || + if (typeContext.ComputedTypeSpecifics.IsBlittable() || typeContext.OriginalType.IsEnum) continue; var newType = typeContext.NewType; diff --git a/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs b/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs index f736cb99..5a5160f7 100644 --- a/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs +++ b/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs @@ -1,4 +1,5 @@ using Il2CppInterop.Generator.Contexts; +using Il2CppInterop.Generator.Extensions; using Il2CppInterop.Generator.Utils; using Mono.Cecil; @@ -12,14 +13,14 @@ public static void DoPass(RewriteGlobalContext context) foreach (var typeContext in assemblyContext.Types) foreach (var fieldContext in typeContext.Fields) { - if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct && + if (typeContext.ComputedTypeSpecifics.IsBlittable() && !fieldContext.OriginalField.IsStatic) continue; var field = fieldContext.OriginalField; var unmangleFieldName = fieldContext.UnmangledName; var property = new PropertyDefinition(unmangleFieldName, PropertyAttributes.None, - assemblyContext.RewriteTypeRef(fieldContext.OriginalField.FieldType)); + assemblyContext.RewriteTypeRef(fieldContext.OriginalField.FieldType, typeContext.isBoxedTypeVariant)); typeContext.NewType.Properties.Add(property); FieldAccessorGenerator.MakeGetter(field, fieldContext, property, assemblyContext.Imports); diff --git a/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs b/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs index 8844d2af..f703f97f 100644 --- a/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs +++ b/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs @@ -66,7 +66,8 @@ public static void DoPass(RewriteGlobalContext context) if (nextInstruction != null) bodyBuilder.Append(nextInstruction); - if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct) + if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct && + typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) { if (originalMethod.IsConstructor) { diff --git a/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs b/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs index 0a174ca3..b32b9ad3 100644 --- a/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs +++ b/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs @@ -26,10 +26,10 @@ public static void DoPass(RewriteGlobalContext context) propertyCountsByName); var property = new PropertyDefinition(unmangledPropertyName, oldProperty.Attributes, - assemblyContext.RewriteTypeRef(oldProperty.PropertyType)); + assemblyContext.RewriteTypeRef(oldProperty.PropertyType, typeContext.isBoxedTypeVariant)); foreach (var oldParameter in oldProperty.Parameters) property.Parameters.Add(new ParameterDefinition(oldParameter.Name, oldParameter.Attributes, - assemblyContext.RewriteTypeRef(oldParameter.ParameterType))); + assemblyContext.RewriteTypeRef(oldParameter.ParameterType, typeContext.isBoxedTypeVariant))); typeContext.NewType.Properties.Add(property); @@ -77,7 +77,7 @@ private static string UnmanglePropertyName(AssemblyRewriteContext assemblyContex if (assemblyContext.GlobalContext.Options.PassthroughNames || !prop.Name.IsObfuscated(assemblyContext.GlobalContext.Options)) return prop.Name; - var baseName = "prop_" + assemblyContext.RewriteTypeRef(prop.PropertyType).GetUnmangledName(); + var baseName = "prop_" + assemblyContext.RewriteTypeRef(prop.PropertyType, false).GetUnmangledName(); countsByBaseName.TryGetValue(baseName, out var index); countsByBaseName[baseName] = index + 1; diff --git a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs index 50d68cb8..47756367 100644 --- a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs +++ b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs @@ -37,7 +37,8 @@ public static void DoPass(RewriteGlobalContext context) private static void ProcessType(AssemblyRewriteContext processedAssembly, TypeDefinition unityType, TypeDefinition? enclosingNewType, RuntimeAssemblyReferences imports, ref int typesUnstripped) { - if (unityType.Name == "") + if (unityType.Name == "" || + unityType.Name == "IsUnmanagedAttribute") return; // Don't unstrip delegates, the il2cpp runtime methods are stripped and we cannot recover them diff --git a/Il2CppInterop.Generator/Passes/Pass89GenerateMethodXrefCache.cs b/Il2CppInterop.Generator/Passes/Pass89GenerateMethodXrefCache.cs index d54ec593..e51ccec1 100644 --- a/Il2CppInterop.Generator/Passes/Pass89GenerateMethodXrefCache.cs +++ b/Il2CppInterop.Generator/Passes/Pass89GenerateMethodXrefCache.cs @@ -73,7 +73,7 @@ public static void DoPass(RewriteGlobalContext context, GeneratorOptions options var refEnd = 0; if (address != 0) - if (Pass16ScanMethodRefs.MapOfCallers.TryGetValue(address, out var callerMap)) + if (Pass17ScanMethodRefs.MapOfCallers.TryGetValue(address, out var callerMap)) { refStart = data.Count; foreach (var xrefInstance in callerMap) diff --git a/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs b/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs index b60d8cb9..7f480380 100644 --- a/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs +++ b/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs @@ -71,24 +71,34 @@ public void Run(GeneratorOptions options) Pass10CreateTypedefs.DoPass(rewriteContext); } + using (new TimingCookie("Computing generic parameter usage")) + { + Pass11ComputeGenericParameterSpecifics.DoPass(rewriteContext); + } + using (new TimingCookie("Computing struct blittability")) { - Pass11ComputeTypeSpecifics.DoPass(rewriteContext); + Pass12ComputeTypeSpecifics.DoPass(rewriteContext); + } + + using (new TimingCookie("Creating Boxed struct types")) + { + Pass13CreateGenericNonBlittableTypes.DoPass(rewriteContext); } using (new TimingCookie("Filling typedefs")) { - Pass12FillTypedefs.DoPass(rewriteContext); + Pass14FillTypedefs.DoPass(rewriteContext); } using (new TimingCookie("Filling generic constraints")) { - Pass13FillGenericConstraints.DoPass(rewriteContext); + Pass15FillGenericConstraints.DoPass(rewriteContext); } using (new TimingCookie("Creating members")) { - Pass15GenerateMemberContexts.DoPass(rewriteContext); + Pass16GenerateMemberContexts.DoPass(rewriteContext); } diff --git a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs index f380fb2b..22ff4e30 100644 --- a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs +++ b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs @@ -69,29 +69,39 @@ public void Run(GeneratorOptions options) Pass10CreateTypedefs.DoPass(rewriteContext); } + using (new TimingCookie("Computing generic parameter usage")) + { + Pass11ComputeGenericParameterSpecifics.DoPass(rewriteContext); + } + using (new TimingCookie("Computing struct blittability")) { - Pass11ComputeTypeSpecifics.DoPass(rewriteContext); + Pass12ComputeTypeSpecifics.DoPass(rewriteContext); + } + + using (new TimingCookie("Creating unboxed struct types")) + { + Pass13CreateGenericNonBlittableTypes.DoPass(rewriteContext); } using (new TimingCookie("Filling typedefs")) { - Pass12FillTypedefs.DoPass(rewriteContext); + Pass14FillTypedefs.DoPass(rewriteContext); } using (new TimingCookie("Filling generic constraints")) { - Pass13FillGenericConstraints.DoPass(rewriteContext); + Pass15FillGenericConstraints.DoPass(rewriteContext); } using (new TimingCookie("Creating members")) { - Pass15GenerateMemberContexts.DoPass(rewriteContext); + Pass16GenerateMemberContexts.DoPass(rewriteContext); } using (new TimingCookie("Scanning method cross-references")) { - Pass16ScanMethodRefs.DoPass(rewriteContext, options); + Pass17ScanMethodRefs.DoPass(rewriteContext, options); } using (new TimingCookie("Finalizing method declarations")) @@ -194,6 +204,7 @@ public void Run(GeneratorOptions options) Pass89GenerateMethodXrefCache.DoPass(rewriteContext, options); } + using (new TimingCookie("Writing assemblies")) { Pass90WriteToDisk.DoPass(rewriteContext, options); diff --git a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs index 5d3bf379..c32d3033 100644 --- a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs +++ b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs @@ -6,6 +6,7 @@ using Il2CppInterop.Common.Attributes; using Il2CppInterop.Generator.Contexts; using Mono.Cecil; +using Mono.Cecil.Rocks; namespace Il2CppInterop.Generator.Utils; @@ -86,6 +87,8 @@ public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext g public TypeReference DelegateSupport { get; private set; } public TypeReference Il2CppException { get; private set; } + public TypeReference NativeBoolean { get; private set; } + private TypeReference ResolveType(string typeName) { return allTypes[typeName]; @@ -141,12 +144,16 @@ private void InitTypeRefs() Il2CppException = new TypeReference("Il2CppInterop.Runtime", "Il2CppException", Module, assemblyRef); + NativeBoolean = new TypeReference("Il2CppInterop.Runtime", "NativeBoolean", Module, assemblyRef); + NativeBoolean.IsValueType = true; + allTypes["Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase"] = Il2CppObjectBase; allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray"] = Il2CppStringArray; allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray"] = Il2CppReferenceArray; allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray"] = Il2CppStructArray; allTypes["Il2CppInterop.Runtime.Il2CppException"] = Il2CppException; allTypes["Il2CppInterop.Runtime.IL2CPP"] = Il2Cpp; + allTypes["Il2CppInterop.Runtime.NativeBoolean"] = NativeBoolean; } private void InitMethodRefs() diff --git a/Il2CppInterop.Runtime/Attributes/HideFromIl2CppAttribute.cs b/Il2CppInterop.Runtime/Attributes/HideFromIl2CppAttribute.cs index 78af88c8..bd17261b 100644 --- a/Il2CppInterop.Runtime/Attributes/HideFromIl2CppAttribute.cs +++ b/Il2CppInterop.Runtime/Attributes/HideFromIl2CppAttribute.cs @@ -6,7 +6,7 @@ namespace Il2CppInterop.Runtime.Attributes; /// This attribute indicates that the target should not be exposed to IL2CPP in injected classes /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property | - AttributeTargets.Event)] + AttributeTargets.Event | AttributeTargets.Field)] public class HideFromIl2CppAttribute : Attribute { } diff --git a/Il2CppInterop.Runtime/Attributes/Il2CppImplementsAttribute.cs b/Il2CppInterop.Runtime/Attributes/Il2CppImplementsAttribute.cs index 9a86fded..7b8fe72b 100644 --- a/Il2CppInterop.Runtime/Attributes/Il2CppImplementsAttribute.cs +++ b/Il2CppInterop.Runtime/Attributes/Il2CppImplementsAttribute.cs @@ -2,7 +2,7 @@ namespace Il2CppInterop.Runtime.Attributes; -[AttributeUsage(AttributeTargets.Class)] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class Il2CppImplementsAttribute : Attribute { public Il2CppImplementsAttribute(params Type[] interfaces) diff --git a/Il2CppInterop.Runtime/Injection/ClassInjector.cs b/Il2CppInterop.Runtime/Injection/ClassInjector.cs index 82de25db..9b50b1a7 100644 --- a/Il2CppInterop.Runtime/Injection/ClassInjector.cs +++ b/Il2CppInterop.Runtime/Injection/ClassInjector.cs @@ -96,16 +96,20 @@ public static void DerivedConstructorBody(Il2CppObjectBase objectBase) { if (objectBase.isWrapped) return; - var fields = objectBase.GetType() - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) - .Where(IsFieldEligible) - .ToArray(); - foreach (var field in fields) - field.SetValue(objectBase, field.FieldType.GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new[] { typeof(Il2CppObjectBase), typeof(string) }, Array.Empty()) - .Invoke(new object[] { objectBase, field.Name }) - ); + if (!objectBase.GetType().IsValueType) + { + var fields = objectBase.GetType() + .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) + .Where(IsFieldEligible) + .ToArray(); + foreach (var field in fields) + field.SetValue(objectBase, field.FieldType.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + new[] { typeof(Il2CppObjectBase), typeof(string) }, Array.Empty()) + .Invoke(new object[] { objectBase, field.Name }) + ); + } + var ownGcHandle = GCHandle.Alloc(objectBase, GCHandleType.Normal); AssignGcHandle(objectBase.Pointer, ownGcHandle); } @@ -118,7 +122,7 @@ public static void AssignGcHandle(IntPtr pointer, GCHandle gcHandle) } - public static bool IsTypeRegisteredInIl2Cpp() where T : class + public static bool IsTypeRegisteredInIl2Cpp() { return IsTypeRegisteredInIl2Cpp(typeof(T)); } @@ -137,7 +141,7 @@ public static bool IsTypeRegisteredInIl2Cpp(Type type) return false; } - public static void RegisterTypeInIl2Cpp() where T : class + public static void RegisterTypeInIl2Cpp() { RegisterTypeInIl2Cpp(typeof(T)); } @@ -147,7 +151,7 @@ public static void RegisterTypeInIl2Cpp(Type type) RegisterTypeInIl2Cpp(type, RegisterTypeOptions.Default); } - public static void RegisterTypeInIl2Cpp(RegisterTypeOptions options) where T : class + public static void RegisterTypeInIl2Cpp(RegisterTypeOptions options) { RegisterTypeInIl2Cpp(typeof(T), options); } @@ -173,6 +177,9 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) return; //already registered in il2cpp var baseType = type.BaseType; + if (type.IsValueType) + baseType = typeof(ValueType); + if (baseType == null) throw new ArgumentException($"Class {type} does not inherit from a class registered in il2cpp"); @@ -190,8 +197,8 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) // Initialize the vtable of all base types (Class::Init is recursive internally) InjectorHelpers.ClassInit(baseClassPointer.ClassPointer); - if (baseClassPointer.ValueType || baseClassPointer.EnumType) - throw new ArgumentException($"Base class {baseType} is value type and can't be inherited from"); + if (baseClassPointer.EnumType) + throw new ArgumentException($"Base class {baseType} is a enum type and can't be inherited from"); if (baseClassPointer.IsGeneric) throw new ArgumentException($"Base class {baseType} is generic and can't be inherited from"); @@ -226,6 +233,7 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) classPointer.SizeInited = true; classPointer.HasFinalize = true; classPointer.IsVtableInitialized = true; + classPointer.ValueType = type.IsValueType; classPointer.Name = Marshal.StringToHGlobalAnsi(type.Name); classPointer.Namespace = Marshal.StringToHGlobalAnsi(type.Namespace ?? string.Empty); @@ -253,9 +261,18 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) fieldInfo.Parent = classPointer.ClassPointer; fieldInfo.Offset = fieldOffset; - var fieldType = fieldsToInject[i].FieldType == typeof(Il2CppStringField) - ? typeof(string) - : fieldsToInject[i].FieldType.GenericTypeArguments[0]; + Type fieldType; + if (type.IsValueType) + { + fieldType = fieldsToInject[i].FieldType; + } + else + { + fieldType = fieldsToInject[i].FieldType == typeof(Il2CppStringField) + ? typeof(string) + : fieldsToInject[i].FieldType.GenericTypeArguments[0]; + } + var fieldAttributes = fieldsToInject[i].Attributes; var fieldInfoClass = Il2CppClassPointerStore.GetNativeClassPointer(fieldType); if (!_injectedFieldTypes.TryGetValue((fieldType, fieldAttributes), out var fieldTypePtr)) @@ -295,8 +312,9 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) classPointer.InstanceSize = (uint)(fieldOffset + sizeof(InjectedClassData)); classPointer.ActualSize = classPointer.InstanceSize; - var eligibleMethods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly).Where(IsMethodEligible).ToArray(); - var methodsOffset = type.IsAbstract ? 1 : 2; // 1 is the finalizer, 1 is empty ctor + var eligibleMethods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) + .Where(IsMethodEligible).ToArray(); + var methodsOffset = (type.IsAbstract || type.IsValueType) ? 1 : 2; // 1 is the finalizer, 1 is empty ctor var methodCount = methodsOffset + eligibleMethods.Length; classPointer.MethodCount = (ushort)methodCount; @@ -305,7 +323,7 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) methodPointerArray[0] = ConvertStaticMethod(FinalizeDelegate, "Finalize", classPointer); var finalizeMethod = UnityVersionHandler.Wrap(methodPointerArray[0]); - if (!type.IsAbstract) methodPointerArray[1] = ConvertStaticMethod(CreateEmptyCtor(type, fieldsToInject), ".ctor", classPointer); + if (!type.IsAbstract && !type.IsValueType) methodPointerArray[1] = ConvertStaticMethod(CreateEmptyCtor(type, fieldsToInject), ".ctor", classPointer); var infos = new Dictionary<(string, int, bool), int>(eligibleMethods.Length); for (var i = 0; i < eligibleMethods.Length; i++) { @@ -515,6 +533,10 @@ private static bool IsTypeSupported(Type type) private static bool IsFieldEligible(FieldInfo field) { + if (field.CustomAttributes.Any(it => typeof(HideFromIl2CppAttribute).IsAssignableFrom(it.AttributeType))) + return false; + if (field.DeclaringType.IsValueType) + return IsTypeSupported(field.FieldType); if (!field.FieldType.IsGenericType) return field.FieldType == typeof(Il2CppStringField); var genericTypeDef = field.FieldType.GetGenericTypeDefinition(); if (genericTypeDef != typeof(Il2CppReferenceField<>) && genericTypeDef != typeof(Il2CppValueField<>)) diff --git a/Il2CppInterop.Runtime/InteropTypes/NativeBoolean.cs b/Il2CppInterop.Runtime/InteropTypes/NativeBoolean.cs new file mode 100644 index 00000000..a397f913 --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/NativeBoolean.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Il2CppInterop.Runtime +{ + [StructLayout(LayoutKind.Sequential)] + public readonly struct NativeBoolean : IComparable, IComparable, IEquatable, IComparable, IEquatable + { + private readonly byte Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator bool(NativeBoolean b) + => Unsafe.As(ref b); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator NativeBoolean(bool b) + => Unsafe.As(ref b); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + => Unsafe.As(ref Unsafe.AsRef(in Value)).GetHashCode(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() + => Unsafe.As(ref Unsafe.AsRef(in Value)).ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ToString(IFormatProvider? provider) + => Unsafe.As(ref Unsafe.AsRef(in Value)).ToString(provider); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryFormat(Span destination, out int charsWritten) + => Unsafe.As(ref Unsafe.AsRef(in Value)).TryFormat(destination, out charsWritten); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object? obj) + => obj switch + { + bool boolean => this == boolean, + NativeBoolean nativeBool => this == nativeBool, + _ => false + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(bool other) + => this == other; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(NativeBoolean other) + => this == other; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int CompareTo(object? obj) + => Unsafe.As(ref Unsafe.AsRef(in Value)).CompareTo(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int CompareTo(bool value) + => Unsafe.As(ref Unsafe.AsRef(in Value)).CompareTo(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int CompareTo(NativeBoolean value) + => CompareTo(Unsafe.As(ref value)); + } +}