Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cpp2IL.Core.Tests/AccessibilityExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ private static void AssertNotAccessibleTo(TypeAnalysisContext type1, TypeAnalysi

private static TypeAnalysisContext GetTypeByFullName(AssemblyAnalysisContext assembly, string fullName)
{
return assembly.Types.FirstOrDefault(t => t.FullName == fullName) ?? throw new($"Could not find {fullName} in {assembly.CleanAssemblyName}.");
return assembly.Types.FirstOrDefault(t => t.FullName == fullName) ?? throw new($"Could not find {fullName} in {assembly.Name}.");
}
}
81 changes: 77 additions & 4 deletions Cpp2IL.Core.Tests/MemberInjectionTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Linq;
using System.Linq;
using System.Reflection;
using AssetRipper.Primitives;

namespace Cpp2IL.Core.Tests;

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

var methodsByAssembly = injectedType.InjectMethodToAllAssemblies("TestZeroArgMethod", false, appContext.SystemTypes.SystemVoidType, MethodAttributes.Public);
var methodsByAssembly = injectedType.InjectMethodToAllAssemblies("TestZeroArgMethod", appContext.SystemTypes.SystemVoidType, MethodAttributes.Public);

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

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

Assert.That(methodsByAssembly, Has.Count.EqualTo(appContext.Assemblies.Count));
Assert.That(methodsByAssembly.Values.First(), Has.Property("Name").EqualTo("TestMethodWithParameters").And.Property("ReturnTypeContext").EqualTo(appContext.SystemTypes.SystemVoidType));
Expand Down Expand Up @@ -94,4 +93,78 @@ public void TestFieldInjection()
Assert.That(fieldsByAssembly.Values.First(), Has.Property("Name").EqualTo("TestField").And.Property("FieldTypeContext").EqualTo(appContext.SystemTypes.SystemInt32Type));
Assert.That(fieldsByAssembly.Values.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithFields"));
}

