From 87fab807fc86ec9a31a968af384c9ca89f04d909 Mon Sep 17 00:00:00 2001 From: acan <1980728690@qq.com> Date: Fri, 29 May 2026 10:15:45 +0800 Subject: [PATCH 1/2] Fix duplicate types in generated assemblies on Unity 6.4+ - Use unified type name conversion (MakeValidInSource) for all unstripped types - Look up types by FullName consistently instead of simple name - Skip nested types when parent type is not in the output assembly - Add null guard for Resolve() in HasNonBlittableFields recursion --- .../Passes/Pass79UnstripTypes.cs | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs index 739d58a9..abaae9e8 100644 --- a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs +++ b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs @@ -47,15 +47,20 @@ private static void ProcessType(AssemblyRewriteContext processedAssembly, TypeDe if (unityType.BaseType != null && unityType.BaseType.FullName == "System.MulticastDelegate") return; var newModule = processedAssembly.NewAssembly.ManifestModule!; - var processedType = enclosingNewType == null - ? processedAssembly.TryGetTypeByName(unityType.FullName)?.NewType - : enclosingNewType.NestedTypes.SingleOrDefault(it => it.Name == unityType.Name); + var processedType = processedAssembly.TryGetTypeByName(unityType.FullName)?.NewType; + var convertedTypeName = GetConvertedUnityTypeName(processedAssembly.GlobalContext, unityType); + + // If the parent type does not exist in the rewritten assembly, this nested type cannot be emitted safely. + // Promoting it to a top-level type creates orphan compiler-generated types such as __O/__c. + if (unityType.DeclaringType != null && enclosingNewType == null && processedType == null) + return; + if (unityType.IsEnum) { if (processedType != null) return; typesUnstripped++; - var clonedType = CloneEnum(unityType, imports); + var clonedType = CloneEnum(unityType, convertedTypeName, imports); if (enclosingNewType == null) { newModule.TopLevelTypes.Add(clonedType); @@ -74,7 +79,8 @@ private static void ProcessType(AssemblyRewriteContext processedAssembly, TypeDe !unityType.HasGenericParameters()) // restore all types even if it would be not entirely correct { typesUnstripped++; - var clonedType = new TypeDefinition(unityType.Namespace, unityType.Name, ForcePublic(unityType.Attributes), unityType.BaseType == null ? null : newModule.DefaultImporter.ImportType(unityType.BaseType)); + var clonedType = new TypeDefinition(unityType.Namespace, convertedTypeName, ForcePublic(unityType.Attributes), + unityType.BaseType == null ? null : newModule.DefaultImporter.ImportType(unityType.BaseType)); if (enclosingNewType == null) { newModule.TopLevelTypes.Add(clonedType); @@ -100,9 +106,9 @@ private static void ProcessType(AssemblyRewriteContext processedAssembly, TypeDe ProcessType(processedAssembly, nestedUnityType, processedType, imports, ref typesUnstripped); } - private static TypeDefinition CloneEnum(TypeDefinition sourceEnum, RuntimeAssemblyReferences imports) + private static TypeDefinition CloneEnum(TypeDefinition sourceEnum, string convertedTypeName, RuntimeAssemblyReferences imports) { - var newType = new TypeDefinition(sourceEnum.Namespace, sourceEnum.Name, ForcePublic(sourceEnum.Attributes), + var newType = new TypeDefinition(sourceEnum.Namespace, convertedTypeName, ForcePublic(sourceEnum.Attributes), imports.Module.Enum().ToTypeDefOrRef()); foreach (var sourceEnumField in sourceEnum.Fields) { @@ -130,14 +136,25 @@ private static bool HasNonBlittableFields(TypeDefinition type) if (!fieldDefinition.Signature!.FieldType.IsValueType()) return true; - if (fieldDefinition.Signature.FieldType.Namespace?.StartsWith("System") ?? false && - HasNonBlittableFields(fieldDefinition.Signature.FieldType.Resolve())) - return true; + if (fieldDefinition.Signature.FieldType.Namespace?.StartsWith("System") ?? false) + { + var resolved = fieldDefinition.Signature.FieldType.Resolve(); + if (resolved != null && HasNonBlittableFields(resolved)) + return true; + } } return false; } + private static string GetConvertedUnityTypeName(RewriteGlobalContext context, TypeDefinition unityType) + { + if (context.Options.PassthroughNames) + return unityType.Name; + + return unityType.Name.MakeValidInSource(); + } + private static TypeAttributes ForcePublic(TypeAttributes typeAttributes) { var visibility = typeAttributes & TypeAttributes.VisibilityMask; From 801a7aef216da33bfb53a832e78d2912045a05c8 Mon Sep 17 00:00:00 2001 From: acan <61283830+acan1980728690@users.noreply.github.com> Date: Fri, 29 May 2026 13:18:33 +0800 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> --- Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs index abaae9e8..9258bddf 100644 --- a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs +++ b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs @@ -1,3 +1,4 @@ +using AsmResolver; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; @@ -52,7 +53,7 @@ private static void ProcessType(AssemblyRewriteContext processedAssembly, TypeDe // If the parent type does not exist in the rewritten assembly, this nested type cannot be emitted safely. // Promoting it to a top-level type creates orphan compiler-generated types such as __O/__c. - if (unityType.DeclaringType != null && enclosingNewType == null && processedType == null) + if ((unityType.DeclaringType is null) != (enclosingNewType is null)) return; if (unityType.IsEnum) @@ -106,7 +107,7 @@ private static void ProcessType(AssemblyRewriteContext processedAssembly, TypeDe ProcessType(processedAssembly, nestedUnityType, processedType, imports, ref typesUnstripped); } - private static TypeDefinition CloneEnum(TypeDefinition sourceEnum, string convertedTypeName, RuntimeAssemblyReferences imports) + private static TypeDefinition CloneEnum(TypeDefinition sourceEnum, Utf8String convertedTypeName, RuntimeAssemblyReferences imports) { var newType = new TypeDefinition(sourceEnum.Namespace, convertedTypeName, ForcePublic(sourceEnum.Attributes), imports.Module.Enum().ToTypeDefOrRef()); @@ -147,10 +148,10 @@ private static bool HasNonBlittableFields(TypeDefinition type) return false; } - private static string GetConvertedUnityTypeName(RewriteGlobalContext context, TypeDefinition unityType) + private static Utf8String GetConvertedUnityTypeName(RewriteGlobalContext context, TypeDefinition unityType) { if (context.Options.PassthroughNames) - return unityType.Name; + return unityType.Name ?? Utf8String.Empty; return unityType.Name.MakeValidInSource(); }