Skip to content

Commit 0354124

Browse files
committed
Fix HybridCLR generator gating
1 parent fb0b0f4 commit 0354124

8 files changed

Lines changed: 151 additions & 115 deletions

File tree

Il2CppInterop.CLI/Program.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
new Option<bool>("--passthrough-names",
3636
"If specified, names will be copied from input assemblies as-is without renaming or deobfuscation."),
3737
new Option<bool>("--no-parallel", "Disable parallel processing when writing assemblies. Use if you encounter stability issues when generating assemblies."),
38+
new Option<bool>("--hybridclr", "Enable HybridCLR-specific metadata compatibility handling."),
39+
new Option<DirectoryInfo>("--existing-interop", "Directory with existing interop assemblies to reference for incremental generation.").ExistingOnly(),
40+
new Option<bool>("--no-skip-existing", "Generate assemblies even when they already exist in --existing-interop."),
3841
};
3942
generateCommand.Description = "Generate wrapper assemblies that can be used to interop with Il2Cpp";
4043
generateCommand.Handler = CommandHandler.Create((GenerateCommandOptions opts) =>
@@ -169,7 +172,10 @@ internal record GenerateCommandOptions(
169172
string[]? BlacklistAssembly,
170173
Regex? ObfRegex,
171174
bool PassthroughNames,
172-
bool NoParallel
175+
bool NoParallel,
176+
bool HybridCLR,
177+
DirectoryInfo? ExistingInterop,
178+
bool NoSkipExisting
173179
) : BaseCmdOptions(Verbose)
174180
{
175181
public override CmdOptionsResult Build()
@@ -188,6 +194,9 @@ public override CmdOptionsResult Build()
188194
opts.ObfuscatedNamesRegex = ObfRegex;
189195
opts.PassthroughNames = PassthroughNames;
190196
opts.Parallel = !NoParallel;
197+
opts.IsHybridCLREnvironment = HybridCLR;
198+
opts.ExistingInteropDir = ExistingInterop?.FullName;
199+
opts.SkipExistingAssemblies = !NoSkipExisting;
191200

192201
if (AddPrefixTo is not null && AddPrefixTo.Length > 0)
193202
{

Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,6 @@ public TypeRewriteContext GetContextForNewType(TypeDefinition type)
5151
return myNewTypeMap[type];
5252
}
5353

54-
public TypeRewriteContext? TryGetContextForNewType(TypeDefinition type)
55-
{
56-
return myNewTypeMap.TryGetValue(type, out var result) ? result : null;
57-
}
58-
5954
public void RegisterTypeRewrite(TypeRewriteContext context)
6055
{
6156
if (context.OriginalType != null)
@@ -79,19 +74,49 @@ public void RegisterTypeByAlternativeName(string alternativeName, TypeRewriteCon
7974

8075
public IMethodDefOrRef? RewriteMethodRef(IMethodDefOrRef? methodRef)
8176
{
82-
if (methodRef?.DeclaringType == null) return null;
77+
if (methodRef?.DeclaringType == null)
78+
{
79+
if (GlobalContext.Options.IsHybridCLREnvironment)
80+
return null;
81+
82+
throw new ArgumentNullException(nameof(methodRef));
83+
}
8384

8485
var declaringType = methodRef.DeclaringType.Resolve();
85-
if (declaringType == null) return null;
86+
if (declaringType == null)
87+
{
88+
if (GlobalContext.Options.IsHybridCLREnvironment)
89+
return null;
90+
91+
throw new($"Could not resolve declaring type {methodRef.DeclaringType.FullName} for method {methodRef.Name}");
92+
}
8693

8794
var newType = GlobalContext.GetNewTypeForOriginal(declaringType);
88-
if (newType == null) return null;
95+
if (newType == null)
96+
{
97+
if (GlobalContext.Options.IsHybridCLREnvironment)
98+
return null;
99+
100+
throw new($"Could not find rewrite context for declaring type {declaringType.FullName}");
101+
}
89102

90103
var resolvedMethod = methodRef.Resolve();
91-
if (resolvedMethod == null) return null;
104+
if (resolvedMethod == null)
105+
{
106+
if (GlobalContext.Options.IsHybridCLREnvironment)
107+
return null;
108+
109+
throw new($"Could not resolve method {methodRef.FullName}");
110+
}
92111

93112
var methodContext = newType.TryGetMethodByOldMethod(resolvedMethod);
94-
if (methodContext == null) return null;
113+
if (methodContext == null)
114+
{
115+
if (GlobalContext.Options.IsHybridCLREnvironment)
116+
return null;
117+
118+
throw new($"Could not find rewrite context for method {resolvedMethod.FullName}");
119+
}
95120

96121
return NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(methodContext.NewMethod);
97122
}
@@ -161,6 +186,8 @@ public TypeSignature RewriteTypeRef(TypeSignature? typeRef)
161186
var mscorlib = GlobalContext.TryGetAssemblyByName("mscorlib");
162187
if (mscorlib != null)
163188
return sourceModule.DefaultImporter.ImportType(mscorlib.GetTypeByName("System.Object").NewType).ToTypeSignature();
189+
if (!GlobalContext.Options.IsHybridCLREnvironment)
190+
throw new KeyNotFoundException("Required corlib type 'System.Object' was not found.");
164191
return sourceModule.CorLibTypeFactory.Object;
165192
}
166193

@@ -169,13 +196,17 @@ public TypeSignature RewriteTypeRef(TypeSignature? typeRef)
169196
var mscorlib = GlobalContext.TryGetAssemblyByName("mscorlib");
170197
if (mscorlib != null)
171198
return sourceModule.DefaultImporter.ImportType(mscorlib.GetTypeByName("System.Attribute").NewType).ToTypeSignature();
199+
if (!GlobalContext.Options.IsHybridCLREnvironment)
200+
throw new KeyNotFoundException("Required corlib type 'System.Attribute' was not found.");
172201
return sourceModule.ImportCorlibReference("System.Attribute");
173202
}
174203

175204
var originalTypeDef = typeRef.Resolve();
176205
if (originalTypeDef == null)
177206
{
178-
// Cannot resolve type - return Object as fallback
207+
if (!GlobalContext.Options.IsHybridCLREnvironment)
208+
throw new($"Could not resolve type {typeRef.FullName}");
209+
179210
var mscorlib = GlobalContext.TryGetAssemblyByName("mscorlib");
180211
if (mscorlib != null)
181212
return sourceModule.DefaultImporter.ImportType(mscorlib.GetTypeByName("System.Object").NewType).ToTypeSignature();
@@ -194,7 +225,10 @@ public TypeSignature RewriteTypeRef(TypeSignature? typeRef)
194225
}
195226
if (targetAssembly == null)
196227
{
197-
// Assembly not found - return Object as fallback
228+
if (!GlobalContext.Options.IsHybridCLREnvironment)
229+
throw new KeyNotFoundException(
230+
$"Could not find target assembly for type {originalTypeDef.FullName} from assembly {originalTypeDef.DeclaringModule?.Assembly?.Name}");
231+
198232
var mscorlib = GlobalContext.TryGetAssemblyByName("mscorlib");
199233
if (mscorlib != null)
200234
return sourceModule.DefaultImporter.ImportType(mscorlib.GetTypeByName("System.Object").NewType).ToTypeSignature();
@@ -212,7 +246,10 @@ public TypeSignature RewriteTypeRef(TypeSignature? typeRef)
212246
}
213247
if (typeContext == null)
214248
{
215-
// Type context not found - return Object as fallback
249+
if (!GlobalContext.Options.IsHybridCLREnvironment)
250+
throw new KeyNotFoundException(
251+
$"Could not find rewrite context for type {originalTypeDef.FullName} in assembly {targetAssembly.NewAssembly.Name}");
252+
216253
var mscorlib = GlobalContext.TryGetAssemblyByName("mscorlib");
217254
if (mscorlib != null)
218255
return sourceModule.DefaultImporter.ImportType(mscorlib.GetTypeByName("System.Object").NewType).ToTypeSignature();

Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ public RewriteGlobalContext(GeneratorOptions options, IIl2CppMetadataAccess game
5353
var newModule = new ModuleDefinition(sourceAssembly.ManifestModule?.Name.UnSystemify(options), CorlibReferences.TargetCorlib);
5454
newAssembly.Modules.Add(newModule);
5555

56-
// Use HybridCLR-aware metadata resolver that handles type relocation
57-
newModule.MetadataResolver = new HybridCLRMetadataResolver(assemblyResolver);
56+
newModule.MetadataResolver = options.IsHybridCLREnvironment
57+
? new HybridCLRMetadataResolver(assemblyResolver)
58+
: new DefaultMetadataResolver(assemblyResolver);
5859
assemblyResolver.AddToCache(newAssembly);
5960

6061
var assemblyRewriteContext = new AssemblyRewriteContext(this, sourceAssembly, newAssembly);
@@ -210,11 +211,21 @@ or GenericParameterSignature
210211

211212
var resolved = typeRef.Resolve();
212213
if (resolved == null)
213-
return TypeRewriteContext.TypeSpecifics.ReferenceType; // Fallback for unresolvable types
214+
{
215+
if (Options.IsHybridCLREnvironment)
216+
return TypeRewriteContext.TypeSpecifics.ReferenceType;
217+
218+
throw new($"Could not resolve {typeRef.FullName}");
219+
}
214220

215221
var fieldTypeContext = GetNewTypeForOriginal(resolved);
216222
if (fieldTypeContext == null)
217-
return TypeRewriteContext.TypeSpecifics.ReferenceType; // Fallback for missing types
223+
{
224+
if (Options.IsHybridCLREnvironment)
225+
return TypeRewriteContext.TypeSpecifics.ReferenceType;
226+
227+
throw new($"Could not find rewrite context for {resolved.FullName}");
228+
}
218229

219230
return fieldTypeContext.ComputedTypeSpecifics;
220231
}
Lines changed: 62 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,62 @@
1-
using System.Collections.Concurrent;
2-
using AsmResolver.DotNet;
3-
using AsmResolver.IO;
4-
5-
namespace Il2CppInterop.Generator.MetadataAccess;
6-
7-
/// <summary>
8-
/// Custom assembly resolver for IL2CPP/HybridCLR environments.
9-
/// Handles signature mismatches between hotfix DLLs and AOT DLLs by ignoring public key token comparison.
10-
/// </summary>
11-
internal sealed class Il2CppAssemblyResolver : AssemblyResolverBase
12-
{
13-
// Cache for fast assembly lookup by name (ignoring signature)
14-
private readonly ConcurrentDictionary<string, AssemblyDefinition> _assemblyCache = new();
15-
16-
public Il2CppAssemblyResolver() : base(new ByteArrayFileService())
17-
{
18-
}
19-
20-
protected override string? ProbeRuntimeDirectories(AssemblyDescriptor assembly) => null;
21-
22-
public void AddToCache(AssemblyDefinition assembly)
23-
{
24-
// Add to base cache with full signature
25-
AddToCache(assembly, assembly);
26-
27-
// Also add to our name-only cache for signature-ignoring lookups
28-
if (!string.IsNullOrEmpty(assembly.Name))
29-
{
30-
_assemblyCache.TryAdd(assembly.Name!, assembly);
31-
32-
// For Il2Cpp-prefixed assemblies, also register under the original name
33-
// so hotfix DLLs referencing "mscorlib" can resolve to "Il2Cppmscorlib"
34-
if (assembly.Name!.Value.StartsWith("Il2Cpp"))
35-
{
36-
var originalName = assembly.Name.Value.Substring(6);
37-
_assemblyCache.TryAdd(originalName, assembly);
38-
}
39-
}
40-
}
41-
42-
/// <summary>
43-
/// Resolves an assembly, ignoring public key token mismatches.
44-
/// This is necessary because IL2CPP remaps DLL signatures differently than the original assemblies.
45-
/// Uses 'new' to hide base method since AssemblyResolverBase.Resolve is not virtual.
46-
/// </summary>
47-
public new AssemblyDefinition? Resolve(AssemblyDescriptor assembly)
48-
{
49-
// First try the standard resolution
50-
var result = base.Resolve(assembly);
51-
if (result != null)
52-
return result;
53-
54-
// If standard resolution failed, try name-only lookup (ignoring signature)
55-
if (!string.IsNullOrEmpty(assembly.Name) && _assemblyCache.TryGetValue(assembly.Name!, out var cached))
56-
{
57-
return cached;
58-
}
59-
60-
return null;
61-
}
62-
}
1+
using System.Collections.Concurrent;
2+
using AsmResolver.DotNet;
3+
using AsmResolver.IO;
4+
5+
namespace Il2CppInterop.Generator.MetadataAccess;
6+
7+
/// <summary>
8+
/// Custom assembly resolver for IL2CPP/HybridCLR environments.
9+
/// Handles signature mismatches between hotfix DLLs and AOT DLLs by ignoring public key token comparison.
10+
/// </summary>
11+
internal sealed class Il2CppAssemblyResolver : AssemblyResolverBase
12+
{
13+
// Cache for fast assembly lookup by name (ignoring signature)
14+
private readonly ConcurrentDictionary<string, AssemblyDefinition> _assemblyCache = new();
15+
16+
public Il2CppAssemblyResolver() : base(new ByteArrayFileService())
17+
{
18+
}
19+
20+
protected override string? ProbeRuntimeDirectories(AssemblyDescriptor assembly) => null;
21+
22+
public void AddToCache(AssemblyDefinition assembly)
23+
{
24+
// Add to base cache with full signature
25+
AddToCache(assembly, assembly);
26+
27+
// Also add to our name-only cache for signature-ignoring lookups
28+
if (!string.IsNullOrEmpty(assembly.Name))
29+
{
30+
_assemblyCache.TryAdd(assembly.Name!, assembly);
31+
32+
// For Il2Cpp-prefixed assemblies, also register under the original name
33+
// so hotfix DLLs referencing "mscorlib" can resolve to "Il2Cppmscorlib"
34+
if (assembly.Name!.Value.StartsWith("Il2Cpp"))
35+
{
36+
var originalName = assembly.Name.Value.Substring(6);
37+
_assemblyCache.TryAdd(originalName, assembly);
38+
}
39+
}
40+
}
41+
42+
/// <summary>
43+
/// Resolves an assembly, ignoring public key token mismatches.
44+
/// This is necessary because IL2CPP remaps DLL signatures differently than the original assemblies.
45+
/// Uses 'new' to hide base method since AssemblyResolverBase.Resolve is not virtual.
46+
/// </summary>
47+
public new AssemblyDefinition? Resolve(AssemblyDescriptor assembly)
48+
{
49+
// First try the standard resolution
50+
var result = base.Resolve(assembly);
51+
if (result != null)
52+
return result;
53+
54+
// If standard resolution failed, try name-only lookup (ignoring signature)
55+
if (!string.IsNullOrEmpty(assembly.Name) && _assemblyCache.TryGetValue(assembly.Name!, out var cached))
56+
{
57+
return cached;
58+
}
59+
60+
return null;
61+
}
62+
}

Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,19 @@ private static void ComputeSpecifics(TypeRewriteContext typeContext)
4242
var resolvedFieldType = fieldType.Resolve();
4343
if (resolvedFieldType == null)
4444
{
45-
// Cannot resolve field type - treat as non-blittable
45+
if (!typeContext.AssemblyContext.GlobalContext.Options.IsHybridCLREnvironment)
46+
throw new($"Could not resolve {fieldType.FullName}");
47+
4648
typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct;
4749
return;
4850
}
4951

5052
var fieldTypeContext = typeContext.AssemblyContext.GlobalContext.GetNewTypeForOriginal(resolvedFieldType);
5153
if (fieldTypeContext == null)
5254
{
53-
// Type not found in rewrite context - treat as non-blittable
55+
if (!typeContext.AssemblyContext.GlobalContext.Options.IsHybridCLREnvironment)
56+
throw new($"Could not find rewrite context for {resolvedFieldType.FullName}");
57+
5458
typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct;
5559
return;
5660
}

Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,28 @@ public void Run(GeneratorOptions options)
6060
using (new TimingCookie("Reading assemblies"))
6161
{
6262
gameAssemblies = new AssemblyMetadataAccess(sourceAssemblies,
63-
isHybridCLREnvironment: !string.IsNullOrEmpty(options.ExistingInteropDir));
63+
isHybridCLREnvironment: options.IsHybridCLREnvironment);
6464
}
6565

6666
// Load reference assemblies: UnityBaseLibsDir or ExistingInteropDir
6767
if (!string.IsNullOrEmpty(options.UnityBaseLibsDir))
6868
using (new TimingCookie("Reading unity assemblies"))
6969
{
70-
unityAssemblies = new AssemblyMetadataAccess(Directory.EnumerateFiles(options.UnityBaseLibsDir, "*.dll"));
70+
unityAssemblies = new AssemblyMetadataAccess(
71+
Directory.EnumerateFiles(options.UnityBaseLibsDir, "*.dll"),
72+
isHybridCLREnvironment: options.IsHybridCLREnvironment);
7173
}
7274
else if (!string.IsNullOrEmpty(options.ExistingInteropDir))
7375
using (new TimingCookie("Reading existing interop assemblies"))
7476
{
75-
unityAssemblies = new AssemblyMetadataAccess(Directory.EnumerateFiles(options.ExistingInteropDir, "*.dll"));
77+
unityAssemblies = new AssemblyMetadataAccess(
78+
Directory.EnumerateFiles(options.ExistingInteropDir, "*.dll"),
79+
isHybridCLREnvironment: options.IsHybridCLREnvironment);
7680
}
7781
else
7882
unityAssemblies = NullMetadataAccess.Instance;
7983

80-
// In HybridCLR mode, source assemblies need to resolve types from reference assemblies
84+
// Incremental/reference mode lets source assemblies resolve types from existing interop assemblies.
8185
if (!string.IsNullOrEmpty(options.ExistingInteropDir) && unityAssemblies is AssemblyMetadataAccess refAccess)
8286
((AssemblyMetadataAccess)gameAssemblies).AddReferenceAssemblies(refAccess.Assemblies);
8387

Il2CppInterop.Runtime/IL2CPP.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ public static void RefreshImageCache()
5959
}
6060
}
6161

62-
/// <summary>
63-
/// Alias for RefreshImageCache - updates the image map with newly loaded HybridCLR hotfix assemblies.
64-
/// </summary>
65-
public static void UpdateHotfixIl2CppImage() => RefreshImageCache();
66-
6762
internal static IntPtr GetIl2CppImage(string name)
6863
{
6964
if (ourImagesMap.ContainsKey(name)) return ourImagesMap[name];

0 commit comments

Comments
 (0)