[Test]
public void TestPropertyInjection()
{
var appContext = Cpp2IlApi.CurrentAppContext;

var baseType = appContext!.SystemTypes.SystemObjectType;
var injectedType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithProperties", baseType);
var gettersByAssembly = injectedType.InjectMethodToAllAssemblies("get_TestProperty", appContext.SystemTypes.SystemInt32Type, MethodAttributes.Public);
var propertiesByAssembly = injectedType.InjectPropertyToAllAssemblies("TestProperty", appContext.SystemTypes.SystemInt32Type, gettersByAssembly, null, PropertyAttributes.None);

Assert.That(propertiesByAssembly, Has.Count.EqualTo(appContext.Assemblies.Count));
Assert.That(propertiesByAssembly.Values.First(), Has.Property("Name").EqualTo("TestProperty").And.Property("PropertyTypeContext").EqualTo(appContext.SystemTypes.SystemInt32Type));
Assert.That(propertiesByAssembly.Values.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithProperties"));
}

[Test]
public void TestEventInjection()
{
var appContext = Cpp2IlApi.CurrentAppContext;

var baseType = appContext!.SystemTypes.SystemObjectType;
var injectedType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithEvents", baseType);
var addersByAssembly = injectedType.InjectMethodToAllAssemblies("add_TestEvent", appContext.SystemTypes.SystemInt32Type, MethodAttributes.Public);
var removersByAssembly = injectedType.InjectMethodToAllAssemblies("remove_TestEvent", appContext.SystemTypes.SystemInt32Type, MethodAttributes.Public);
var eventsByAssembly = injectedType.InjectEventToAllAssemblies("TestEvent", appContext.SystemTypes.SystemInt32Type, addersByAssembly, removersByAssembly, null, EventAttributes.None);

Assert.That(eventsByAssembly, Has.Count.EqualTo(appContext.Assemblies.Count));
Assert.That(eventsByAssembly.Values.First(), Has.Property("Name").EqualTo("TestEvent").And.Property("EventTypeContext").EqualTo(appContext.SystemTypes.SystemInt32Type));
Assert.That(eventsByAssembly.Values.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithEvents"));
}

[Test]
public void TestNestedTypeInjection()
{
var appContext = Cpp2IlApi.CurrentAppContext;

var baseType = appContext!.SystemTypes.SystemObjectType;
var declaringType = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TestInjectedTypeWithEvents", baseType);
var injectedType = declaringType.InjectNestedType("NestedType", baseType);

Assert.That(injectedType.InjectedTypes, Has.Length.EqualTo(appContext.Assemblies.Count));
Assert.That(injectedType.InjectedTypes.First(), Has.Property("Name").EqualTo("NestedType").And.Property("Namespace").EqualTo("").And.Property("BaseType").EqualTo(appContext.SystemTypes.SystemObjectType));
Assert.That(injectedType.InjectedTypes.First().DeclaringType, Has.Property("FullName").EqualTo("Cpp2ILInjected.TestInjectedTypeWithEvents"));
}

[Test]
public void TestNestedTypeInjectionSingle()
{
var appContext = Cpp2IlApi.CurrentAppContext;

var declaringType = appContext!.SystemTypes.SystemObjectType;
var injectedType = declaringType.InjectNestedType("NestedType", null);

using (Assert.EnterMultipleScope())
{
Assert.That(appContext.AllTypes, Contains.Item(injectedType));
Assert.That(declaringType.NestedTypes, Contains.Item(injectedType));
}
}

[Test]
public void TestAssemblyInjection()
{
var appContext = Cpp2IlApi.CurrentAppContext;

var assemblyCount = appContext!.Assemblies.Count;
var injectedAssembly = appContext.InjectAssembly("TestInjectedAssembly");
using (Assert.EnterMultipleScope())
{
Assert.That(appContext.Assemblies, Has.Count.EqualTo(assemblyCount + 1));
Assert.That(appContext.Assemblies, Contains.Item(injectedAssembly));
}
}
}
17 changes: 16 additions & 1 deletion Cpp2IL.Core/Extensions/AccessibilityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static bool IsAccessibleTo(this TypeAnalysisContext referenceType, TypeAn

return true;
}
else if (referenceType.DeclaringAssembly.Definition.IsDependencyOf(referencingType.DeclaringAssembly.Definition))
else if (referenceType.DeclaringAssembly.IsDependencyOf(referencingType.DeclaringAssembly))
{
for (var i = 0; i < declaringTypesHierarchy.Length; i++)
{
Expand Down Expand Up @@ -141,6 +141,21 @@ private static bool IsAssignableToInterface(this TypeAnalysisContext derivedType
return false;
}

private static bool IsDependencyOf(this AssemblyAnalysisContext referencedAssembly, AssemblyAnalysisContext referencingAssembly)
{
if (referencingAssembly.Definition is null)
{
// Injected assemblies can access everything
return true;
}
if (referencedAssembly.Definition is null)
{
// Injected assemblies cannot be accessed by metadata assemblies
return false;
}
return referencedAssembly.Definition.IsDependencyOf(referencingAssembly.Definition);
}

private static bool IsDependencyOf(this Il2CppAssemblyDefinition referencedAssembly, Il2CppAssemblyDefinition referencingAssembly)
{
if (Array.IndexOf(referencingAssembly.ReferencedAssemblies, referencedAssembly) >= 0)
Expand Down
32 changes: 32 additions & 0 deletions Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using AssetRipper.Primitives;
using Cpp2IL.Core.Api;
Expand Down Expand Up @@ -196,6 +197,22 @@ private void PopulateMethodsByAddressTable()
return ResolveContextForType(propertyDefinition?.DeclaringType)?.Properties.FirstOrDefault(p => p.Definition == propertyDefinition);
}

public GenericParameterTypeAnalysisContext? ResolveContextForGenericParameter(Il2CppGenericParameter? genericParameter)
{
if (genericParameter is null)
return null;

if (genericParameter.Owner.TypeOwner is { } typeOwner)
{
return ResolveContextForType(typeOwner)?.GenericParameters[genericParameter.genericParameterIndexInOwner];
}
else
{
Debug.Assert(genericParameter.Owner.MethodOwner is not null);
return ResolveContextForMethod(genericParameter.Owner.MethodOwner)?.GenericParameters[genericParameter.genericParameterIndexInOwner];
}
}

public BaseKeyFunctionAddresses GetOrCreateKeyFunctionAddresses()
{
lock (InstructionSet)
Expand All @@ -214,5 +231,20 @@ public MultiAssemblyInjectedType InjectTypeIntoAllAssemblies(string ns, string n
return new(types);
}

public InjectedAssemblyAnalysisContext InjectAssembly(
string name,
Version? version = null,
uint hashAlgorithm = 0,
uint flags = 0,
string? culture = null,
byte[]? publicKeyToken = null,
byte[]? publicKey = null)
{
var assembly = new InjectedAssemblyAnalysisContext(name, this, version, hashAlgorithm, flags, culture, publicKeyToken, publicKey);
Assemblies.Add(assembly);
AssembliesByName.Add(name, assembly);
return assembly;
}

public IEnumerable<TypeAnalysisContext> AllTypes => Assemblies.SelectMany(a => a.Types);
}
64 changes: 54 additions & 10 deletions Cpp2IL.Core/Model/Contexts/AssemblyAnalysisContext.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand All @@ -11,12 +12,12 @@ namespace Cpp2IL.Core.Model.Contexts;
/// <summary>
/// Represents a single Assembly that was converted using IL2CPP.
/// </summary>
public class AssemblyAnalysisContext : HasCustomAttributes
public class AssemblyAnalysisContext : HasCustomAttributesAndName
{
/// <summary>
/// The raw assembly metadata, such as its name, version, etc.
/// </summary>
public Il2CppAssemblyDefinition Definition;
public Il2CppAssemblyDefinition? Definition { get; set; }

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

protected override int CustomAttributeIndex => Definition.CustomAttributeIndex;
public virtual Version Version
{
get
{
//handle __Generated assembly on v29, which has a version of 0.0.-1.-1
return Definition is null || Definition.AssemblyName.build < 0
? new(0,0,0,0)
: new(Definition.AssemblyName.major, Definition.AssemblyName.minor, Definition.AssemblyName.build, Definition.AssemblyName.revision);
}
}

public override AssemblyAnalysisContext CustomAttributeAssembly => this;
public virtual uint HashAlgorithm => Definition?.AssemblyName.hash_alg ?? default;

public virtual uint Flags => Definition?.AssemblyName.flags ?? default;

public override string CustomAttributeOwnerName => Definition.AssemblyName.Name;
public virtual string? Culture => Definition?.AssemblyName.Culture;

public virtual byte[]? PublicKeyToken => Definition?.AssemblyName.PublicKeyToken;

public virtual byte[]? PublicKey => Definition?.AssemblyName.PublicKey;

protected override int CustomAttributeIndex => Definition?.CustomAttributeIndex ?? -1;

public override AssemblyAnalysisContext CustomAttributeAssembly => this;

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

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

public override string DefaultName => Definition?.AssemblyName.Name ?? throw new($"Injected assemblies should override {nameof(DefaultName)}");

protected override bool IsInjected => Definition is null;

/// <summary>
/// Get assembly name without the extension and with any invalid path characters or elements removed.
/// </summary>
public string CleanAssemblyName => MiscUtils.CleanPathElement(Definition.AssemblyName.Name);
public string CleanAssemblyName => MiscUtils.CleanPathElement(Name);

public string ModuleName
{
get
{
var moduleName = Definition?.Image.Name ?? Name;
if (moduleName == "__Generated")
moduleName += ".dll"; //__Generated doesn't have a .dll extension in the metadata but it is still of course a DLL
return moduleName;
}
}

public AssemblyAnalysisContext(Il2CppAssemblyDefinition assemblyDefinition, ApplicationAnalysisContext appContext) : base(assemblyDefinition.Token, appContext)
public AssemblyAnalysisContext(Il2CppAssemblyDefinition? assemblyDefinition, ApplicationAnalysisContext appContext) : base(assemblyDefinition?.Token ?? 0, appContext)
{
if (assemblyDefinition is null)
return;

Definition = assemblyDefinition;

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

public TypeAnalysisContext InjectType(string ns, string name, TypeAnalysisContext? baseType, TypeAttributes typeAttributes = TypeAnalysisContext.DefaultTypeAttributes)
{
var ret = new InjectedTypeAnalysisContext(this, name, ns, baseType, typeAttributes);
Types.Add(ret);
var ret = new InjectedTypeAnalysisContext(this, ns, name, baseType, typeAttributes);
InjectType(ret);
return ret;
}

internal void InjectType(InjectedTypeAnalysisContext ret)
{
Types.Add(ret);
TypesByName[ret.FullName] = ret;
}

public TypeAnalysisContext? GetTypeByFullName(string fullName) => TypesByName.TryGetValue(fullName, out var typeContext) ? typeContext : null;

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

public override string ToString() => "Assembly: " + Definition.AssemblyName.Name;
public override string ToString() => "Assembly: " + Name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ private ConcreteGenericMethodAnalysisContext(Cpp2IlMethodRef? methodRef, MethodA

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

for (var i = 0; i < BaseMethodContext.Parameters.Count; i++)
{
Expand Down
30 changes: 21 additions & 9 deletions Cpp2IL.Core/Model/Contexts/EventAnalysisContext.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Reflection;
using Cpp2IL.Core.Utils;
using LibCpp2IL.Metadata;
Expand All @@ -8,22 +9,22 @@ namespace Cpp2IL.Core.Model.Contexts;
public class EventAnalysisContext : HasCustomAttributesAndName, IEventInfoProvider
{
public readonly TypeAnalysisContext DeclaringType;
public readonly Il2CppEventDefinition Definition;
public readonly Il2CppEventDefinition? Definition;
public readonly MethodAnalysisContext? Adder;
public readonly MethodAnalysisContext? Remover;
public readonly MethodAnalysisContext? Invoker;

protected override int CustomAttributeIndex => Definition.customAttributeIndex;
protected override int CustomAttributeIndex => Definition?.customAttributeIndex ?? -1;

public override AssemblyAnalysisContext CustomAttributeAssembly => DeclaringType.DeclaringAssembly;

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

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

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

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

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

public override string ToString() => $"Event: {Definition.DeclaringType!.Name}::{Definition.Name}";
protected EventAnalysisContext(MethodAnalysisContext? adder, MethodAnalysisContext? remover, MethodAnalysisContext? invoker, TypeAnalysisContext parent) : base(0, parent.AppContext)
{
if (adder is null && remover is null && invoker is null)
throw new ArgumentException("Event must have at least one method");

DeclaringType = parent;
Adder = adder;
Remover = remover;
Invoker = invoker;
}

public override string ToString() => $"Event: {DeclaringType.Name}::{Name}";

#region StableNameDotNet Impl

public ITypeInfoProvider EventTypeInfoProvider => Definition.RawType!.ThisOrElementIsGenericParam()
public ITypeInfoProvider EventTypeInfoProvider => Definition!.RawType!.ThisOrElementIsGenericParam()
? new GenericParameterTypeInfoProviderWrapper(Definition.RawType!.GetGenericParamName())
: TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition.RawType!);

public string EventName => DefaultName;
public string EventName => Name;

#endregion
}
Loading
Loading