Skip to content

Commit 717c40a

Browse files
ds5678SamboyCoding
authored andcommitted
Support injecting anything
* Nested types * Events * Properties * Assemblies Support additional metadata in injected assemblies Make setter for TypeAnalysisContext::OverrideBaseType public TypeAnalysisContext::InterfaceContexts as list rather than array Fix sign bug Support generic parameters on type contexts and method contexts Make GenericParameterTypeAnalysisContext instances unique Revert change to Il2CppGenericParameter::Index Use attributes to determine if injected methods are static * Also add hide by sig attribute for injected constructors Support overrides on injected methods In ControlFlowGraphOutputFormat, exclude injected assemblies Ensure injected assemblies can't delete existing assemblies Backing field on .NET Standard for injected method overrides Implement requested change
1 parent d3bbbb3 commit 717c40a

34 files changed

+510
-160
lines changed

Cpp2IL.Core.Tests/AccessibilityExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ private static void AssertNotAccessibleTo(TypeAnalysisContext type1, TypeAnalysi
4343

4444
private static TypeAnalysisContext GetTypeByFullName(AssemblyAnalysisContext assembly, string fullName)
4545
{
46-
return assembly.Types.FirstOrDefault(t => t.FullName == fullName) ?? throw new($"Could not find {fullName} in {assembly.CleanAssemblyName}.");
46+
return assembly.Types.FirstOrDefault(t => t.FullName == fullName) ?? throw new($"Could not find {fullName} in {assembly.Name}.");
4747
}
4848
}

Cpp2IL.Core.Tests/MemberInjectionTests.cs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
using System.Linq;
1+
using System.Linq;
22
using System.Reflection;
3-
using AssetRipper.Primitives;
43

54
namespace Cpp2IL.Core.Tests;
65

@@ -32,7 +31,7 @@ public void TestZeroArgumentMethodInjection()
3231
var baseType = appContext!.SystemTypes.SystemObjectType;
3332
var injectedType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithMethods", baseType);
3433

35-
var methodsByAssembly = injectedType.InjectMethodToAllAssemblies("TestZeroArgMethod", false, appContext.SystemTypes.SystemVoidType, MethodAttributes.Public);
34+
var methodsByAssembly = injectedType.InjectMethodToAllAssemblies("TestZeroArgMethod", appContext.SystemTypes.SystemVoidType, MethodAttributes.Public);
3635

3736
Assert.That(methodsByAssembly, Has.Count.EqualTo(appContext.Assemblies.Count));
3837
Assert.That(methodsByAssembly.Values.First(), Has.Property("Name").EqualTo("TestZeroArgMethod").And.Property("ReturnTypeContext").EqualTo(appContext.SystemTypes.SystemVoidType));
@@ -62,7 +61,7 @@ public void TestMethodWithParametersInjection()
6261
var baseType = appContext!.SystemTypes.SystemObjectType;
6362
var injectedType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithMethodsWithParameters", baseType);
6463

65-
var methodsByAssembly = injectedType.InjectMethodToAllAssemblies("TestMethodWithParameters", false, appContext.SystemTypes.SystemVoidType, MethodAttributes.Public, appContext.SystemTypes.SystemInt32Type, appContext.SystemTypes.SystemStringType);
64+
var methodsByAssembly = injectedType.InjectMethodToAllAssemblies("TestMethodWithParameters", appContext.SystemTypes.SystemVoidType, MethodAttributes.Public, appContext.SystemTypes.SystemInt32Type, appContext.SystemTypes.SystemStringType);
6665

