diff --git a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs index 057a2ac3..5247929d 100644 --- a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs @@ -1,6 +1,8 @@ using System.Diagnostics; using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Metadata.Tables; using Il2CppInterop.Generator.Extensions; using Il2CppInterop.Generator.Utils; @@ -14,6 +16,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; @@ -31,7 +34,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) { @@ -48,6 +52,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,12 +72,26 @@ public IMethodDefOrRef RewriteMethodRef(IMethodDefOrRef methodRef) return NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(newMethod); } - public ITypeDefOrRef RewriteTypeRef(ITypeDescriptor typeRef) + /// + /// Rewrite passes type + /// + /// type + /// generic context. Ensure you pass NEW types here + /// are we rewriting boxed variant + /// new type + public ITypeDefOrRef RewriteTypeRef(ITypeDescriptor typeRef, GenericParameterContext context = default, bool typeIsBoxed = false) { - return RewriteTypeRef(typeRef?.ToTypeSignature()).ToTypeDefOrRef(); + return RewriteTypeRef(typeRef?.ToTypeSignature(), context, typeIsBoxed).ToTypeDefOrRef(); } - public TypeSignature RewriteTypeRef(TypeSignature? typeRef) + /// + /// Rewrite passes type + /// + /// type + /// generic context. Ensure you pass NEW types here + /// are we rewriting boxed variant + /// new type + public TypeSignature RewriteTypeRef(TypeSignature? typeRef, GenericParameterContext context = default, bool typeIsBoxed = false) { if (typeRef == null) return Imports.Il2CppObjectBase; @@ -84,7 +107,7 @@ public TypeSignature RewriteTypeRef(TypeSignature? typeRef) if (elementType.FullName == "System.String") return Imports.Il2CppStringArray; - var convertedElementType = RewriteTypeRef(elementType); + var convertedElementType = RewriteTypeRef(elementType, context, typeIsBoxed); if (elementType is GenericParameterSignature) return new GenericInstanceTypeSignature(Imports.Il2CppArrayBase.ToTypeDefOrRef(), false, convertedElementType); @@ -94,24 +117,35 @@ public TypeSignature RewriteTypeRef(TypeSignature? typeRef) } if (typeRef is GenericParameterSignature genericParameter) - { return new GenericParameterSignature(sourceModule, genericParameter.ParameterType, genericParameter.Index); - } if (typeRef is ByReferenceTypeSignature byRef) - return new ByReferenceTypeSignature(RewriteTypeRef(byRef.BaseType)); + return new ByReferenceTypeSignature(RewriteTypeRef(byRef.BaseType, context, typeIsBoxed)); if (typeRef is PointerTypeSignature pointerType) - return new PointerTypeSignature(RewriteTypeRef(pointerType.BaseType)); + return new PointerTypeSignature(RewriteTypeRef(pointerType.BaseType, context, typeIsBoxed)); if (typeRef is GenericInstanceTypeSignature genericInstance) { - var genericType = RewriteTypeRef(genericInstance.GenericType.ToTypeSignature()).ToTypeDefOrRef(); - var newRef = new GenericInstanceTypeSignature(genericType, genericType.IsValueType); - foreach (var originalParameter in genericInstance.TypeArguments) - newRef.TypeArguments.Add(RewriteTypeRef(originalParameter)); - - return newRef; + var genericTypeContext = GetTypeContext(genericInstance); + if (genericTypeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct && !IsUnmanaged(typeRef, context)) + { + var type = sourceModule.DefaultImporter.ImportType(genericTypeContext.BoxedTypeContext.NewType); + var newRef = new GenericInstanceTypeSignature(type, type.IsValueType); + foreach (var originalParameter in genericInstance.TypeArguments) + newRef.TypeArguments.Add(RewriteTypeRef(originalParameter, context, typeIsBoxed)); + + return newRef; + } + else + { + var genericType = RewriteTypeRef(genericInstance.GenericType.ToTypeSignature(), context, typeIsBoxed).ToTypeDefOrRef(); + var newRef = new GenericInstanceTypeSignature(genericType, genericType.IsValueType); + foreach (var originalParameter in genericInstance.TypeArguments) + newRef.TypeArguments.Add(RewriteTypeRef(originalParameter, context, typeIsBoxed)); + + return newRef; + } } if (typeRef.IsPrimitive() || typeRef.FullName == "System.TypedReference") @@ -131,11 +165,76 @@ public TypeSignature RewriteTypeRef(TypeSignature? typeRef) return sourceModule.DefaultImporter.ImportType(GlobalContext.GetAssemblyByName("mscorlib") .GetTypeByName("System.Attribute").NewType).ToTypeSignature(); - 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.DefaultImporter.ImportType(target.NewType).ToTypeSignature(); + } + + private TypeRewriteContext GetTypeContext(TypeSignature typeRef) + { + return GlobalContext.GetNewTypeForOriginal(typeRef.Resolve()!); + } + + private bool IsUnmanaged(TypeSignature originalType, GenericParameterContext context) + { + if (originalType is GenericParameterSignature parameterSignature) + { + var genericParameter = context.GetGenericParameter(parameterSignature)!; + return genericParameter.IsUnmanaged(); + } + + if (originalType is GenericInstanceTypeSignature genericInstanceType) + { + foreach (TypeSignature genericArgument in genericInstanceType.TypeArguments) + { + if (!IsUnmanaged(genericArgument, context)) + return false; + } + } + + var paramTypeContext = GetTypeContext(originalType); + return paramTypeContext.ComputedTypeSpecifics.IsBlittable(); + } + + public TypeDefinition GetOrInjectIsUnmanagedAttribute() + { + if (isUnmanagedAttributeType != null) + return isUnmanagedAttributeType; + + var importer = NewAssembly.ManifestModule!.DefaultImporter; + + isUnmanagedAttributeType = new TypeDefinition( + "System.Runtime.CompilerServices", + "IsUnmanagedAttribute", + TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Public | TypeAttributes.Sealed, + importer.ImportType(typeof(Attribute))); + + NewAssembly.ManifestModule!.TopLevelTypes.Add(isUnmanagedAttributeType); + + var attributeCctr = new MethodDefinition( + ".ctor", + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RuntimeSpecialName | MethodAttributes.SpecialName, + MethodSignature.CreateInstance(NewAssembly.ManifestModule.Void())); + attributeCctr.CilMethodBody = new CilMethodBody(attributeCctr); + + isUnmanagedAttributeType.Methods.Add(attributeCctr); + var ilProcessor = attributeCctr.CilMethodBody!.Instructions; + ilProcessor.Add(OpCodes.Ldarg_0); + + var method = new MemberReference( + isUnmanagedAttributeType.BaseType!, + ".ctor", + MethodSignature.CreateInstance(NewAssembly.ManifestModule.Void())); + + ilProcessor.Add(OpCodes.Call, importer.ImportMethod(method)); + ilProcessor.Add(OpCodes.Ret); - return sourceModule.DefaultImporter.ImportType(target).ToTypeSignature(); + return isUnmanagedAttributeType; } public TypeRewriteContext GetTypeByName(string name) diff --git a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs b/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs index a2420e68..4bba40f7 100644 --- a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs @@ -51,7 +51,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.Signature!.FieldType).GetUnmangledName(field.DeclaringType); + DeclaringType.AssemblyContext.RewriteTypeRef(field.Signature!.FieldType, field.DeclaringType!.GetGenericParameterContext()).GetUnmangledName(field.DeclaringType); } private string UnmangleFieldName(FieldDefinition field, GeneratorOptions options, diff --git a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs b/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs index 178bfae1..e3f88f42 100644 --- a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs @@ -3,6 +3,7 @@ using System.Text; using AsmResolver; using AsmResolver.DotNet; +using AsmResolver.DotNet.Collections; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; using Il2CppInterop.Common.XrefScans; @@ -74,9 +75,18 @@ public MethodRewriteContext(TypeRewriteContext declaringType, MethodDefinition o foreach (var oldParameter in genericParams) { - newMethod.GenericParameters.Add(new GenericParameter( - oldParameter.Name.MakeValidInSource(), - oldParameter.Attributes.StripValueTypeConstraint())); + var genericParameter = new GenericParameter(oldParameter.Name.MakeValidInSource()); + if (ShouldParameterBeBlittable(originalMethod, oldParameter.ToTypeSignature())) + { + genericParameter.Attributes = oldParameter.Attributes; + genericParameter.MakeUnmanaged(DeclaringType.AssemblyContext); + } + else + { + genericParameter.Attributes = oldParameter.Attributes.StripValueTypeConstraint(); + } + + newMethod.GenericParameters.Add(genericParameter); } } @@ -92,6 +102,38 @@ public MethodRewriteContext(TypeRewriteContext declaringType, MethodDefinition o declaringType.AssemblyContext.GlobalContext.MethodStartAddresses.Add(FileOffset); } + private bool ShouldParameterBeBlittable(MethodDefinition method, GenericParameterSignature genericParameter) + { + if (HasGenericParameter(method.Signature!.ReturnType, genericParameter, out GenericParameter? parameter)) + { + return parameter!.IsUnmanaged(); + } + + foreach (Parameter methodParameter in method.Parameters) + { + if (HasGenericParameter(methodParameter.ParameterType, genericParameter, out parameter)) + { + return parameter!.IsUnmanaged(); + } + } + + return false; + } + + private bool HasGenericParameter(TypeSignature typeReference, GenericParameterSignature inputGenericParameter, out GenericParameter? typeGenericParameter) + { + typeGenericParameter = null; + if (typeReference is not GenericInstanceTypeSignature genericInstance) return false; + + var index = genericInstance.TypeArguments.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 Utf8String? UnmangledName { get; private set; } public string? UnmangledNameWithSignature { get; private set; } @@ -104,11 +146,14 @@ public MethodRewriteContext(TypeRewriteContext declaringType, MethodDefinition o public void CtorPhase2() { - UnmangledName = UnmangleMethodName(); - UnmangledNameWithSignature = UnmangleMethodNameWithSignature(); + // Make context manually, NewMethod.DeclaringType is null for now + var genericContext = new GenericParameterContext(DeclaringType.NewType, NewMethod); + + UnmangledName = UnmangleMethodName(genericContext); + UnmangledNameWithSignature = UnmangleMethodNameWithSignature(genericContext); NewMethod.Name = UnmangledName; - NewMethod.Signature!.ReturnType = DeclaringType.AssemblyContext.RewriteTypeRef(OriginalMethod.Signature?.ReturnType); + NewMethod.Signature!.ReturnType = DeclaringType.AssemblyContext.RewriteTypeRef(OriginalMethod.Signature?.ReturnType, genericContext, DeclaringType.isBoxedTypeVariant); var nonGenericMethodInfoPointerField = new FieldDefinition( "NativeMethodInfoPtr_" + UnmangledNameWithSignature, @@ -153,8 +198,9 @@ public void CtorPhase2() continue; } - newParameter.Constraints.Add(new GenericParameterConstraint( - DeclaringType.AssemblyContext.RewriteTypeRef(oldConstraint.Constraint?.ToTypeSignature()).ToTypeDefOrRef())); + var newType = DeclaringType.AssemblyContext.RewriteTypeRef(oldConstraint.Constraint?.ToTypeSignature(), default, DeclaringType.isBoxedTypeVariant) + .ToTypeDefOrRef(); + newParameter.Constraints.Add(new GenericParameterConstraint(newType)); } } @@ -184,7 +230,7 @@ private MethodAttributes AdjustAttributes(MethodAttributes original, bool stripV return original; } - private string UnmangleMethodName() + private string UnmangleMethodName(GenericParameterContext context) { var method = OriginalMethod; @@ -198,12 +244,12 @@ private string UnmangleMethodName() return ".ctor"; if (method.Name.IsObfuscated(DeclaringType.AssemblyContext.GlobalContext.Options)) - return UnmangleMethodNameWithSignature(); + return UnmangleMethodNameWithSignature(context); return method.Name.MakeValidInSource(); } - private string ProduceMethodSignatureBase() + private string ProduceMethodSignatureBase(GenericParameterContext context) { var method = OriginalMethod; @@ -231,12 +277,12 @@ private string ProduceMethodSignatureBase() builder.Append(str); builder.Append('_'); - builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(method.Signature?.ReturnType).GetUnmangledName(method.DeclaringType, method)); + builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(method.Signature?.ReturnType, context, DeclaringType.isBoxedTypeVariant).GetUnmangledName(method.DeclaringType, method)); foreach (var param in method.Parameters) { builder.Append('_'); - builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(param.ParameterType).GetUnmangledName(method.DeclaringType, method)); + builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(param.ParameterType, context, DeclaringType.isBoxedTypeVariant).GetUnmangledName(method.DeclaringType, method)); } var address = Rva; @@ -247,9 +293,9 @@ private string ProduceMethodSignatureBase() } - private string UnmangleMethodNameWithSignature() + private string UnmangleMethodNameWithSignature(GenericParameterContext context) { - var unmangleMethodNameWithSignature = ProduceMethodSignatureBase() + "_" + DeclaringType.Methods + var unmangleMethodNameWithSignature = ProduceMethodSignatureBase(context) + "_" + DeclaringType.Methods .Where(ParameterSignatureMatchesThis).TakeWhile(it => it != this).Count(); if (DeclaringType.AssemblyContext.GlobalContext.Options.RenameMap.TryGetValue( diff --git a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs b/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs index 6b017c25..7ca14724 100644 --- a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs +++ b/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs @@ -3,6 +3,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; using Il2CppInterop.Generator.Extensions; +using Il2CppInterop.Generator.Passes; using Il2CppInterop.Generator.Utils; namespace Il2CppInterop.Generator.Contexts; @@ -16,15 +17,26 @@ 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; #nullable disable @@ -34,6 +46,8 @@ public enum TypeSpecifics #nullable enable public TypeSpecifics ComputedTypeSpecifics; + public GenericParameterSpecifics[] genericParameterUsage; + public bool genericParameterUsageComputed; public TypeRewriteContext(AssemblyRewriteContext assemblyContext, TypeDefinition? originalType, TypeDefinition newType) @@ -44,7 +58,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 && + Pass12CreateGenericNonBlittableTypes.GetUnboxedName(originalType.Name) != NewType.Name; if (OriginalNameWasObfuscated) NewType.CustomAttributes.Add(new CustomAttribute( (ICustomAttributeType)assemblyContext.Imports.ObfuscatedNameAttributector.Value, @@ -54,8 +70,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 } // These are initialized in AddMembers, which is called from an early rewrite pass. @@ -183,6 +197,49 @@ 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.Number]) + { + genericParameterUsage[parameter.Number] = 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); + } + } + } + private string GetDebuggerDisplay() { return NewType.FullName; diff --git a/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs b/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs index aaddb2b6..d8605530 100644 --- a/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs +++ b/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs @@ -2,6 +2,7 @@ using AsmResolver.DotNet.Collections; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using Il2CppInterop.Generator.Utils; namespace Il2CppInterop.Generator.Extensions; @@ -104,4 +105,14 @@ public static GenericParameterSignature ToTypeSignature(this GenericParameter ge { return new GenericParameterSignature(genericParameter.Owner is ITypeDescriptor ? GenericParameterType.Type : GenericParameterType.Method, genericParameter.Number); } + + public static GenericParameterContext GetGenericParameterContext(this TypeDefinition type) + { + return new GenericParameterContext(type, null); + } + + public static GenericParameterContext GetGenericParameterContext(this MethodDefinition method) + { + return new GenericParameterContext(method.DeclaringType, method); + } } diff --git a/Il2CppInterop.Generator/Extensions/EnumEx.cs b/Il2CppInterop.Generator/Extensions/EnumEx.cs index b305c61d..f76bf7c2 100644 --- a/Il2CppInterop.Generator/Extensions/EnumEx.cs +++ b/Il2CppInterop.Generator/Extensions/EnumEx.cs @@ -1,4 +1,5 @@ using AsmResolver.PE.DotNet.Metadata.Tables; +using Il2CppInterop.Generator.Contexts; 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..1105c6a6 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/GenericParameterEx.cs @@ -0,0 +1,33 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Metadata.Tables; +using Il2CppInterop.Generator.Contexts; +using Il2CppInterop.Generator.Utils; + +namespace Il2CppInterop.Generator.Extensions +{ + public static class GenericParameterEx + { + public static void MakeUnmanaged(this GenericParameter genericParameter, AssemblyRewriteContext assemblyContext) + { + var isUnmanagedAttribute = assemblyContext.GetOrInjectIsUnmanagedAttribute(); + + genericParameter.Attributes |= GenericParameterAttributes.DefaultConstructorConstraint | GenericParameterAttributes.NotNullableValueTypeConstraint; + + var importer = assemblyContext.Imports.Module.DefaultImporter; + genericParameter.Constraints.Add( + new GenericParameterConstraint( + importer.ImportType(typeof(ValueType)) + .MakeModifierType(importer.ImportType(typeof(System.Runtime.InteropServices.UnmanagedType)), true) + .ToTypeDefOrRef() + )); + + genericParameter.CustomAttributes.Add(new CustomAttribute(isUnmanagedAttribute.Methods[0])); + } + + 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 5c1ee845..6571a157 100644 --- a/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs +++ b/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs @@ -382,6 +382,7 @@ private static void EmitPointerToObjectGeneric(ILProcessor body, TypeSignature o body.Add(extraDerefForNonValueTypes ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); body.Add(unboxValueType ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + body.Add(OpCodes.Call, imports.Module.DefaultImporter.ImportMethod(imports.IL2CPP_PointerToValueGeneric.Value.MakeGenericInstanceMethod(newReturnType))); } diff --git a/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs b/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs index e898eda4..b9d0ca94 100644 --- a/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs +++ b/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs @@ -1,5 +1,6 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using Il2CppInterop.Generator.Utils; namespace Il2CppInterop.Generator.Extensions; diff --git a/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs b/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs index b50cd2e8..f16e30e0 100644 --- a/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs +++ b/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs @@ -1,16 +1,221 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Collections; using AsmResolver.DotNet.Signatures; using Il2CppInterop.Generator.Contexts; using Il2CppInterop.Generator.Extensions; +using Il2CppInterop.Generator.Utils; namespace Il2CppInterop.Generator.Passes; public static class Pass11ComputeTypeSpecifics { + private static RewriteGlobalContext globalContext; + public static void DoPass(RewriteGlobalContext context) { + globalContext = context; + typeUsageDictionary.Clear(); + + foreach (var assemblyContext in context.Assemblies) + foreach (var typeContext in assemblyContext.Types) + { + ComputeGenericParameterUsageSpecifics(typeContext); + ScanTypeContextUsage(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); + } + + + 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.Owner != originalType) return; + + typeContext.SetGenericParameterUsageSpecifics(parameter.Number, 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.Signature!.FieldType, originalType.GetGenericParameterContext(), + 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.Signature!.FieldType, originalType.GetGenericParameterContext(), + TypeRewriteContext.GenericParameterSpecifics.Relaxed, OnResult); + } + + foreach (var originalMethod in originalType.Methods) + { + FindTypeGenericParameters(originalMethod.Signature!.ReturnType, originalMethod.GetGenericParameterContext(), + TypeRewriteContext.GenericParameterSpecifics.Relaxed, OnResult); + + foreach (Parameter parameter in originalMethod.Parameters) + { + FindTypeGenericParameters(parameter.ParameterType, originalMethod.GetGenericParameterContext(), + 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.Number]; + if (otherParameterSpecific == TypeRewriteContext.GenericParameterSpecifics.Strict) + typeContext.SetGenericParameterUsageSpecifics(myParameter.Number, otherParameterSpecific); + } + } + } + + private static void FindTypeGenericParameters( + TypeSignature? reference, + GenericParameterContext parameterContext, + TypeRewriteContext.GenericParameterSpecifics currentConstraint, + Action onFound) + { + if (reference is GenericParameterSignature parameterSignature) + { + var genericParameter = parameterContext.GetGenericParameter(parameterSignature); + onFound?.Invoke(genericParameter!, currentConstraint); + return; + } + + if (reference is PointerTypeSignature) + { + FindTypeGenericParameters(reference!.GetElementType(), parameterContext, + TypeRewriteContext.GenericParameterSpecifics.Strict, onFound); + return; + } + + if (reference is ArrayBaseTypeSignature or ByReferenceTypeSignature) + { + FindTypeGenericParameters(reference.GetElementType(), parameterContext, + currentConstraint, onFound); + return; + } + + if (reference is GenericInstanceTypeSignature genericInstance) + { + var typeDefinition = reference.Resolve()!; + var typeContext = globalContext.GetNewTypeForOriginal(typeDefinition); + ComputeGenericParameterUsageSpecifics(typeContext); + for (var i = 0; i < genericInstance.TypeArguments.Count; i++) + { + var myConstraint = typeContext.genericParameterUsage[i]; + if (myConstraint == TypeRewriteContext.GenericParameterSpecifics.AffectsBlittability) + myConstraint = currentConstraint; + + var genericArgument = genericInstance.TypeArguments[i]; + FindTypeGenericParameters(genericArgument, parameterContext, myConstraint, onFound); + } + } + } + + internal static Dictionary typeUsageDictionary = new Dictionary(new TypeComparer()); + + private static void ScanTypeContextUsage(TypeRewriteContext typeContext) + { + var type = typeContext.OriginalType; + + foreach (FieldDefinition fieldDefinition in type.Fields) + { + ScanTypeUsage(fieldDefinition.Signature!.FieldType, type.GetGenericParameterContext()); + } + + foreach (PropertyDefinition propertyDefinition in type.Properties) + { + ScanTypeUsage(propertyDefinition.Signature!.ReturnType, type.GetGenericParameterContext()); + } + + foreach (MethodDefinition method in type.Methods) + { + ScanTypeUsage(method.Signature!.ReturnType, method.GetGenericParameterContext()); + foreach (Parameter parameterDefinition in method.Parameters) + { + ScanTypeUsage(parameterDefinition.ParameterType, method.GetGenericParameterContext()); + } + } + } + + private static void ScanTypeUsage(TypeSignature? typeRef, GenericParameterContext parameterContext) + { + while (typeRef is PointerTypeSignature pointerType) + { + typeRef = pointerType.BaseType; + } + + while (typeRef is ArrayTypeSignature arrayType) + { + typeRef = arrayType.BaseType; + } + + if (typeRef is not GenericInstanceTypeSignature genericInstanceType) return; + + foreach (TypeSignature typeReference in genericInstanceType.TypeArguments) + { + ScanTypeUsage(typeReference, parameterContext); + } + + TypeDefinition typeDef = typeRef.Resolve()!; + if (typeDef?.BaseType == null || !typeDef.BaseType.Name!.Equals("ValueType")) return; + + + if (!typeUsageDictionary.TryGetValue(typeDef, out ParameterUsage usage)) + { + usage = new ParameterUsage(genericInstanceType.TypeArguments.Count); + typeUsageDictionary.Add(typeDef, usage); + } + + for (var i = 0; i < genericInstanceType.TypeArguments.Count; i++) + { + usage.AddUsage(i, genericInstanceType.TypeArguments[i], parameterContext); + } + } + + private static bool IsValueTypeOnly(TypeRewriteContext typeContext, GenericParameter genericParameter) + { + if (genericParameter.Constraints.All(constraint => constraint.Constraint!.FullName != "System.ValueType")) + return false; + + if (typeUsageDictionary.ContainsKey(typeContext.OriginalType)) + { + var usage = typeUsageDictionary[typeContext.OriginalType]; + return usage.IsBlittableParameter(typeContext.AssemblyContext.GlobalContext, genericParameter.Number); + } + + return true; } private static void ComputeSpecifics(TypeRewriteContext typeContext) @@ -30,10 +235,11 @@ private static void ComputeSpecifics(TypeRewriteContext typeContext) if (originalField.IsStatic) continue; var fieldType = originalField.Signature!.FieldType; - if (fieldType.IsPrimitive() || fieldType is PointerTypeSignature) - continue; + + if (fieldType.IsPrimitive() || fieldType is PointerTypeSignature or GenericParameterSignature) continue; + if (fieldType.FullName == "System.String" || fieldType.FullName == "System.Object" - || fieldType is ArrayBaseTypeSignature or ByReferenceTypeSignature or GenericParameterSignature or GenericInstanceTypeSignature) + || fieldType is ArrayBaseTypeSignature or ByReferenceTypeSignature) { typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; return; @@ -41,13 +247,143 @@ private static void ComputeSpecifics(TypeRewriteContext typeContext) var fieldTypeContext = typeContext.AssemblyContext.GlobalContext.GetNewTypeForOriginal(fieldType.Resolve()!); ComputeSpecifics(fieldTypeContext); - if (fieldTypeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct) + if (fieldTypeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) + { + var genericInstance = fieldType as GenericInstanceTypeSignature; + for (var i = 0; i < genericInstance!.TypeArguments.Count; i++) + { + var genericArgument = genericInstance.TypeArguments[i]; + if (genericArgument is GenericParameterSignature) 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.Number] == TypeRewriteContext.GenericParameterSpecifics.AffectsBlittability || + typeContext.genericParameterUsage[genericParameter.Number] == TypeRewriteContext.GenericParameterSpecifics.Strict) + + { + if (IsValueTypeOnly(typeContext, genericParameter)) + { + typeContext.SetGenericParameterUsageSpecifics(genericParameter.Number, TypeRewriteContext.GenericParameterSpecifics.Strict); + continue; + } + + return; + } + } + + typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.BlittableStruct; + } + + internal class ParameterUsage + { + private readonly 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, TypeSignature type, GenericParameterContext context) + { + if (type is GenericParameterSignature parameterSignature) + { + var genericParameter = context.GetGenericParameter(parameterSignature)!; + var declaringName = GetDeclaringName(genericParameter); + + if (usageData[index].All(reference => + reference is not GenericParameter parameter || + !GetDeclaringName(parameter).Equals(declaringName)) + ) + { + usageData[index].Add(genericParameter); + } + } + else if (usageData[index].All(reference => + reference is not TypeSignature fullNameProvider || + !fullNameProvider.FullName.Equals(type.FullName))) + { + usageData[index].Add(type); + } + } + + private static string GetDeclaringName(GenericParameter genericParameter) + { + var declaringName = genericParameter.Owner!.FullName; + declaringName += genericParameter.Name; + return declaringName; + } + + public bool IsBlittableParameter(RewriteGlobalContext globalContext, int index) + { + var usages = usageData[index]; + + foreach (INameProvider reference in usages) + { + if (reference is GenericParameter genericParameter) + { + if (genericParameter.Constraints.All(constraint => constraint.Constraint!.FullName != "System.ValueType")) + return false; + } + else if (reference is TypeSignature typeSignature) + { + var typeDef = typeSignature.Resolve()!; + var fieldTypeContext = globalContext.GetNewTypeForOriginal(typeDef); + ComputeSpecifics(fieldTypeContext); + if (fieldTypeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.NonBlittableStruct) + return false; + } + } + + return true; + } + } + + private sealed class TypeComparer : EqualityComparer + { + public override bool Equals(TypeDefinition x, TypeDefinition y) + { + return x.FullName.Equals(y.FullName); + } + + public override int GetHashCode(TypeDefinition obj) + { + return obj.FullName.GetHashCode(); + } + } } diff --git a/Il2CppInterop.Generator/Passes/Pass12CreateGenericNonBlittableTypes.cs b/Il2CppInterop.Generator/Passes/Pass12CreateGenericNonBlittableTypes.cs new file mode 100644 index 00000000..3680b906 --- /dev/null +++ b/Il2CppInterop.Generator/Passes/Pass12CreateGenericNonBlittableTypes.cs @@ -0,0 +1,63 @@ +using AsmResolver.DotNet; +using Il2CppInterop.Generator.Contexts; + +namespace Il2CppInterop.Generator.Passes; + +public static class Pass12CreateGenericNonBlittableTypes +{ + 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.ManifestModule!.TopLevelTypes.Add(newBoxedType); + } + else + { + declaringType.NestedTypes.Add(newBoxedType); + } + + 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/Pass13FillTypedefs.cs similarity index 55% rename from Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs rename to Il2CppInterop.Generator/Passes/Pass13FillTypedefs.cs index 39b9f5b1..b0f5321a 100644 --- a/Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs +++ b/Il2CppInterop.Generator/Passes/Pass13FillTypedefs.cs @@ -5,11 +5,12 @@ namespace Il2CppInterop.Generator.Passes; -public static class Pass12FillTypedefs +public static class Pass13FillTypedefs { 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) @@ -17,19 +18,30 @@ public static void DoPass(RewriteGlobalContext context) var newParameter = new GenericParameter(originalParameter.Name.MakeValidInSource(), originalParameter.Attributes.StripValueTypeConstraint()); typeContext.NewType.GenericParameters.Add(newParameter); + + var parameterSpecifics = typeContext.genericParameterUsage[originalParameter.Number]; + 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().ToTypeDefOrRef(); - else if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct) + else if (typeContext.ComputedTypeSpecifics.IsBlittable()) typeContext.NewType.BaseType = assemblyContext.Imports.Module.ValueType().ToTypeDefOrRef(); } + } // 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, typeContext.OriginalType.GetGenericParameterContext(), false); } } diff --git a/Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs b/Il2CppInterop.Generator/Passes/Pass14FillGenericConstraints.cs similarity index 93% rename from Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs rename to Il2CppInterop.Generator/Passes/Pass14FillGenericConstraints.cs index 58451af2..0fdb9864 100644 --- a/Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs +++ b/Il2CppInterop.Generator/Passes/Pass14FillGenericConstraints.cs @@ -5,7 +5,7 @@ namespace Il2CppInterop.Generator.Passes; -public static class Pass13FillGenericConstraints +public static class Pass14FillGenericConstraints { public static void DoPass(RewriteGlobalContext context) { @@ -31,7 +31,7 @@ public static void DoPass(RewriteGlobalContext context) newParameter.Constraints.Add( new GenericParameterConstraint( - assemblyContext.RewriteTypeRef(originalConstraint.Constraint!))); + assemblyContext.RewriteTypeRef(originalConstraint.Constraint!, default, false))); } } } diff --git a/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs b/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs index 709dd202..0d6b9719 100644 --- a/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs +++ b/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs @@ -24,7 +24,7 @@ public static void DoPass(RewriteGlobalContext context) : originalMethodParameter.Name; var newParameter = newMethod.AddParameter( - assemblyContext.RewriteTypeRef(originalMethodParameter.ParameterType), + assemblyContext.RewriteTypeRef(originalMethodParameter.ParameterType, newMethod.GetGenericParameterContext(), typeContext.isBoxedTypeVariant), newName, originalMethodParameter.GetOrCreateDefinition().Attributes & ~ParameterAttributes.HasFieldMarshal); @@ -40,7 +40,7 @@ public static void DoPass(RewriteGlobalContext context) } var paramsMethod = context.CreateParamsMethod(originalMethod, newMethod, assemblyContext.Imports, - type => assemblyContext.RewriteTypeRef(type)); + type => assemblyContext.RewriteTypeRef(type, default, 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 47c93e7f..0087cebc 100644 --- a/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs +++ b/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs @@ -32,7 +32,7 @@ private static void GenerateStaticProxy(AssemblyRewriteContext assemblyContext, var ctorBuilder = staticCtorMethod.CilMethodBody!.Instructions; ctorBuilder.Clear(); - if (newType.IsNested) + if (newType.IsNested && oldType.IsNested) { ctorBuilder.Add(OpCodes.Ldsfld, assemblyContext.GlobalContext.GetNewTypeForOriginal(oldType.DeclaringType!).ClassPointerFieldRef); diff --git a/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs b/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs index 2ad087df..a4575f17 100644 --- a/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs +++ b/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs @@ -20,33 +20,64 @@ 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 typeRef = assemblyContext.Imports.Module.DefaultImporter.ImportType(boxedType); + var genericBoxedType = new GenericInstanceTypeSignature(typeRef, typeRef.IsValueType); + foreach (GenericParameter parameter in newType.GenericParameters) + genericBoxedType.TypeArguments.Add(parameter.ToTypeSignature()); + 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.Signature!.FieldType.IsValueType - ? assemblyContext.Imports.Module.IntPtr() - : assemblyContext.RewriteTypeRef(field.Signature.FieldType)); + TypeSignature rewriteTypeRef; + if (!field.Signature!.FieldType.IsValueType && field.Signature.FieldType is not PointerTypeSignature and not GenericParameterSignature) + rewriteTypeRef = assemblyContext.Imports.Module.IntPtr(); + else + rewriteTypeRef = assemblyContext.RewriteTypeRef(field.Signature.FieldType, newType.GetGenericParameterContext()); + + var newField = new FieldDefinition(fieldContext.UnmangledName, field.Attributes.ForcePublic(), rewriteTypeRef); + + if (!typeContext.OriginalType.HasGenericParameters()) + newField.FieldOffset = field.ExtractFieldOffset(); - newField.FieldOffset = field.ExtractFieldOffset(); // Special case: bools in Il2Cpp are bytes if (newField.Signature!.FieldType.FullName == "System.Boolean") - newField.MarshalDescriptor = new SimpleMarshalDescriptor(NativeType.U1); + { + if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.GenericBlittableStruct) + { + newField.Signature.FieldType = assemblyContext.Imports.NativeBoolean; + } + else + { + newField.MarshalDescriptor = new SimpleMarshalDescriptor(NativeType.U1); + } + } newType.Fields.Add(newField); } diff --git a/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs b/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs index 7615ecce..462ae201 100644 --- a/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs +++ b/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs @@ -44,7 +44,7 @@ public static void DoPass(RewriteGlobalContext context) fieldName = newName; var newDef = new FieldDefinition(fieldName, fieldDefinition.Attributes | FieldAttributes.HasDefault, - assemblyContext.RewriteTypeRef(fieldDefinition.Signature!.FieldType)); + assemblyContext.RewriteTypeRef(fieldDefinition.Signature!.FieldType, newType.GetGenericParameterContext())); newType.Fields.Add(newDef); newDef.Constant = fieldDefinition.Constant; diff --git a/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs b/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs index d5d468c2..47f5322b 100644 --- a/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs +++ b/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs @@ -14,7 +14,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 b100ebc0..0f5f2b66 100644 --- a/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs +++ b/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs @@ -2,6 +2,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; using Il2CppInterop.Generator.Contexts; +using Il2CppInterop.Generator.Extensions; using Il2CppInterop.Generator.Utils; namespace Il2CppInterop.Generator.Passes; @@ -16,13 +17,13 @@ public static void DoPass(RewriteGlobalContext context) { 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 propertyType = assemblyContext.RewriteTypeRef(fieldContext.OriginalField.Signature!.FieldType); + var propertyType = assemblyContext.RewriteTypeRef(field.Signature!.FieldType, typeContext.NewType!.GetGenericParameterContext(), typeContext.isBoxedTypeVariant); var signature = field.IsStatic ? PropertySignature.CreateStatic(propertyType) : PropertySignature.CreateInstance(propertyType); diff --git a/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs b/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs index d0f04a70..dd18b637 100644 --- a/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs +++ b/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs @@ -69,7 +69,8 @@ public static void DoPass(RewriteGlobalContext context) if (nextInstruction != null) nextInstruction.Instruction = bodyBuilder.Add(OpCodes.Nop); - 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 e80e7911..0dc9d357 100644 --- a/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs +++ b/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs @@ -25,12 +25,12 @@ public static void DoPass(RewriteGlobalContext context) var unmangledPropertyName = UnmanglePropertyName(assemblyContext, oldProperty, typeContext.NewType, propertyCountsByName); - var propertyType = assemblyContext.RewriteTypeRef(oldProperty.Signature!.ReturnType); + var propertyType = assemblyContext.RewriteTypeRef(oldProperty.Signature!.ReturnType, typeContext.NewType.GetGenericParameterContext(), typeContext.isBoxedTypeVariant); var signature = oldProperty.Signature.HasThis ? PropertySignature.CreateInstance(propertyType) : PropertySignature.CreateStatic(propertyType); foreach (var oldParameter in oldProperty.Signature.ParameterTypes) - signature.ParameterTypes.Add(assemblyContext.RewriteTypeRef(oldParameter)); + signature.ParameterTypes.Add(assemblyContext.RewriteTypeRef(oldParameter, typeContext.NewType.GetGenericParameterContext(), typeContext.isBoxedTypeVariant)); var property = new PropertyDefinition(unmangledPropertyName, oldProperty.Attributes, signature); @@ -77,12 +77,12 @@ static bool IsDefaultMemberAttributeReal(CustomAttribute attribute) } private static string UnmanglePropertyName(AssemblyRewriteContext assemblyContext, PropertyDefinition prop, - ITypeDefOrRef declaringType, Dictionary countsByBaseName) + TypeDefinition declaringType, Dictionary countsByBaseName) { if (assemblyContext.GlobalContext.Options.PassthroughNames || !prop.Name.IsObfuscated(assemblyContext.GlobalContext.Options)) return prop.Name!; - var baseName = "prop_" + assemblyContext.RewriteTypeRef(prop.Signature!.ReturnType).GetUnmangledName(prop.DeclaringType); + var baseName = "prop_" + assemblyContext.RewriteTypeRef(prop.Signature!.ReturnType, declaringType.GetGenericParameterContext()).GetUnmangledName(prop.DeclaringType); 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 8e16c9e9..3bbc9cf7 100644 --- a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs +++ b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs @@ -40,7 +40,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/Runners/DeobfuscationMapGenerator.cs b/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs index e6a5e28b..a8ec6cae 100644 --- a/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs +++ b/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs @@ -74,14 +74,19 @@ public void Run(GeneratorOptions options) Pass11ComputeTypeSpecifics.DoPass(rewriteContext); } + using (new TimingCookie("Creating Boxed struct types")) + { + Pass12CreateGenericNonBlittableTypes.DoPass(rewriteContext); + } + using (new TimingCookie("Filling typedefs")) { - Pass12FillTypedefs.DoPass(rewriteContext); + Pass13FillTypedefs.DoPass(rewriteContext); } using (new TimingCookie("Filling generic constraints")) { - Pass13FillGenericConstraints.DoPass(rewriteContext); + Pass14FillGenericConstraints.DoPass(rewriteContext); } using (new TimingCookie("Creating members")) diff --git a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs index f3f3efe7..2dc46391 100644 --- a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs +++ b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs @@ -72,14 +72,19 @@ public void Run(GeneratorOptions options) Pass11ComputeTypeSpecifics.DoPass(rewriteContext); } + using (new TimingCookie("Creating unboxed struct types")) + { + Pass12CreateGenericNonBlittableTypes.DoPass(rewriteContext); + } + using (new TimingCookie("Filling typedefs")) { - Pass12FillTypedefs.DoPass(rewriteContext); + Pass13FillTypedefs.DoPass(rewriteContext); } using (new TimingCookie("Filling generic constraints")) { - Pass13FillGenericConstraints.DoPass(rewriteContext); + Pass14FillGenericConstraints.DoPass(rewriteContext); } using (new TimingCookie("Creating members")) @@ -192,6 +197,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/GenericParameterContext.cs b/Il2CppInterop.Generator/Utils/GenericParameterContext.cs new file mode 100644 index 00000000..1e6be987 --- /dev/null +++ b/Il2CppInterop.Generator/Utils/GenericParameterContext.cs @@ -0,0 +1,45 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; + +namespace Il2CppInterop.Generator.Utils; + +public struct GenericParameterContext +{ + public GenericParameterContext(IHasGenericParameters? type, IHasGenericParameters? method) + { + this.Type = type; + this.Method = method; + } + + /// + /// Gets the object responsible for providing current type arguments generic parameters + /// + public IHasGenericParameters? Type { get; } + + /// + /// Gets the object responsible for providing current method generic parameters + /// + public IHasGenericParameters? Method { get; } + + public GenericParameter? GetGenericParameter(GenericParameterSignature signature) + { + IHasGenericParameters? parameterSource; + switch (signature.ParameterType) + { + case GenericParameterType.Type: + parameterSource = Type; + break; + case GenericParameterType.Method: + parameterSource = Method; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (parameterSource == null) return null; + + if (signature.Index >= 0 && signature.Index < parameterSource.GenericParameters.Count) + return parameterSource.GenericParameters[signature.Index]; + throw new ArgumentOutOfRangeException(); + } +} diff --git a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs index fbdfc204..f7da5de1 100644 --- a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs +++ b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs @@ -90,6 +90,8 @@ public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext g public TypeSignature DelegateSupport { get; private set; } public TypeSignature Il2CppException { get; private set; } #nullable enable + public TypeSignature NativeBoolean { get; private set; } + private TypeSignature ResolveType(string typeName) { return allTypes[typeName]; @@ -141,6 +143,8 @@ private void InitTypeRefs() Il2CppException = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime", "Il2CppException").ToTypeSignature(); + NativeBoolean = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime", "NativeBoolean").ToTypeSignature(true); + allTypes["Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase"] = Il2CppObjectBase; allTypes["Il2CppInterop.Runtime.Runtime.Il2CppObjectPool"] = Il2CppObjectPool; allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"] = nonGenericIl2CppArrayBase; @@ -150,6 +154,7 @@ private void InitTypeRefs() 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 6db08110..b141e360 100644 --- a/Il2CppInterop.Runtime/Injection/ClassInjector.cs +++ b/Il2CppInterop.Runtime/Injection/ClassInjector.cs @@ -97,16 +97,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); } @@ -119,7 +123,7 @@ public static void AssignGcHandle(IntPtr pointer, GCHandle gcHandle) } - public static bool IsTypeRegisteredInIl2Cpp() where T : class + public static bool IsTypeRegisteredInIl2Cpp() { return IsTypeRegisteredInIl2Cpp(typeof(T)); } @@ -145,7 +149,7 @@ internal static bool IsManagedTypeInjected(Type type) return false; } - public static void RegisterTypeInIl2Cpp() where T : class + public static void RegisterTypeInIl2Cpp() { RegisterTypeInIl2Cpp(typeof(T)); } @@ -155,7 +159,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); } @@ -181,6 +185,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"); @@ -198,8 +205,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"); @@ -234,6 +241,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); @@ -261,9 +269,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)) @@ -303,8 +320,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; @@ -318,7 +336,7 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) .Where(IsFieldEligible) .ToArray(); - if (!type.IsAbstract) methodPointerArray[1] = ConvertStaticMethod(CreateEmptyCtor(type, fieldsToInitialize), ".ctor", classPointer); + if (!type.IsAbstract && !type.IsValueType) methodPointerArray[1] = ConvertStaticMethod(CreateEmptyCtor(type, fieldsToInitialize), ".ctor", classPointer); var infos = new Dictionary<(string, int, bool), int>(eligibleMethods.Length); for (var i = 0; i < eligibleMethods.Length; i++) { @@ -539,6 +557,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)); + } +}