6766
Assert.That(methodsByAssembly, Has.Count.EqualTo(appContext.Assemblies.Count));
6867
Assert.That(methodsByAssembly.Values.First(), Has.Property("Name").EqualTo("TestMethodWithParameters").And.Property("ReturnTypeContext").EqualTo(appContext.SystemTypes.SystemVoidType));
@@ -94,4 +93,78 @@ public void TestFieldInjection()
9493
Assert.That(fieldsByAssembly.Values.First(), Has.Property("Name").EqualTo("TestField").And.Property("FieldTypeContext").EqualTo(appContext.SystemTypes.SystemInt32Type));
9594
Assert.That(fieldsByAssembly.Values.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithFields"));
9695
}
96+
97+
[Test]
98+
public void TestPropertyInjection()
99+
{
100+
var appContext = Cpp2IlApi.CurrentAppContext;
101+
102+
var baseType = appContext!.SystemTypes.SystemObjectType;
103+
var injectedType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithProperties", baseType);
104+
var gettersByAssembly = injectedType.InjectMethodToAllAssemblies("get_TestProperty", appContext.SystemTypes.SystemInt32Type, MethodAttributes.Public);
105+
var propertiesByAssembly = injectedType.InjectPropertyToAllAssemblies("TestProperty", appContext.SystemTypes.SystemInt32Type, gettersByAssembly, null, PropertyAttributes.None);
106+
107+
Assert.That(propertiesByAssembly, Has.Count.EqualTo(appContext.Assemblies.Count));
108+
Assert.That(propertiesByAssembly.Values.First(), Has.Property("Name").EqualTo("TestProperty").And.Property("PropertyTypeContext").EqualTo(appContext.SystemTypes.SystemInt32Type));
109+
Assert.That(propertiesByAssembly.Values.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithProperties"));
110+
}
111+
112+
[Test]
113+
public void TestEventInjection()
114+
{
115+
var appContext = Cpp2IlApi.CurrentAppContext;
116+
117+
var baseType = appContext!.SystemTypes.SystemObjectType;
118+
var injectedType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithEvents", baseType);
119+
var addersByAssembly = injectedType.InjectMethodToAllAssemblies("add_TestEvent", appContext.SystemTypes.SystemInt32Type, MethodAttributes.Public);
120+
var removersByAssembly = injectedType.InjectMethodToAllAssemblies("remove_TestEvent", appContext.SystemTypes.SystemInt32Type, MethodAttributes.Public);
121+
var eventsByAssembly = injectedType.InjectEventToAllAssemblies("TestEvent", appContext.SystemTypes.SystemInt32Type, addersByAssembly, removersByAssembly, null, EventAttributes.None);
122+
123+
Assert.That(eventsByAssembly, Has.Count.EqualTo(appContext.Assemblies.Count));
124+
Assert.That(eventsByAssembly.Values.First(), Has.Property("Name").EqualTo("TestEvent").And.Property("EventTypeContext").EqualTo(appContext.SystemTypes.SystemInt32Type));
125+
Assert.That(eventsByAssembly.Values.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithEvents"));
126+
}
127+
128+
[Test]
129+
public void TestNestedTypeInjection()
130+
{
131+
var appContext = Cpp2IlApi.CurrentAppContext;
132+
133+
var baseType = appContext!.SystemTypes.SystemObjectType;
134+
var declaringType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithEvents", baseType);
135+
var injectedType = declaringType.InjectNestedType("NestedType", baseType);
136+
137+
Assert.That(injectedType.InjectedTypes, Has.Length.EqualTo(appContext.Assemblies.Count));
138+
Assert.That(injectedType.InjectedTypes.First(), Has.Property("Name").EqualTo("NestedType").And.Property("Namespace").EqualTo("").And.Property("BaseType").EqualTo(appContext.SystemTypes.SystemObjectType));
139+
Assert.That(injectedType.InjectedTypes.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithEvents"));
140+
}
141+
142+
[Test]
143+
public void TestNestedTypeInjectionSingle()
144+
{
145+
var appContext = Cpp2IlApi.CurrentAppContext;
146+
147+
var declaringType = appContext!.SystemTypes.SystemObjectType;
148+
var injectedType = declaringType.InjectNestedType("NestedType", null);
149+
150+
using (Assert.EnterMultipleScope())
151+
{
152+
Assert.That(appContext.AllTypes, Contains.Item(injectedType));
153+
Assert.That(declaringType.NestedTypes, Contains.Item(injectedType));
154+
}
155+
}
156+
157+
[Test]
158+
public void TestAssemblyInjection()
159+
{
160+
var appContext = Cpp2IlApi.CurrentAppContext;
161+
162+
var assemblyCount = appContext!.Assemblies.Count;
163+
var injectedAssembly = appContext.InjectAssembly("TestInjectedAssembly");
164+
using (Assert.EnterMultipleScope())
165+
{
166+
Assert.That(appContext.Assemblies, Has.Count.EqualTo(assemblyCount + 1));
167+
Assert.That(appContext.Assemblies, Contains.Item(injectedAssembly));
168+
}
169+
}
97170
}

Cpp2IL.Core/Extensions/AccessibilityExtensions.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static bool IsAccessibleTo(this TypeAnalysisContext referenceType, TypeAn
4444

4545
return true;
4646
}
47-
else if (referenceType.DeclaringAssembly.Definition.IsDependencyOf(referencingType.DeclaringAssembly.Definition))
47+
else if (referenceType.DeclaringAssembly.IsDependencyOf(referencingType.DeclaringAssembly))
4848
{
4949
for (var i = 0; i < declaringTypesHierarchy.Length; i++)
5050
{
@@ -141,6 +141,21 @@ private static bool IsAssignableToInterface(this TypeAnalysisContext derivedType
141141
return false;
142142
}
143143

144+
private static bool IsDependencyOf(this AssemblyAnalysisContext referencedAssembly, AssemblyAnalysisContext referencingAssembly)
145+
{
146+
if (referencingAssembly.Definition is null)
147+
{
148+
// Injected assemblies can access everything
149+
return true;
150+
}
151+
if (referencedAssembly.Definition is null)
152+
{
153+
// Injected assemblies cannot be accessed by metadata assemblies
154+
return false;
155+
}
156+
return referencedAssembly.Definition.IsDependencyOf(referencingAssembly.Definition);
157+
}
158+
144159
private static bool IsDependencyOf(this Il2CppAssemblyDefinition referencedAssembly, Il2CppAssemblyDefinition referencingAssembly)
145160
{
146161
if (Array.IndexOf(referencingAssembly.ReferencedAssemblies, referencedAssembly) >= 0)

Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using AssetRipper.Primitives;
56
using Cpp2IL.Core.Api;
@@ -196,6 +197,22 @@ private void PopulateMethodsByAddressTable()
196197
return ResolveContextForType(propertyDefinition?.DeclaringType)?.Properties.FirstOrDefault(p => p.Definition == propertyDefinition);
197198
}
198199

200+
public GenericParameterTypeAnalysisContext? ResolveContextForGenericParameter(Il2CppGenericParameter? genericParameter)
201+
{
202+
if (genericParameter is null)
203+
return null;
204+
205+
if (genericParameter.Owner.TypeOwner is { } typeOwner)
206+
{
207+
return ResolveContextForType(typeOwner)?.GenericParameters[genericParameter.genericParameterIndexInOwner];
208+
}
209+
else
210+
{
211+
Debug.Assert(genericParameter.Owner.MethodOwner is not null);
212+
return ResolveContextForMethod(genericParameter.Owner.MethodOwner)?.GenericParameters[genericParameter.genericParameterIndexInOwner];
213+
}
214+
}
215+
199216
public BaseKeyFunctionAddresses GetOrCreateKeyFunctionAddresses()
200217
{
201218
lock (InstructionSet)
@@ -214,5 +231,20 @@ public MultiAssemblyInjectedType InjectTypeIntoAllAssemblies(string ns, string n
214231
return new(types);
215232
}
216233

234+
public InjectedAssemblyAnalysisContext InjectAssembly(
235+
string name,
236+
Version? version = null,
237+
uint hashAlgorithm = 0,
238+
uint flags = 0,
239+
string? culture = null,
240+
byte[]? publicKeyToken = null,
241+
byte[]? publicKey = null)
242+
{
243+
var assembly = new InjectedAssemblyAnalysisContext(name, this, version, hashAlgorithm, flags, culture, publicKeyToken, publicKey);
244+
Assemblies.Add(assembly);
245+
AssembliesByName.Add(name, assembly);
246+
return assembly;
247+
}
248+
217249
public IEnumerable<TypeAnalysisContext> AllTypes => Assemblies.SelectMany(a => a.Types);
218250
}

Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using System.Reflection;
@@ -11,12 +12,12 @@ namespace Cpp2IL.Core.Model.Contexts;
1112
/// <summary>
1213
/// Represents a single Assembly that was converted using IL2CPP.
1314
/// </summary>
14-
public class AssemblyAnalysisContext : HasCustomAttributes
15+
public class AssemblyAnalysisContext : HasCustomAttributesAndName
1516
{
1617
/// <summary>
1718
/// The raw assembly metadata, such as its name, version, etc.
1819
/// </summary>
19-
public Il2CppAssemblyDefinition Definition;
20+
public Il2CppAssemblyDefinition? Definition { get; set; }
2021

2122
/// <summary>
2223
/// The analysis context objects for all types contained within the assembly, including those nested within a parent type.
@@ -35,23 +36,60 @@ public class AssemblyAnalysisContext : HasCustomAttributes
3536
/// </summary>
3637
public Il2CppCodeGenModule? CodeGenModule;
3738

38-
protected override int CustomAttributeIndex => Definition.CustomAttributeIndex;
39+
public virtual Version Version
40+
{
41+
get
42+
{
43+
//handle __Generated assembly on v29, which has a version of 0.0.-1.-1
44+
return Definition is null || Definition.AssemblyName.build < 0
45+
? new(0,0,0,0)
46+
: new(Definition.AssemblyName.major, Definition.AssemblyName.minor, Definition.AssemblyName.build, Definition.AssemblyName.revision);
47+
}
48+
}
3949

40-
public override AssemblyAnalysisContext CustomAttributeAssembly => this;
50+
public virtual uint HashAlgorithm => Definition?.AssemblyName.hash_alg ?? default;
51+
52+
public virtual uint Flags => Definition?.AssemblyName.flags ?? default;
4153

42-
public override string CustomAttributeOwnerName => Definition.AssemblyName.Name;
54+
public virtual string? Culture => Definition?.AssemblyName.Culture;
55+
56+
public virtual byte[]? PublicKeyToken => Definition?.AssemblyName.PublicKeyToken;
57+
58+
public virtual byte[]? PublicKey => Definition?.AssemblyName.PublicKey;
59+
60+
protected override int CustomAttributeIndex => Definition?.CustomAttributeIndex ?? -1;
61+
62+
public override AssemblyAnalysisContext CustomAttributeAssembly => this;
4363

4464
private readonly Dictionary<string, TypeAnalysisContext> TypesByName = new();
4565

4666
private readonly Dictionary<Il2CppTypeDefinition, TypeAnalysisContext> TypesByDefinition = new();
4767

68+
public override string DefaultName => Definition?.AssemblyName.Name ?? throw new($"Injected assemblies should override {nameof(DefaultName)}");
69+
70+
protected override bool IsInjected => Definition is null;
71+
4872
/// <summary>
4973
/// Get assembly name without the extension and with any invalid path characters or elements removed.
5074
/// </summary>
51-
public string CleanAssemblyName => MiscUtils.CleanPathElement(Definition.AssemblyName.Name);
75+
public string CleanAssemblyName => MiscUtils.CleanPathElement(Name);
76+
77+
public string ModuleName
78+
{
79+
get
80+
{
81+
var moduleName = Definition?.Image.Name ?? Name;
82+
if (moduleName == "__Generated")
83+
moduleName += ".dll"; //__Generated doesn't have a .dll extension in the metadata but it is still of course a DLL
84+
return moduleName;
85+
}
86+
}
5287

53-
public AssemblyAnalysisContext(Il2CppAssemblyDefinition assemblyDefinition, ApplicationAnalysisContext appContext) : base(assemblyDefinition.Token, appContext)
88+
public AssemblyAnalysisContext(Il2CppAssemblyDefinition? assemblyDefinition, ApplicationAnalysisContext appContext) : base(assemblyDefinition?.Token ?? 0, appContext)
5489
{
90+
if (assemblyDefinition is null)
91+
return;
92+
5593
Definition = assemblyDefinition;
5694

5795
if (AppContext.MetadataVersion >= 24.2f)
@@ -80,14 +118,20 @@ public AssemblyAnalysisContext(Il2CppAssemblyDefinition assemblyDefinition, Appl
80118

81119
public TypeAnalysisContext InjectType(string ns, string name, TypeAnalysisContext? baseType, TypeAttributes typeAttributes = TypeAnalysisContext.DefaultTypeAttributes)
82120
{
83-
var ret = new InjectedTypeAnalysisContext(this, name, ns, baseType, typeAttributes);
84-
Types.Add(ret);
121+
var ret = new InjectedTypeAnalysisContext(this, ns, name, baseType, typeAttributes);
122+
InjectType(ret);
85123
return ret;
86124
}
87125

126+
internal void InjectType(InjectedTypeAnalysisContext ret)
127+
{
128+
Types.Add(ret);
129+
TypesByName[ret.FullName] = ret;
130+
}
131+
88132
public TypeAnalysisContext? GetTypeByFullName(string fullName) => TypesByName.TryGetValue(fullName, out var typeContext) ? typeContext : null;
89133

90134
public TypeAnalysisContext? GetTypeByDefinition(Il2CppTypeDefinition typeDefinition) => TypesByDefinition.TryGetValue(typeDefinition, out var typeContext) ? typeContext : null;
91135

92-
public override string ToString() => "Assembly: " + Definition.AssemblyName.Name;
136+
public override string ToString() => "Assembly: " + Name;
93137
}

Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ private ConcreteGenericMethodAnalysisContext(Cpp2IlMethodRef? methodRef, MethodA
102102

103103
// For the purpose of generic instantiation, we need an array of method generic parameters, even if none are provided.
104104
if (methodGenericParameters.Length == 0 && baseMethodContext.GenericParameterCount > 0)
105-
methodGenericParameters = Enumerable.Range(0, baseMethodContext.GenericParameterCount)
106-
.Select(i => new GenericParameterTypeAnalysisContext("T", i, Il2CppTypeEnum.IL2CPP_TYPE_MVAR, declaringAssembly))
107-
.ToArray();
105+
methodGenericParameters = baseMethodContext.GenericParameters.ToArray();
108106

109107
for (var i = 0; i < BaseMethodContext.Parameters.Count; i++)
110108
{
Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Reflection;
23
using Cpp2IL.Core.Utils;
34
using LibCpp2IL.Metadata;
@@ -8,22 +9,22 @@ namespace Cpp2IL.Core.Model.Contexts;
89
public class EventAnalysisContext : HasCustomAttributesAndName, IEventInfoProvider
910
{
1011
public readonly TypeAnalysisContext DeclaringType;
11-
public readonly Il2CppEventDefinition Definition;
12+
public readonly Il2CppEventDefinition? Definition;
1213
public readonly MethodAnalysisContext? Adder;
1314
public readonly MethodAnalysisContext? Remover;
1415
public readonly MethodAnalysisContext? Invoker;
1516

16-
protected override int CustomAttributeIndex => Definition.customAttributeIndex;
17+
protected override int CustomAttributeIndex => Definition?.customAttributeIndex ?? -1;
1718

1819
public override AssemblyAnalysisContext CustomAttributeAssembly => DeclaringType.DeclaringAssembly;
1920

20-
public override string DefaultName => Definition.Name!;
21+
public override string DefaultName => Definition?.Name ?? throw new($"Subclasses must override {nameof(DefaultName)}.");
2122

22-
public EventAttributes EventAttributes => Definition.EventAttributes;
23+
public virtual EventAttributes EventAttributes => Definition?.EventAttributes ?? throw new($"Subclasses must override {nameof(EventAttributes)}.");
2324

24-
public TypeAnalysisContext EventTypeContext => DeclaringType.DeclaringAssembly.ResolveIl2CppType(Definition.RawType!);
25+
public virtual TypeAnalysisContext EventTypeContext => DeclaringType.DeclaringAssembly.ResolveIl2CppType(Definition?.RawType) ?? throw new($"Subclasses must override {nameof(EventAttributes)}.");
2526

26-
public bool IsStatic => Definition.IsStatic;
27+
public virtual bool IsStatic => Definition?.IsStatic ?? throw new($"Subclasses must override {nameof(IsStatic)}.");
2728

2829
public EventAnalysisContext(Il2CppEventDefinition definition, TypeAnalysisContext parent) : base(definition.token, parent.AppContext)
2930
{
@@ -37,15 +38,26 @@ public EventAnalysisContext(Il2CppEventDefinition definition, TypeAnalysisContex
3738
Invoker = parent.GetMethod(definition.Invoker);
3839
}
3940

40-
public override string ToString() => $"Event: {Definition.DeclaringType!.Name}::{Definition.Name}";
41+
protected EventAnalysisContext(MethodAnalysisContext? adder, MethodAnalysisContext? remover, MethodAnalysisContext? invoker, TypeAnalysisContext parent) : base(0, parent.AppContext)
42+
{
43+
if (adder is null && remover is null && invoker is null)
44+
throw new ArgumentException("Event must have at least one method");
45+
46+
DeclaringType = parent;
47+
Adder = adder;
48+
Remover = remover;
49+
Invoker = invoker;
50+
}
51+
52+
public override string ToString() => $"Event: {DeclaringType.Name}::{Name}";
4153

4254
#region StableNameDotNet Impl
4355

44-
public ITypeInfoProvider EventTypeInfoProvider => Definition.RawType!.ThisOrElementIsGenericParam()
56+
public ITypeInfoProvider EventTypeInfoProvider => Definition!.RawType!.ThisOrElementIsGenericParam()
4557
? new GenericParameterTypeInfoProviderWrapper(Definition.RawType!.GetGenericParamName())
4658
: TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition.RawType!);
4759

48-
public string EventName => DefaultName;
60+
public string EventName => Name;
4961

5062
#endregion
5163
}

0 commit comments

Comments
 (0)