diff --git a/.editorconfig b/.editorconfig
index 3478ef3a..b6c6c3fb 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -11,10 +11,29 @@ insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
+end_of_line = lf
# Generated code
[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}]
generated_code = true
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+end_of_line = lf
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
# C# files
[*.cs]
@@ -54,7 +73,7 @@ dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
-dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
@@ -62,7 +81,7 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
-dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
@@ -72,7 +91,7 @@ dotnet_naming_style.static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
-dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
+dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
@@ -150,6 +169,11 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_prefer_system_threading_lock = true:suggestion
# C++ Files
[*.{cpp,h,in}]
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 04038ad1..fb4a849a 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- global-json-file: global.json
+ dotnet-version: 10.0.x
- name: Extract Release Version
id: get_version
diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml
index ac60880e..bacf0397 100644
--- a/.github/workflows/format_check.yml
+++ b/.github/workflows/format_check.yml
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- global-json-file: global.json
+ dotnet-version: 10.0.x
- name: Check format
run: |
diff --git a/Directory.Build.props b/Directory.Build.props
index c121dfab..20829406 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,7 +7,6 @@
latest
disable
true
- Il2CppInterop
latest
logo_icon.png
LGPL-3.0-only
diff --git a/Documentation/Design/Arrays.md b/Documentation/Design/Arrays.md
new file mode 100644
index 00000000..dbf4919d
--- /dev/null
+++ b/Documentation/Design/Arrays.md
@@ -0,0 +1,10 @@
+# Arrays
+
+## Array Types
+
+Il2Cpp arrays are represented with a closed type hierarchy.
+
+* `Il2CppArrayBase`
+* `Il2CppArrayBase`
+
+`Il2CppArrayBase` is the type used everywhere. `Il2CppArrayBase` is just for unstripping certain op codes.
diff --git a/Documentation/Design/Attributes.md b/Documentation/Design/Attributes.md
new file mode 100644
index 00000000..9dd34133
--- /dev/null
+++ b/Documentation/Design/Attributes.md
@@ -0,0 +1,3 @@
+# Attributes
+
+Attributes are not applied to types.
diff --git a/Documentation/Design/CorLibTypes.md b/Documentation/Design/CorLibTypes.md
new file mode 100644
index 00000000..b99950c4
--- /dev/null
+++ b/Documentation/Design/CorLibTypes.md
@@ -0,0 +1,187 @@
+# Core Library Types
+
+## Numeric Primitives, `char`, and `bool`
+
+These are blittable and should be directly reused, kind of. Signatures will use the Il2Cpp types, but implicit conversions will be used inside unstripped method bodies.
+
+```cs
+public static Il2CppSystem.Int32 Add(Il2CppSystem.Int32 x, Il2CppSystem.Int32 y)
+{
+ // Conversions to Managed are applied on function entry
+ ref
+ int x2 = (int)x;
+ int y2 = (int)y;
+
+ // All operations are done with managed types
+ int z = x2 + y2;
+
+ // If an Il2Cpp primitive must be returned, conversion back is applied right before the return instruction.
+ return (Il2CppSystem.Int32)z;
+}
+```
+
+This ensures that CIL opcodes function as expected. However, it can cause some complexities with instance methods on the corlib types. For that, unsafe helpers are used.
+
+```cs
+public static Il2CppSystem.String Sum(Il2CppSystem.Int32 x, Il2CppSystem.Int32 y)
+{
+ int z = (int)x + (int)y;
+ return Unsafe.As(ref z).ToString();
+}
+```
+
+## `string`
+
+Strings should not be implicitly marshalled. In other words, `Il2CppSystem.String` should be used.
+
+## `object`
+
+For compatibility with interfaces and value types, this should be emitted as-is, despite the more complicated marshalling involved.
+
+## `Attribute`
+
+## `ValueType` and `Enum`
+
+Boxing to these types is invalid.
+
+## `Exception`
+
+## Counterargument to all of the above
+
+```cs
+// Original code
+public static void DoSomething(T value) where T : System.IConvertible
+{
+}
+public static void DoSomethingElse()
+{
+ DoSomething(System.StringComparison.Ordinal);
+ DoSomething(default);
+ DoSomething(default);
+}
+
+// Unstripped code
+public static void DoSomething() where T : Il2CppSystem.IConvertible
+{
+}
+public static void DoSomethingElse()
+{
+ // Which is correct?
+ // System.Enum fails the constraint check.
+ // Il2CppSystem.Enum makes the method unusable because boxed enums inherit from `System.Enum` not `Il2CppSystem.Enum`.
+ DoSomething(Il2CppSystem.StringComparison.Ordinal);
+ DoSomething(Il2CppSystem.StringComparison.Ordinal);
+ DoSomething(Cast(Il2CppSystem.StringComparison.Ordinal)); // Maybe this is the way it should be emitted?
+
+ // If the type is a real enum, it fails the constraint check.
+ DoSomething(default);
+
+ // Which is correct?
+ // int fails the constraint check, but is what we currently do.
+ DoSomething(default);
+ DoSomething(default);
+}
+private static Il2CppSystem.Enum Cast(object value)
+{
+ if (value is Il2CppSystem.Enum il2cppEnum)
+ {
+ return il2cppEnum;
+ }
+ if (value is System.Enum sysEnum)
+ {
+ throw new NotImplementedException("Cannot cast System.Enum to Il2CppSystem.Enum");
+ }
+ throw new InvalidCastException("Cannot cast to Il2CppSystem.Enum");
+}
+
+// Proposal
+public static void DoSomething() where T : Il2CppSystem.IConvertible
+{
+}
+public static void DoSomethingElse()
+{
+ DoSomething(Il2CppSystem.StringComparison.Ordinal);
+ DoSomething(default);
+ DoSomething(default);
+}
+namespace Il2CppSystem
+{
+ public interface IObject
+ {
+ // Instance members of Il2CppSystem.Object
+ }
+ public interface IValueType : IObject
+ {
+ // No members
+ }
+ public interface IEnum : IValueType, Il2CppSystem.IComparable, Il2CppSystem.IFormattable, Il2CppSystem.IConvertible
+ {
+ // Instance members of Il2CppSystem.Enum, except for interface implementations
+ }
+ public class Object : IObject
+ {
+ // A static method should be generated for each instance method
+ }
+ public abstract class ValueType : Object, IValueType
+ {
+ // A static method should be generated for each instance method
+ }
+ public abstract class Enum : ValueType, IEnum
+ {
+ // A static method should be generated for each instance method
+ }
+ public readonly struct StringComparison : IEnum // Maybe inject other interfaces like System.IEquatable<> for user convenience
+ {
+ // [System.Flags] // Only if the Il2Cpp enum has the Flags attribute
+ private enum __Internal
+ {
+ CurrentCulture = 0,
+ CurrentCultureIgnoreCase = 1,
+ InvariantCulture = 2,
+ InvariantCultureIgnoreCase = 3,
+ Ordinal = 4,
+ OrdinalIgnoreCase = 5
+ }
+
+ // Might make this `int` instead. The only reason to use `__Internal` is to have a more efficient ToString implementation.
+ private readonly __Internal value__;
+
+ // Sacrifice the ability to use Il2Cpp enums in constants.
+ public static readonly StringComparison CurrentCulture = new StringComparison(__Internal.CurrentCulture);
+ public static readonly StringComparison CurrentCultureIgnoreCase = new StringComparison(__Internal.CurrentCultureIgnoreCase);
+ public static readonly StringComparison InvariantCulture = new StringComparison(__Internal.InvariantCulture);
+ public static readonly StringComparison InvariantCultureIgnoreCase = new StringComparison(__Internal.InvariantCultureIgnoreCase);
+ public static readonly StringComparison Ordinal = new StringComparison(__Internal.Ordinal);
+ public static readonly StringComparison OrdinalIgnoreCase = new StringComparison(__Internal.OrdinalIgnoreCase);
+
+ private StringComparison(__Internal value) => value__ = value;
+ public StringComparison(int value) => value__ = unchecked((__Internal)value);
+ public static explicit operator int(StringComparison value) => unchecked((int)value.value__);
+ public static explicit operator StringComparison(int value) => new StringComparison(value);
+
+ // Numerical operators like shift
+
+ // Override ToString, GetHashCode, Equals, etc.
+ public override int GetHashCode()
+ {
+ // Use the static method from Il2CppSystem.Enum
+ // We need to ensure that behavior is consistent with the native method.
+ return Il2CppSystem.Enum.GetHashCode(this);
+ }
+
+ static StringComparison()
+ {
+ // OriginalNameAttribute no longer needed.
+ }
+ }
+ public interface ICloneable : IObject, System.ICloneable
+ {
+ IObject Clone();
+
+ object System.ICloneable.Clone()
+ {
+ return Clone();
+ }
+ }
+}
+```
diff --git a/Documentation/Design/Delegates.md b/Documentation/Design/Delegates.md
new file mode 100644
index 00000000..90bedea7
--- /dev/null
+++ b/Documentation/Design/Delegates.md
@@ -0,0 +1,3 @@
+# Delegates
+
+All Il2Cpp delegates have a generated conversion to a system delegate, which might be generated if necessary.
diff --git a/Documentation/Design/Enums.md b/Documentation/Design/Enums.md
new file mode 100644
index 00000000..715ebaee
--- /dev/null
+++ b/Documentation/Design/Enums.md
@@ -0,0 +1,29 @@
+# Enums
+
+Enums are converted to readonly structs.
+
+```cs
+// Original
+public enum ElectricityType
+{
+ Off = 0,
+ On = 1
+}
+
+// Converted
+public struct ElectricityType : IObject, IValueType, IEnum, IComparable, IFormattable, IConvertible
+{
+ private readonly Int32 value__;
+
+ public static readonly ElectricityType Off = (ElectricityType)0;
+ public static readonly ElectricityType On = (ElectricityType)1;
+}
+```
+
+## Generic constraint
+
+`Il2CppSystem.Enum` should be replaced with `Il2CppSystem.IEnum` in generic constraints.
+
+## Interfaces
+
+Additional interfaces like `IEquatable<>`, `IEqualityOperators<,,>`, and `IBitwiseOperators<,,>` could be introduced for user convenience.
diff --git a/Documentation/Design/Exceptions.md b/Documentation/Design/Exceptions.md
new file mode 100644
index 00000000..11aac1e7
--- /dev/null
+++ b/Documentation/Design/Exceptions.md
@@ -0,0 +1,7 @@
+# Exceptions
+
+Il2Cpp exceptions each have a cooresponding system exception generated, making up a full hierarchy and enabling try catch support in unstripped code.
+
+## Runtime exceptions
+
+Unstripped code currently allows exceptions (such as `NullReferenceException`) to be thrown by the .NET runtime. Ideally, all such exceptions would be handled.
diff --git a/Documentation/Design/Fields.md b/Documentation/Design/Fields.md
new file mode 100644
index 00000000..aa93e62f
--- /dev/null
+++ b/Documentation/Design/Fields.md
@@ -0,0 +1,19 @@
+# Fields
+
+## Generated code
+
+```cs
+// Reference type
+public FieldType fieldName
+{
+ get => throw null;
+ set => throw null;
+}
+public static ByReference UnsafeField_fieldName(Class obj) => throw null;
+private static readonly IntPtr FieldInfoPtr_fieldIndex;
+
+// Value type
+public FieldType fieldName;
+public static ByReference UnsafeField_fieldName(ByRerefence obj) => throw null;
+private static readonly IntPtr FieldInfoPtr_fieldIndex;
+```
diff --git a/Documentation/Design/GenericMethods.md b/Documentation/Design/GenericMethods.md
new file mode 100644
index 00000000..00c7e4a5
--- /dev/null
+++ b/Documentation/Design/GenericMethods.md
@@ -0,0 +1,22 @@
+# Generic Methods
+
+## Pointers
+
+Pointers are resolved as needed using Il2Cpp reflection. It's fine to do this initialization lazily because all generic type instances are known in advance.
+
+```cs
+private static class MethodInfoStoreGeneric_Aggregate
+{
+ internal static System.IntPtr Pointer = IL2CPP.il2cpp_method_get_from_reflection(IL2CPP.Il2CppObjectBaseToPtrNotNull(new MethodInfo(IL2CPP.il2cpp_method_get_object(NativeMethodInfoPtr_Aggregate, Il2CppClassPointerStore.NativeClassPtr))
+ .MakeGenericMethod(new Il2CppReferenceArray(new Type[2]
+ {
+ Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr)),
+ Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr))
+ }))));
+}
+```
+
+If a modder tries to use a generic method instantiation that doesn't exist, an exception is thrown.
+
+*
+*
diff --git a/Documentation/Design/GenericTypes.md b/Documentation/Design/GenericTypes.md
new file mode 100644
index 00000000..04efa215
--- /dev/null
+++ b/Documentation/Design/GenericTypes.md
@@ -0,0 +1,10 @@
+# Generic Types
+
+```cs
+Il2CppClassPointerStore>.NativeClassPtr = IL2CPP.il2cpp_class_from_type(Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(IL2CPP.GetIl2CppClass("mscorlib.dll", "System.Collections.Generic", "Dictionary`2"))).MakeGenericType(new Il2CppReferenceArray(new Type[2]
+{
+ Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr)),
+ Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr))
+})).TypeHandle.value);
+IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore>.NativeClassPtr);
+```
diff --git a/Documentation/Design/Initialization.md b/Documentation/Design/Initialization.md
new file mode 100644
index 00000000..0333624d
--- /dev/null
+++ b/Documentation/Design/Initialization.md
@@ -0,0 +1,11 @@
+# Il2Cpp Initialization
+
+Every Il2Cpp class has a generated `Il2CppInternals` class.
+
+## Module Initialization
+
+The static constructor for a type triggers its Il2Cpp initialization.
+
+## Global Initialization
+
+A global initialization assembly ensures that all types get initialized on startup. This is essential for ensuring that the object pool can create objects.
diff --git a/Documentation/Design/Injection.md b/Documentation/Design/Injection.md
new file mode 100644
index 00000000..fcf88489
--- /dev/null
+++ b/Documentation/Design/Injection.md
@@ -0,0 +1,113 @@
+# Injection
+
+## Major Flaw
+
+Current injection has a major flaw. It waits until the last minute to allocate a new type. This can cause problems with:
+
+* Cyclical field types (two classes have a field on each other)
+* Self-referential field type
+* Injected type used in base type or interfaces
+
+## Class
+
+```cs
+// User code
+
+// This attribute is for source generation and has no runtime impact.
+[InjectInIl2Cpp(Assembly = "OptionalAssemblyName")] // By default, types are injected into Assembly-CSharp
+public partial class MyMonoBehaviour : MonoBehaviour
+{
+ [Il2CppField]
+ public static partial Int32 staticField { get; set; }
+ [Il2CppField]
+ public partial Boolean instanceField { get; set; }
+
+ // Note: Injected classes cannot have uninjected instance fields and static fields are always ignored.
+
+ [Il2CppMethod]
+ public static void DoSomething(String s)
+ {
+ }
+
+ public static void DoAnything(string s) // If this wasn't hidden, it would be invalid because string is not a valid Il2Cpp parameter type.
+ {
+ }
+
+ [Il2CppProperty]
+ public static Int32 MyProperty { get => default; set {} }
+}
+
+// Source generated
+
+partial class MyMonoBehaviour
+{
+ static MyMonoBehaviour()
+ {
+ ClassInjector.RegisterTypeInIl2Cpp();
+ }
+
+ // Required
+ public MyMonoBehaviour(ObjectPointer ptr) : base(ptr)
+ {
+ }
+
+ public static partial int staticField { get => Il2CppInternals.staticField.Get(); set => Il2CppInternals.staticField.Set(value); }
+ public partial bool instanceField { get => Il2CppInternals.instanceField.Get(this); set => Il2CppInternals.instanceField.Set(this, value); }
+}
+file static class Il2CppInternals // If the injected class is generic, this will also be generic.
+{
+ internal static readonly Il2CppStaticField staticField = new("staticField", GetClassPointer);
+ internal static readonly Il2CppField instanceField = new("instanceField", GetClassPointer);
+ private static IntPtr GetClassPointer() => Il2CppClassPointerStore.NativeClassPtr;
+}
+```
+
+## Struct
+
+```cs
+// User code
+
+[InjectInIl2Cpp]
+public partial struct MyStruct
+{
+ [Il2CppField]
+ public static partial T staticField { get; set; }
+
+ // [Il2CppField] is optional for struct instance fields.
+ public bool instanceField;
+}
+
+// Source generated
+
+partial struct MyStruct : IIl2CppType>
+ where T : IIl2CppType
+{
+ static MyStruct()
+ {
+ ClassInjector.RegisterTypeInIl2Cpp());
+ }
+
+ public static partial T staticField { get => Il2CppInternals.staticField.Get(); set => Il2CppInternals.staticField.Set(value); }
+}
+file static class Il2CppInternals
+{
+ internal static readonly Il2CppStaticField staticField = new("staticField", GetClassPointer);
+ private static IntPtr GetClassPointer() => Il2CppClassPointerStore.NativeClassPtr;
+}
+```
+
+## New Design
+
+### Design Constraints
+
+A class pointer needs registered in `Il2CppClassPointerStore` before anything else can reference this type. In other words, the class pointer needs registered before setting:
+
+* Declaring type
+* Nested types
+* Base type
+* Interface implementations
+* Types in member signatures
+
+In addition, we need to know the virtual table slot count when allocating data for the class because the virtual table is always positioned in method directly after the class struct.
+
+Ideally, no Il2Cpp APIs should be called while class structs are partially initialized.
diff --git a/Documentation/Design/Interfaces.md b/Documentation/Design/Interfaces.md
new file mode 100644
index 00000000..4900224f
--- /dev/null
+++ b/Documentation/Design/Interfaces.md
@@ -0,0 +1,3 @@
+# Interfaces
+
+Interfaces are generated as actual interfaces.
diff --git a/Documentation/Design/Marshalling.md b/Documentation/Design/Marshalling.md
new file mode 100644
index 00000000..0560b8cc
--- /dev/null
+++ b/Documentation/Design/Marshalling.md
@@ -0,0 +1,3 @@
+# Marshalling
+
+All Il2Cpp types implement the `Il2CppType<>` interface, which enables marshalling.
diff --git a/Documentation/Design/MethodInvokers.md b/Documentation/Design/MethodInvokers.md
new file mode 100644
index 00000000..f2a6e8d4
--- /dev/null
+++ b/Documentation/Design/MethodInvokers.md
@@ -0,0 +1,180 @@
+# Method Invokers
+
+Every normal (user-facing) Il2Cpp method has an unsafe implementation.
+
+* Implementation methods are static.
+* Implementation methods are private, so they can only be called from within the type.
+* If the Il2Cpp method is instance, the first parameter is the object.
+* All other implementation parameter types are wrapped in `ByReference<>`, including parameters that are already `ByReference<>`.
+* Similarly, local variable types are also wrapped in `ByReference<>`.
+* The data for local variables is stack allocated at the beginning of the method.
+
+Some methods have an unsafe invoker.
+
+* Invoker methods are static.
+* Invoker methods are public, so they can be called from other generated assemblies where desirable.
+
+This can be verbose, but it preserves semantics exactly for indirect memory access of reference types.
+
+## Example Output
+
+```cs
+// Original
+public string GetString(int param1, T param2, IInterface param3);
+
+// Il2Cpp
+public String GetString(Int32 param1, T param2, IInterface param3) where T : IIl2CppType
+{
+ ByReference data_this = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+
+ // Value type
+ data_this.CopyFrom(this);
+
+ // Reference type
+ data_this.SetValue(this);
+
+ String result = UnsafeInvoke_GetString(data_this, param1, param2, param3);
+
+ // Value type
+ data_this.CopyTo(this);
+
+ return result;
+}
+
+// Invoker
+public static String UnsafeInvoke_GetString(ByReference @this, Int32 param1, T param2, IInterface param3) where T : IIl2CppType
+{
+ // Param 1
+ ByReference data_param1 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param1.SetValue(param1);
+
+ // Param 2
+ ByReference data_param2 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param2.SetValue(param2);
+
+ // Param 3
+ ByReference data_param3 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param3.SetValue(param3);
+
+ return UnsafeImplementation_GetString(@this, data_param1, data_param2, data_param3);
+}
+private static String UnsafeImplementation_GetString(ByReference @this, ByReference param1, ByReference param2, ByReference param3) where T : IIl2CppType
+{
+ IntPtr* arguments = stackalloc IntPtr[3];
+ arguments[0] = RuntimeInvokeHelper.GetPointerForParameter(param1);
+ arguments[1] = RuntimeInvokeHelper.GetPointerForParameter(param2);
+ arguments[2] = RuntimeInvokeHelper.GetPointerForParameter(param3);
+ return RuntimeInvokeHelper.InvokeFunction(/* method info */, RuntimeInvokeHelper.GetPointerForThis(@this), (void**)arguments);
+}
+
+// Unstripped Original
+public static bool Compare(Self param1, Self param2)
+{
+ string local1 = param1.GetString(0, 0, null);
+ string local2 = param2.GetString(0, 0, null);
+ return local1 == local2;
+}
+
+// Unstripped Invoker
+public static Boolean Compare(Self param1, Self param2)
+{
+ ByReference data_param1 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param1.SetValue(param1);
+ ByReference data_param2 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param1.SetValue(param2);
+
+ return UnsafeImplementation_Compare(data_param1, data_param2)
+}
+private static Boolean UnsafeImplementation_Compare(ByReference param1, ByReference param2)
+{
+ ByReference data_local1 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ ByReference data_local2 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+
+ data_local1.SetValue(param1.GetValue().GetString((Int32)0, (Int64)0, (IInterface?)null));
+ data_local2.SetValue(param2.GetValue().GetString((Int32)0, (Int64)0, (IInterface?)null));
+
+ return data_local1.GetValue() == data_local2.GetValue();
+}
+```
+
+## Unstripped Instruction Translations
+
+### ldind_Ref
+
+* ldind_I
+* `Il2CppObjectPool.Get`
+
+### stind_Ref
+
+* `Box`
+* stind_I
+
+### ldarga
+
+* ldarg
+* `ByReference.ToPointer()`
+
+### ldarg
+
+* ldarg
+* `ByReference.GetValue()`
+
+### starg
+
+* ldarg
+* `ByReference.SetValue`
+
+### ldloca
+
+* ldloc
+* `ByReference.ToPointer()`
+
+### ldloc
+
+* ldloc
+* `ByReference.GetValue()`
+
+### stloc
+
+* ldloc
+* `ByReference.SetValue`
+
+### call / callvirt
+
+Since argument data is handled by the caller, additional locals need to be created to store those arguments.
+
+* Arguments are converted from Mono to Il2Cpp and popped off 1 by 1 into the data.
+* Data variables are loaded onto the stack.
+* Call the target's invoker method
+
+## `ref` parameters
+
+```cs
+// Il2Cpp method
+public ByReference Method(Int32 param_normal, [In] ByReference param_in, ByReference param_ref, [Out] ByReference param_out);
+
+// Injected overload
+public ByReference Method(Int32 param_normal, in Int32 param_in, ref Int32 param_ref, out Int32 param_out)
+{
+ ByReference data_param_in = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param_in.CopyFrom(in param_in);
+
+ ByReference data_param_ref = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param_ref.CopyFrom(ref param_ref);
+
+ ByReference data_param_out = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]);
+ data_param_out.Clear();
+
+ ByReference result = Method(param_normal, data_param_in, data_param_ref, data_param_out);
+
+ data_param_ref.CopyTo(out param_ref);
+ data_param_out.CopyTo(out param_out);
+
+ return result;
+}
+```
+
+Return types are unchanged for two reasons:
+
+* There's no way to convert `ByReference` to a managed reference for all type parameters.
+* Overloads can't differ only on the return type.
diff --git a/Documentation/Design/NativeStaticConstructors.md b/Documentation/Design/NativeStaticConstructors.md
new file mode 100644
index 00000000..447e39ef
--- /dev/null
+++ b/Documentation/Design/NativeStaticConstructors.md
@@ -0,0 +1,7 @@
+# Native Static Constructors
+
+We give users a way to interact with static constructors.
+
+* They are renamed from `.cctor` to `StaticConstructor`. If this is unavailable, additional underscores are added.
+* Like other methods, they are publicized.
+* The `specialname` and `rtspecialname` attributes are removed.
diff --git a/Documentation/Design/NestedTypes.md b/Documentation/Design/NestedTypes.md
new file mode 100644
index 00000000..d7f6dbf8
--- /dev/null
+++ b/Documentation/Design/NestedTypes.md
@@ -0,0 +1,6 @@
+# Nested Types
+
+```cs
+Il2CppClassPointerStore.NativeClassPtr = IL2CPP.GetIl2CppNestedType(Il2CppClassPointerStore.NativeClassPtr, "NestedClass");
+IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore.NativeClassPtr);
+```
diff --git a/Documentation/Design/ReturnObjects.md b/Documentation/Design/ReturnObjects.md
new file mode 100644
index 00000000..803f8511
--- /dev/null
+++ b/Documentation/Design/ReturnObjects.md
@@ -0,0 +1,34 @@
+# Return Objects
+
+## Value Types
+
+These should be returned "as-is".
+
+```cs
+return *(bool*)IL2CPP.il2cpp_object_unbox(intPtr);
+```
+
+## Sealed Classes
+
+Since these can't be inherited from, we can call the pointer constructor directly. However, we may want to treat all classes the same, for simplicity.
+
+## Normal Classes
+
+During initialization, we cache delegates (or function pointers) for creating an object.
+
+```cs
+private static object Create(ObjectPointer ptr) => new Class(ptr);
+internal static void Initialize()
+{
+ Il2CppObjectPool.RegisterFactoryMethod(Il2CppClassPointerStore.NativeClassPtr, (Func)Create);
+}
+```
+
+When returning from a method, we check the pool to see if the object already exists. If the object doesn't yet exist in managed code, we use the cached factory method to create a new one.
+
+```cs
+// Maybe instead Il2CppObjectPool.Get should perform this null check?
+return (ReturnType?)Il2CppObjectPool.Get(intPtr);
+```
+
+In the event that a factory method has not been registered, we throw an exception. If we pre-register factory methods for all generic type instances this should never happen.
diff --git a/Documentation/Design/SimpleClasses.md b/Documentation/Design/SimpleClasses.md
new file mode 100644
index 00000000..5d390006
--- /dev/null
+++ b/Documentation/Design/SimpleClasses.md
@@ -0,0 +1,59 @@
+# Simple Classes
+
+This document outlines the high level emission of simple classes.
+
+## Universal Base Type
+
+All reference classes inherit from `Il2CppObjectBase`.
+
+## Constructors
+
+All reference classes have an injected primary constructor. This carries the object pointer from derived classes to their base
+
+```cs
+public class Derived(ObjectPointer ptr) : MonoBehaviour(ptr)
+{
+ static Derived()
+ {
+ Il2CppClassPointerStore.NativeClassPtr = IL2CPP.GetIl2CppClass("Assembly-CSharp.dll", "", "Derived");
+ IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore.NativeClassPtr);
+ NativeMethodInfoPtr__ctor_Public_Void_0 = IL2CPP.GetIl2CppMethodByToken(Il2CppClassPointerStore.NativeClassPtr, 100666213);
+ }
+
+ public unsafe Derived() : this(IL2CPP.il2cpp_object_new(Il2CppClassPointerStore.NativeClassPtr))
+ {
+ Unsafe.SkipInit(out IntPtr intPtr2);
+ IntPtr intPtr = IL2CPP.il2cpp_runtime_invoke(NativeMethodInfoPtr__ctor_Public_Void_0, IL2CPP.Il2CppObjectBaseToPtrNotNull(this), (void**)null, ref intPtr2);
+ Il2CppException.RaiseExceptionIfNecessary(intPtr2);
+ }
+}
+```
+
+A wrapper struct in the common library prevents conflicts.
+
+```cs
+public readonly record struct ObjectPointer(IntPtr Value)
+{
+ public static implicit operator ObjectPointer(IntPtr value) => new(value);
+ public static implicit operator IntPtr(ObjectPointer value) => value.Value;
+}
+```
+
+## Static classes
+
+We make the class abstract but not sealed and inject a private constructor.
+
+```cs
+public abstract class StaticClass
+{
+ static StaticClass()
+ {
+ Il2CppClassPointerStore.NativeClassPtr = IL2CPP.GetIl2CppClass("Assembly-CSharp.dll", "", "StaticClass");
+ IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore.NativeClassPtr);
+ }
+
+ private StaticClass()
+ {
+ }
+}
+```
diff --git a/Documentation/Design/Unstripping.md b/Documentation/Design/Unstripping.md
new file mode 100644
index 00000000..cb8e2d01
--- /dev/null
+++ b/Documentation/Design/Unstripping.md
@@ -0,0 +1,92 @@
+# Unstripping
+
+Certain assemblies can be unstripped because we have source code available, eg mscorlib and UnityEngine.
+
+## Injection
+
+Unstripped types and members get injected, similar to custom user classes.
+
+## Replace Native Implementation
+
+If the success rate for unstripping is high, and if a certain method can be unstripped, its implementation should be moved to managed land. This enables several things:
+
+* Transpilers (assuming that native is patched to use this implementation)
+* Generic instances not present in the game
+
+However, unstripping semantics would have to be perfect. Little flaws are unacceptable.
+
+```cs
+static IntPtr GeneratedDynamicMethod(IntPtr @this, FixedSizeStruct_8b param0)
+{
+ IntPtr result;
+ try
+ {
+ result = Il2CppTypeHelper.ReadFromPointer(&@this).ManagedMethod(Il2CppTypeHelper.ReadFromPointer(¶m0)).Box();
+ }
+ catch (Il2CppException e)
+ {
+ result = default;
+ il2cpp_raise_exception(e.Il2CppObject.Pointer)
+ }
+ return result;
+}
+```
+
+## ICalls
+
+Unity internal calls can be recovered.
+
+```cs
+// Ignore the use of string, rather than Il2CppSystem.String
+public static class SceneUtility
+{
+ private delegate IntPtr GetScenePathByBuildIndexDelegate(int buildIndex);
+
+ private delegate int GetBuildIndexByScenePathDelegate(IntPtr scenePath);
+
+ private static readonly GetScenePathByBuildIndexDelegate GetScenePathByBuildIndexDelegateField = IL2CPP.ResolveICall("UnityEngine.SceneManagement.SceneUtility::GetScenePathByBuildIndex");
+
+ private static readonly GetBuildIndexByScenePathDelegate GetBuildIndexByScenePathDelegateField = IL2CPP.ResolveICall("UnityEngine.SceneManagement.SceneUtility::GetBuildIndexByScenePath");
+
+ public static string GetScenePathByBuildIndex(int buildIndex)
+ {
+ return IL2CPP.Il2CppStringToManaged(GetScenePathByBuildIndexDelegateField(buildIndex));
+ }
+
+ public static int GetBuildIndexByScenePath(string scenePath)
+ {
+ return GetBuildIndexByScenePathDelegateField(IL2CPP.ManagedStringToIl2Cpp(scenePath));
+ }
+}
+```
+
+## callvirt
+
+```cs
+public static T* ThrowIfNull(this T* pointer) where T : struct
+{
+ if (pointer is null)
+ {
+ throw new Il2CppSystem.NullReferenceException();
+ }
+ return pointer;
+}
+public static T ThrowIfNull(this T obj) where T : class
+{
+ if (obj is null)
+ {
+ throw new Il2CppSystem.NullReferenceException();
+ }
+ return obj;
+}
+```
+
+Inserting this before any `callvirt` instruction ensures that correct semantics are maintained.
+
+## ldelem/stelem/ldlen
+
+Null checking and bounds checking are handled in the array helper methods.
+
+## Numeric op codes
+
+These are untyped, so some rudimentary analysis is needed to determine the type.
diff --git a/Documentation/Design/ValueTypes.md b/Documentation/Design/ValueTypes.md
new file mode 100644
index 00000000..4e0bc3ef
--- /dev/null
+++ b/Documentation/Design/ValueTypes.md
@@ -0,0 +1,9 @@
+# Value Types
+
+## `readonly`
+
+Structs that were originally readonly no longer are.
+
+## `IValueType`
+
+All value types implement this interface. It replaces `ValueType` anywhere that would normally be used, like in generic constraints.
diff --git a/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj b/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj
index 1e5a507e..c4c0b04e 100644
--- a/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj
+++ b/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net10.0
Exe
true
il2cppinterop
@@ -15,14 +15,13 @@
-
-
+
-
-
-
+
+
+
diff --git a/Il2CppInterop.CLI/Program.cs b/Il2CppInterop.CLI/Program.cs
index 5796435c..a61d0019 100644
--- a/Il2CppInterop.CLI/Program.cs
+++ b/Il2CppInterop.CLI/Program.cs
@@ -1,316 +1,66 @@
-using System.CommandLine;
-using System.CommandLine.NamingConventionBinder;
-using System.Text.RegularExpressions;
-using Il2CppInterop;
-using Il2CppInterop.Common;
+using Cpp2IL.Core.ProcessingLayers;
using Il2CppInterop.Generator;
-using Il2CppInterop.Generator.Runners;
-using Il2CppInterop.StructGenerator;
-using Microsoft.Extensions.Logging;
-var command = new RootCommand { new Option("--verbose", "Produce more verbose output") };
-command.Description = "Generate Managed<->IL2CPP interop assemblies from Cpp2IL's output.";
-
-var generateCommand = new Command("generate")
-{
- new Option("--input", "Directory with Il2CppDumper's dummy assemblies") {IsRequired = true}
- .ExistingOnly(),
- new Option("--output", "Directory to write generated assemblies to") {IsRequired = true},
- new Option("--unity", "Directory with original Unity assemblies for unstripping").ExistingOnly(),
- new Option("--game-assembly", "Path to GameAssembly.dll. Used for certain analyses").ExistingOnly(),
- new Option("--no-xref-cache", "Don't generate xref scanning cache. All scanning will be done at runtime."),
- new Option("--add-prefix-to",
- "Assemblies and namespaces starting with these will get an Il2Cpp prefix in generated assemblies. Allows multiple values. Obsolete."),
- new Option("--dont-add-prefix-to",
- "Assemblies and namespaces starting with these will not get an Il2Cpp prefix in generated assemblies. Allows multiple values."),
- new Option("--use-opt-out-prefixing",
- "Assemblies and namespaces will get an Il2Cpp prefix in generated assemblies unless otherwise specified. Obsolete."),
- new Option("--deobf-map",
- "Specifies a file specifying deobfuscation map for obfuscated types and members.").ExistingOnly(),
- new Option("--deobf-uniq-chars", "How many characters per unique token to use during deobfuscation"),
- new Option("--deobf-uniq-max", "How many maximum unique tokens per type are allowed during deobfuscation"),
- new Option("--blacklist-assembly", "Don't write specified assembly to output. Allows multiple values."),
- new Option("--obf-regex",
- "Specifies a regex for obfuscated names. All types and members matching will be renamed."),
- new Option("--passthrough-names",
- "If specified, names will be copied from input assemblies as-is without renaming or deobfuscation."),
- new Option("--no-parallel", "Disable parallel processing when writing assemblies. Use if you encounter stability issues when generating assemblies."),
-};
-generateCommand.Description = "Generate wrapper assemblies that can be used to interop with Il2Cpp";
-generateCommand.Handler = CommandHandler.Create((GenerateCommandOptions opts) =>
-{
- var buildResult = opts.Build();
- Il2CppInteropGenerator.Create(buildResult.Options)
- .AddLogger(buildResult.Logger)
- .AddInteropAssemblyGenerator()
- .Run();
-});
-
-var deobfCommand = new Command("deobf");
-deobfCommand.Description = "Tools for deobfuscating assemblies";
-var deobfAnalyzeCommand = new Command("analyze")
-{
- new Option("--input", "Directory of assemblies to deobfuscate") {IsRequired = true}.ExistingOnly(),
- new Option("--add-prefix-to",
- "Assemblies and namespaces starting with these will get an Il2Cpp prefix in generated assemblies. Allows multiple values. Obsolete."),
- new Option("--dont-add-prefix-to",
- "Assemblies and namespaces starting with these will not get an Il2Cpp prefix in generated assemblies. Allows multiple values."),
- new Option("--use-opt-out-prefixing",
- "Assemblies and namespaces will get an Il2Cpp prefix in generated assemblies unless otherwise specified. Obsolete.")
-};
-deobfAnalyzeCommand.Description =
- "Analyze deobfuscation performance with different parameter values. Will not generate assemblies.";
-
-deobfAnalyzeCommand.Handler = CommandHandler.Create((DeobfAnalyzeCommandOptions opts) =>
-{
- var buildResult = opts.Build();
- Il2CppInteropGenerator.Create(buildResult.Options)
- .AddLogger(buildResult.Logger)
- .AddDeobfuscationAnalyzer()
- .Run();
-});
-
-var deobfGenerateCommand = new Command("generate")
-{
- new Option("--old-assemblies", "Directory with old unobfuscated assemblies") {IsRequired = true}
- .ExistingOnly(),
- new Option("--new-assemblies", "Directory to write obfuscation maps to") {IsRequired = true}
- .ExistingOnly(),
- new Option("--output", "Directory to write obfuscation maps to") {IsRequired = true},
- new Option("--include",
- "Include these assemblies for deobfuscation map generation. If none are specified, all assemblies will be included."),
- new Option("--deobf-uniq-chars", "How many characters per unique token to use during deobfuscation"),
- new Option("--deobf-uniq-max", "How many maximum unique tokens per type are allowed during deobfuscation"),
- new Option("--obf-regex",
- "Specifies a regex for obfuscated names. All types and members matching will be renamed.")
-};
-deobfGenerateCommand.Description =
- "Generate a deobfuscation map from original unobfuscated assemblies. Will not generate assemblies.";
-deobfGenerateCommand.Handler = CommandHandler.Create((DeobfGenerateCommandOptions opts) =>
-{
- var buildResult = opts.Build();
- Il2CppInteropGenerator.Create(buildResult.Options)
- .AddLogger(buildResult.Logger)
- .AddDeobfuscationMapGenerator()
- .Run();
-});
-
-var wrapperCommand = new Command("wrapper-gen")
-{
- new Option("--headers",
- "Directory that contains libil2cpp headers. Directory must contains subdirectories named after libil2cpp version.")
- {
- IsRequired = true
- }.ExistingOnly(),
- new Option("--output", "Directory to write managed struct wrapper sources to") {IsRequired = true}
-};
-wrapperCommand.Description = "Tools for generating Il2Cpp struct wrappers from libi2lcpp source";
-wrapperCommand.Handler = CommandHandler.Create((WrapperCommandOptions opts) =>
-{
- Il2CppStructWrapperGenerator.Generate(opts.Build());
-});
-
-deobfCommand.Add(deobfAnalyzeCommand);
-deobfCommand.Add(deobfGenerateCommand);
-command.Add(deobfCommand);
-command.Add(generateCommand);
-command.Add(wrapperCommand);
-
-return command.Invoke(args);
-
-
-internal record CmdOptionsResult(GeneratorOptions Options, ILogger Logger);
-
-internal record BaseCmdOptions(bool Verbose)
-{
- public virtual CmdOptionsResult Build()
- {
- var loggerFactory = LoggerFactory.Create(builder =>
- {
- builder
- .AddFilter("Il2CppInterop", Verbose ? LogLevel.Trace : LogLevel.Information)
- .AddSimpleConsole(opt => { opt.SingleLine = true; });
- });
-
- var logger = loggerFactory.CreateLogger("Il2CppInterop");
-
- return new CmdOptionsResult(new GeneratorOptions { Verbose = Verbose }, logger);
- }
-}
-
-internal record WrapperCommandOptions(DirectoryInfo Headers, DirectoryInfo Output, bool Verbose)
-{
- public virtual Il2CppStructWrapperGeneratorOptions Build()
- {
- var loggerFactory = LoggerFactory.Create(builder =>
- {
- builder
- .AddFilter("Il2CppInterop", Verbose ? LogLevel.Trace : LogLevel.Information)
- .AddSimpleConsole(opt => { opt.SingleLine = true; });
- });
- var logger = loggerFactory.CreateLogger("Il2CppInterop");
- return new Il2CppStructWrapperGeneratorOptions(Headers.FullName, Output.FullName, logger);
- }
-}
-
-internal record GenerateCommandOptions(
- bool Verbose,
- DirectoryInfo Input,
- DirectoryInfo Output,
- DirectoryInfo? Unity,
- FileInfo? GameAssembly,
- bool NoXrefCache,
- string[]? AddPrefixTo,
- string[]? DontAddPrefixTo,
- bool UseOptOutPrefixing,
- FileInfo? DeobfMap,
- int DeobfUniqChars,
- int DeobfUniqMax,
- string[]? BlacklistAssembly,
- Regex? ObfRegex,
- bool PassthroughNames,
- bool NoParallel
-) : BaseCmdOptions(Verbose)
-{
- public override CmdOptionsResult Build()
- {
- var res = base.Build();
- var opts = res.Options;
-
- opts.Source = Utils.LoadAssembliesFrom(Input);
- opts.OutputDir = Output.FullName;
- opts.UnityBaseLibsDir = Unity?.FullName;
- opts.GameAssemblyPath = GameAssembly?.FullName ?? "";
- opts.NoXrefCache = NoXrefCache;
- opts.TypeDeobfuscationCharsPerUniquifier = DeobfUniqChars;
- opts.TypeDeobfuscationMaxUniquifiers = DeobfUniqMax;
- opts.AdditionalAssembliesBlacklist.AddRange(BlacklistAssembly ?? Array.Empty());
- opts.ObfuscatedNamesRegex = ObfRegex;
- opts.PassthroughNames = PassthroughNames;
- opts.Parallel = !NoParallel;
-
- if (AddPrefixTo is not null && AddPrefixTo.Length > 0)
- {
- if (DontAddPrefixTo is not null && DontAddPrefixTo.Length > 0)
- {
- throw new Exception("--add-prefix-to cannot be used with --dont-add-prefix-to");
- }
- else if (UseOptOutPrefixing)
- {
- throw new Exception("--add-prefix-to cannot be used with --use-opt-out-prefixing");
- }
-
- opts.Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptIn;
- foreach (var s in AddPrefixTo)
- {
- opts.NamespacesAndAssembliesToPrefix.Add(s);
- }
- }
- else if (DontAddPrefixTo is not null && DontAddPrefixTo.Length > 0)
- {
- opts.Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptOut;
- foreach (var s in DontAddPrefixTo)
- {
- opts.NamespacesAndAssembliesToNotPrefix.Add(s);
- }
- }
- else if (UseOptOutPrefixing)
- {
- opts.Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptOut;
- }
-
- if (opts.Il2CppPrefixMode == GeneratorOptions.PrefixMode.OptIn)
- {
- res.Logger.LogWarning("Opt-In prefixing is obsolete and will be removed in a future version.");
- }
-
- if (DeobfMap is not null)
- {
- opts.ReadRenameMap(DeobfMap.FullName);
- }
-
- return res;
- }
-}
-
-internal record DeobfAnalyzeCommandOptions(
- bool Verbose,
- string[]? AddPrefixTo,
- string[]? DontAddPrefixTo,
- bool UseOptOutPrefixing,
- DirectoryInfo Input
-) : BaseCmdOptions(Verbose)
-{
- public override CmdOptionsResult Build()
- {
- var res = base.Build();
- var opts = res.Options;
-
- opts.Source = Utils.LoadAssembliesFrom(Input);
-
- if (AddPrefixTo is not null && AddPrefixTo.Length > 0)
- {
- if (DontAddPrefixTo is not null && DontAddPrefixTo.Length > 0)
- {
- throw new Exception("--add-prefix-to cannot be used with --dont-add-prefix-to");
- }
- else if (UseOptOutPrefixing)
- {
- throw new Exception("--add-prefix-to cannot be used with --use-opt-out-prefixing");
- }
-
- opts.Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptIn;
- foreach (var s in AddPrefixTo)
- {
- opts.NamespacesAndAssembliesToPrefix.Add(s);
- }
- }
- else if (DontAddPrefixTo is not null && DontAddPrefixTo.Length > 0)
- {
- opts.Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptOut;
- foreach (var s in DontAddPrefixTo)
- {
- opts.NamespacesAndAssembliesToNotPrefix.Add(s);
- }
- }
- else if (UseOptOutPrefixing)
- {
- opts.Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptOut;
- }
-
- if (opts.Il2CppPrefixMode == GeneratorOptions.PrefixMode.OptIn)
- {
- res.Logger.LogWarning("Opt-In prefixing is obsolete and will be removed in a future version.");
- }
-
- return res;
- }
-}
-
-internal record DeobfGenerateCommandOptions(
- bool Verbose,
- DirectoryInfo OldAssemblies,
- DirectoryInfo NewAssemblies,
- DirectoryInfo Output,
- string[]? Include,
- int DeobfUniqChars,
- int DeobfUniqMax,
- Regex? ObfRegex
-) : BaseCmdOptions(Verbose)
-{
- public override CmdOptionsResult Build()
- {
- var res = base.Build();
- var opts = res.Options;
-
- opts.Source = Utils.LoadAssembliesFrom(OldAssemblies);
- opts.OutputDir = Output.FullName;
- opts.DeobfuscationNewAssembliesPath = NewAssemblies.FullName;
- opts.DeobfuscationGenerationAssemblies.AddRange(Include ?? Array.Empty());
- opts.TypeDeobfuscationCharsPerUniquifier = DeobfUniqChars;
- opts.TypeDeobfuscationMaxUniquifiers = DeobfUniqMax;
- opts.ObfuscatedNamesRegex = ObfRegex;
-
- return res;
- }
-}
+string gameExePath = args[0];
+string outputFolder = args[1];
+string unstripDirectory = args[2];
+
+// Unstrip directory needs to contain all files recursively contained in these directories:
+// \Editor\Data\MonoBleedingEdge\lib\mono\unityaot-win32
+// \Editor\Data\PlaybackEngines\windowsstandalonesupport\Variations\win64_player_nondevelopment_il2cpp\Data\Managed
+
+Il2CppGame.Process(
+ gameExePath,
+ outputFolder,
+ new AsmResolverDllOutputFormatBinding(),
+ [
+ new AttributeAnalysisProcessingLayer(), // Needed for recovery of unmanaged constraints
+ //new StableRenamingProcessingLayer(),
+ new UnstripProcessingLayer(), // Can be disabled for performance during development
+ new InterfaceOverrideProcessingLayer(),
+ new InvalidFieldRemovalProcessingLayer(),
+ new Il2CppRenamingProcessingLayer(),
+ new CleanRenamingProcessingLayer(),
+ new ConflictRenamingProcessingLayer(),
+ new AttributesOverrideProcessingLayer(),
+ new PublicizerProcessingLayer(),
+ new MscorlibAssemblyInjectionProcessingLayer(),
+ new KnownTypeAssignmentProcessingLayer(),
+ new ReferenceAssemblyInjectionProcessingLayer(),
+ new InvisibleInterfaceProcessingLayer(),
+ new ObjectInterfaceProcessingLayer(),
+ new ReferenceReplacementProcessingLayer(),
+ new AttributeRemovalProcessingLayer(),
+ new IndexerAttributeInjectionProcessingLayer(),
+ new PointerConstructorProcessingLayer(),
+ new Il2CppTypeConstraintProcessingLayer(),
+ new InitializationClassProcessingLayer(),
+ new MarshallingProcessingLayer(),
+ new PrimitiveImplicitConversionProcessingLayer(),
+ new EnumProcessingLayer(),
+ new ObjectOverridesProcessingLayer(),
+ new ObjectInternalsProcessingLayer(),
+ new MemberAttributeProcessingLayer(),
+ new FieldAccessorProcessingLayer(),
+ new EventProcessingLayer(),
+ new ExceptionHierarchyProcessingLayer(),
+ new MethodInvokerProcessingLayer(),
+ new MethodBodyTranslationProcessingLayer(),
+ new NativeMethodBodyProcessingLayer(),
+ new DelegateConversionProcessingLayer(),
+ new ByRefParameterOverloadProcessingLayer(),
+ new UserFriendlyOverloadProcessingLayer(),
+ // new SystemInterfaceRecoveryProcessingLayer(), // Should handle INotifyCompletion, IEnumerable, IEquatable, etc
+ new ConstantInitializationProcessingLayer(),
+ new StaticConstructorProcessingLayer(),
+ ],
+ [new(UnstripBaseProcessingLayer.DirectoryKey, unstripDirectory)]);
+Console.WriteLine("Done!");
+
+/*
+Todo
+- System interfaces
+- overloads with delegates, primitives
+- Source generation for user-injected types
+- Add attributes to "Unsafe" methods so that users cannot see them
+*/
diff --git a/Il2CppInterop.CLI/Utils.cs b/Il2CppInterop.CLI/Utils.cs
deleted file mode 100644
index 6fa7fa1e..00000000
--- a/Il2CppInterop.CLI/Utils.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using AsmResolver.DotNet;
-
-namespace Il2CppInterop;
-
-internal static class Utils
-{
- public static List LoadAssembliesFrom(DirectoryInfo directoryInfo)
- {
- var inputAssemblies = directoryInfo.EnumerateFiles("*.dll").Select(f => AssemblyDefinition.FromFile(
- f.FullName)).ToList();
-
- return inputAssemblies;
- }
-}
diff --git a/Il2CppInterop.Common/AssemblyInfo.cs b/Il2CppInterop.Common/AssemblyInfo.cs
deleted file mode 100644
index 9fbd1411..00000000
--- a/Il2CppInterop.Common/AssemblyInfo.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-using System.Runtime.CompilerServices;
-
-[assembly: InternalsVisibleTo("Il2CppInterop.Runtime")]
-[assembly: InternalsVisibleTo("Il2CppInterop.Generator")]
-[assembly: InternalsVisibleTo("Il2CppInterop.HarmonySupport")]
diff --git a/Il2CppInterop.Common/Attributes/Il2CppEventAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppEventAttribute.cs
new file mode 100644
index 00000000..e4fb36a9
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppEventAttribute.cs
@@ -0,0 +1,9 @@
+namespace Il2CppInterop.Common.Attributes;
+
+///
+/// Indicates that the attributed method is associated with an event in the IL2CPP runtime.
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+public sealed class Il2CppEventAttribute : Il2CppMemberAttribute
+{
+}
diff --git a/Il2CppInterop.Common/Attributes/Il2CppFieldAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppFieldAttribute.cs
new file mode 100644
index 00000000..e9cdf889
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppFieldAttribute.cs
@@ -0,0 +1,10 @@
+namespace Il2CppInterop.Common.Attributes;
+
+///
+/// Indicates that the attributed field is associated with a field in the IL2CPP runtime.
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+public sealed class Il2CppFieldAttribute : Il2CppMemberAttribute
+{
+ public int Index { get; init; } = -1;
+}
diff --git a/Il2CppInterop.Common/Attributes/Il2CppFinalizerAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppFinalizerAttribute.cs
new file mode 100644
index 00000000..c6ed8772
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppFinalizerAttribute.cs
@@ -0,0 +1,9 @@
+namespace Il2CppInterop.Common.Attributes;
+
+///
+/// Indicates that the attributed method should be called in the source generated finalizer for an injected class.
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+public sealed class Il2CppFinalizerAttribute : Attribute
+{
+}
diff --git a/Il2CppInterop.Common/Attributes/Il2CppMemberAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppMemberAttribute.cs
new file mode 100644
index 00000000..80151a27
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppMemberAttribute.cs
@@ -0,0 +1,9 @@
+namespace Il2CppInterop.Common.Attributes;
+
+public abstract class Il2CppMemberAttribute : Attribute
+{
+ public string? Name { get; init; }
+ private protected Il2CppMemberAttribute()
+ {
+ }
+}
diff --git a/Il2CppInterop.Common/Attributes/Il2CppMethodAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppMethodAttribute.cs
new file mode 100644
index 00000000..d140b768
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppMethodAttribute.cs
@@ -0,0 +1,10 @@
+namespace Il2CppInterop.Common.Attributes;
+
+///
+/// Indicates that the attributed method is associated with a method in the IL2CPP runtime.
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+public sealed class Il2CppMethodAttribute : Il2CppMemberAttribute
+{
+ public int Index { get; init; } = -1;
+}
diff --git a/Il2CppInterop.Common/Attributes/Il2CppPropertyAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppPropertyAttribute.cs
new file mode 100644
index 00000000..aca20c33
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppPropertyAttribute.cs
@@ -0,0 +1,12 @@
+namespace Il2CppInterop.Common.Attributes;
+
+///
+/// Indicates that the attributed property is associated with a property in the IL2CPP runtime.
+///
+///
+/// Marking the accessor methods with is optional.
+///
+[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+public sealed class Il2CppPropertyAttribute : Il2CppMemberAttribute
+{
+}
diff --git a/Il2CppInterop.Common/Attributes/Il2CppTypeAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppTypeAttribute.cs
new file mode 100644
index 00000000..b4ca89a2
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppTypeAttribute.cs
@@ -0,0 +1,7 @@
+namespace Il2CppInterop.Common.Attributes;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
+public sealed class Il2CppTypeAttribute(Type internals) : Attribute
+{
+ public Type Internals { get; } = internals;
+}
diff --git a/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs b/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs
new file mode 100644
index 00000000..c2989a5d
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs
@@ -0,0 +1,18 @@
+namespace Il2CppInterop.Common.Attributes;
+
+///
+/// Indicates that a type should be source generated with Il2Cpp injection code.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
+public sealed class InjectedTypeAttribute : Attribute
+{
+ ///
+ /// The name of the type. If not specified, it will be inferred from the managed type's name.
+ ///
+ public string? Name { get; init; }
+ ///
+ /// The file name of the assembly that the type is defined in.
+ /// If not specified, the assembly name will be inferred from the type's containing assembly.
+ ///
+ public string? Assembly { get; init; }
+}
diff --git a/Il2CppInterop.Common/Attributes/ManagedFieldAttribute.cs b/Il2CppInterop.Common/Attributes/ManagedFieldAttribute.cs
new file mode 100644
index 00000000..3d5b3ff6
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/ManagedFieldAttribute.cs
@@ -0,0 +1,14 @@
+namespace Il2CppInterop.Common.Attributes;
+
+///
+/// Indicates that the attributed partial property is an injected field with an uninjected reference type.
+/// Code will be source generated to properly reference it using a managed GCHandle, which is freed when the object is collected by the Il2Cpp GC.
+///
+///
+/// Storing an Il2Cpp object in this field can cause memory leaks in some cases.
+///
+[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+public sealed class ManagedFieldAttribute : Attribute
+{
+ public string? Name { get; init; }
+}
diff --git a/Il2CppInterop.Common/Attributes/ObfuscatedNameAttribute.cs b/Il2CppInterop.Common/Attributes/ObfuscatedNameAttribute.cs
deleted file mode 100644
index a0b6c54f..00000000
--- a/Il2CppInterop.Common/Attributes/ObfuscatedNameAttribute.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Il2CppInterop.Common.Attributes;
-
-[AttributeUsage(AttributeTargets.All, Inherited = false)]
-public class ObfuscatedNameAttribute : Attribute
-{
- public readonly string ObfuscatedName;
-
- public ObfuscatedNameAttribute(string obfuscatedName)
- {
- ObfuscatedName = obfuscatedName;
- }
-}
diff --git a/Il2CppInterop.Common/Attributes/OriginalNameAttribute.cs b/Il2CppInterop.Common/Attributes/OriginalNameAttribute.cs
deleted file mode 100644
index ff798fca..00000000
--- a/Il2CppInterop.Common/Attributes/OriginalNameAttribute.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Il2CppInterop.Common.Attributes;
-
-// This attribute is only applied to enums so the class pointer
-// can be resolved correctly at runtime
-// https://github.com/BepInEx/Il2CppInterop/issues/66
-[AttributeUsage(AttributeTargets.Enum, Inherited = false)]
-public class OriginalNameAttribute : Attribute
-{
- public readonly string AssemblyName;
- public readonly string Namespace;
- public readonly string Name;
-
- public OriginalNameAttribute(string assemblyName, string @namespace, string name)
- {
- AssemblyName = assemblyName;
- Namespace = @namespace;
- Name = name;
- }
-}
diff --git a/Il2CppInterop.Common/Host/BaseHost.cs b/Il2CppInterop.Common/Host/BaseHost.cs
index eee92bc2..f95652d3 100644
--- a/Il2CppInterop.Common/Host/BaseHost.cs
+++ b/Il2CppInterop.Common/Host/BaseHost.cs
@@ -2,7 +2,7 @@
public abstract class BaseHost : IDisposable
{
- private static BaseHost s_instance;
+ private static BaseHost? s_instance;
protected static T GetInstance() where T : BaseHost
{
diff --git a/Il2CppInterop.Common/IIl2CppType.cs b/Il2CppInterop.Common/IIl2CppType.cs
new file mode 100644
index 00000000..63688169
--- /dev/null
+++ b/Il2CppInterop.Common/IIl2CppType.cs
@@ -0,0 +1,48 @@
+namespace Il2CppInterop.Common;
+
+public interface IIl2CppType
+{
+ IntPtr ObjectClass { get; }
+}
+public interface IIl2CppType : IIl2CppType where TSelf : notnull, IIl2CppType
+{
+ ///
+ /// The native size of the type in bytes
+ ///
+ static abstract int Size { get; }
+ ///
+ /// The file name of the assembly that the type is defined in
+ ///
+ static virtual string AssemblyName
+ {
+ get
+ {
+ var result = typeof(TSelf).Assembly.GetName().Name;
+ return string.IsNullOrEmpty(result)
+ ? "Assembly-CSharp.dll"
+ : result.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
+ ? result
+ : $"{result}.dll";
+ }
+ }
+ ///
+ /// The namespace of type
+ ///
+ static virtual string Namespace => typeof(TSelf).Namespace ?? "";
+ ///
+ /// The class name of the type
+ ///
+ static virtual string Name => typeof(TSelf).Name;
+ ///
+ /// Writes the native representation of the value to the provided span. The span is required to be at least bytes long.
+ ///
+ /// The value to write.
+ /// The span to write the value to.
+ static abstract void WriteToSpan(TSelf? value, Span span);
+ ///
+ /// Reads the native representation of the value from the provided span. The span is required to be at least bytes long.
+ ///
+ /// The span to read the value from.
+ /// The value read from the span.
+ static abstract TSelf? ReadFromSpan(ReadOnlySpan span);
+}
diff --git a/Il2CppInterop.Common/Il2CppInterop.Common.csproj b/Il2CppInterop.Common/Il2CppInterop.Common.csproj
index d9320b4d..e703cb0f 100644
--- a/Il2CppInterop.Common/Il2CppInterop.Common.csproj
+++ b/Il2CppInterop.Common/Il2CppInterop.Common.csproj
@@ -1,16 +1,25 @@
- netstandard2.0
+ net10.0
BepInEx, knah et al.
Common API for Il2CppInterop packages
- Il2CppInterop.Common
+ preview
+ enable
-
-
-
+
+ <_Parameter1>Il2CppInterop.HarmonySupport
+
+
+ <_Parameter1>Il2CppInterop.Runtime
+
+
+
+
+
+
diff --git a/Il2CppInterop.Common/Il2CppInteropUtils.cs b/Il2CppInterop.Common/Il2CppInteropUtils.cs
index 99948d3c..956373d4 100644
--- a/Il2CppInterop.Common/Il2CppInteropUtils.cs
+++ b/Il2CppInterop.Common/Il2CppInteropUtils.cs
@@ -1,38 +1,61 @@
using System.Reflection;
-using System.Reflection.Emit;
+using Il2CppInterop.Common.Attributes;
namespace Il2CppInterop.Common;
public static class Il2CppInteropUtils
{
- private static FieldInfo? GetFieldInfoFromMethod(MethodBase method, string prefix)
+ private static FieldInfo? GetFieldInfo(Type declaringType, string prefix, int index)
{
- var body = method.GetMethodBody();
- if (body == null) throw new ArgumentException("Target method may not be abstract");
- var methodModule = method.DeclaringType.Assembly.Modules.Single();
- foreach (var (opCode, opArg) in MiniIlParser.Decode(body.GetILAsByteArray()))
+ if (index < 0)
+ return null;
+
+ var internalsType = ResolveInternals(declaringType);
+ if (internalsType == null)
+ return null;
+
+ return internalsType.GetField($"{prefix}{index}", BindingFlags.Static | BindingFlags.NonPublic);
+
+ static Type? ResolveInternals(Type declaringType)
{
- if (opCode != OpCodes.Ldsfld) continue;
+ var attr = declaringType.GetCustomAttribute();
+ if (attr == null)
+ return null;
- var fieldInfo = methodModule.ResolveField((int)opArg, method.DeclaringType.GenericTypeArguments, method.GetGenericArguments());
- if (fieldInfo?.FieldType != typeof(IntPtr)) continue;
+ var internals = attr.Internals;
- if (fieldInfo.Name.StartsWith(prefix)) return fieldInfo;
+ if (internals.IsGenericTypeDefinition && declaringType.IsConstructedGenericType)
+ {
+ internals = internals.MakeGenericType(declaringType.GetGenericArguments());
+ }
- // Resolve generic method info pointer fields
- if (method.IsGenericMethod && fieldInfo.DeclaringType.Name.StartsWith("MethodInfoStoreGeneric_") && fieldInfo.Name == "Pointer") return fieldInfo;
+ return internals;
}
-
- return null;
}
- public static FieldInfo GetIl2CppMethodInfoPointerFieldForGeneratedMethod(MethodBase method)
+ public static FieldInfo? GetIl2CppMethodInfoPointerFieldForGeneratedMethod(MethodBase method)
{
- return GetFieldInfoFromMethod(method, "NativeMethodInfoPtr_");
+ var declaringType = method.DeclaringType;
+ if (declaringType == null)
+ return null;
+
+ var index = method.GetCustomAttribute()?.Index ?? -1;
+
+ return GetFieldInfo(declaringType, "MethodInfoPtr_", index);
}
- public static FieldInfo GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(MethodBase method)
+ public static FieldInfo? GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(MethodBase method)
{
- return GetFieldInfoFromMethod(method, "NativeFieldInfoPtr_");
+ var declaringType = method.DeclaringType;
+ if (declaringType == null)
+ return null;
+
+ var prop = declaringType
+ .GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
+ .FirstOrDefault(p => p.GetMethod == method || p.SetMethod == method);
+
+ var index = prop?.GetCustomAttribute()?.Index ?? -1;
+
+ return GetFieldInfo(declaringType, "FieldInfoPtr_", index);
}
}
diff --git a/Il2CppInterop.Common/Maps/MethodAddressToTokenMap.cs b/Il2CppInterop.Common/Maps/MethodAddressToTokenMap.cs
index b993e551..46043cac 100644
--- a/Il2CppInterop.Common/Maps/MethodAddressToTokenMap.cs
+++ b/Il2CppInterop.Common/Maps/MethodAddressToTokenMap.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System.Reflection;
namespace Il2CppInterop.Common.Maps;
diff --git a/Il2CppInterop.Common/Maps/MethodAddressToTokenMapBase.cs b/Il2CppInterop.Common/Maps/MethodAddressToTokenMapBase.cs
index b57b66f8..ca95c7eb 100644
--- a/Il2CppInterop.Common/Maps/MethodAddressToTokenMapBase.cs
+++ b/Il2CppInterop.Common/Maps/MethodAddressToTokenMapBase.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using System.Collections;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
diff --git a/Il2CppInterop.Common/MiniILParser.cs b/Il2CppInterop.Common/MiniILParser.cs
deleted file mode 100644
index 40a1f2dc..00000000
--- a/Il2CppInterop.Common/MiniILParser.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System.Reflection;
-using System.Reflection.Emit;
-
-namespace Il2CppInterop.Common;
-
-internal static class MiniIlParser
-{
- private static readonly Dictionary OpCodesMap = new();
- private static readonly HashSet PrefixCodes = new();
-
- static MiniIlParser()
- {
- foreach (var fieldInfo in typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public))
- {
- if (fieldInfo.FieldType != typeof(OpCode)) continue;
-
- var fieldValue = (OpCode)fieldInfo.GetValue(null);
- OpCodesMap[fieldValue.Value] = fieldValue;
- if ((ushort)fieldValue.Value > byte.MaxValue)
- PrefixCodes.Add((byte)((ushort)fieldValue.Value >> 8));
- }
- }
-
- public static IEnumerable<(OpCode, long)> Decode(byte[] ilBytes)
- {
- var index = 0;
- while (index < ilBytes.Length)
- {
- short currentOp = ilBytes[index++];
- if (PrefixCodes.Contains(currentOp))
- currentOp = (short)((ushort)(currentOp << 8) | ilBytes[index++]);
-
- if (!OpCodesMap.TryGetValue(currentOp, out var opCode))
- throw new NotSupportedException($"Unknown opcode {currentOp} encountered");
-
- var argLength = GetOperandSize(opCode);
-
- switch (argLength)
- {
- case 0:
- yield return (opCode, 0);
- break;
- case 1:
- yield return (opCode, ilBytes[index]);
- break;
- case 2:
- yield return (opCode, BitConverter.ToInt16(ilBytes, index));
- break;
- case 4:
- yield return (opCode, BitConverter.ToInt32(ilBytes, index));
- break;
- case 8:
- yield return (opCode, BitConverter.ToInt64(ilBytes, index));
- break;
- default:
- throw new NotSupportedException($"Unsupported opcode argument length {argLength}");
- }
-
- index += argLength;
- }
- }
-
- private static int GetOperandSize(OpCode opCode)
- {
- switch (opCode.OperandType)
- {
- case OperandType.InlineField:
- case OperandType.InlineBrTarget:
- case OperandType.InlineI:
- case OperandType.InlineMethod:
- case OperandType.InlineSig:
- case OperandType.InlineString:
- case OperandType.InlineSwitch:
- case OperandType.InlineTok:
- case OperandType.InlineType:
- case OperandType.ShortInlineR:
- return 4;
- case OperandType.InlineI8:
- case OperandType.InlineR:
- return 8;
- case OperandType.InlineNone:
- return 0;
- case OperandType.InlineVar:
- return 2;
- case OperandType.ShortInlineBrTarget:
- case OperandType.ShortInlineVar:
- case OperandType.ShortInlineI:
- return 1;
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
-}
diff --git a/Il2CppInterop.Common/ObjectPointer.cs b/Il2CppInterop.Common/ObjectPointer.cs
new file mode 100644
index 00000000..03544bbf
--- /dev/null
+++ b/Il2CppInterop.Common/ObjectPointer.cs
@@ -0,0 +1,12 @@
+namespace Il2CppInterop.Common;
+
+public readonly record struct ObjectPointer(IntPtr Value)
+{
+ public static explicit operator ObjectPointer(IntPtr value) => new(value);
+ public static explicit operator IntPtr(ObjectPointer value) => value.Value;
+
+ public static unsafe explicit operator ObjectPointer(void* value) => new((IntPtr)value);
+ public static unsafe explicit operator void*(ObjectPointer value) => (void*)value.Value;
+
+ public static ObjectPointer Null => new(IntPtr.Zero);
+}
diff --git a/Il2CppInterop.Common/XrefScans/GeneratedDatabasesUtil.cs b/Il2CppInterop.Common/XrefScans/GeneratedDatabasesUtil.cs
index f8538077..2eac06ab 100644
--- a/Il2CppInterop.Common/XrefScans/GeneratedDatabasesUtil.cs
+++ b/Il2CppInterop.Common/XrefScans/GeneratedDatabasesUtil.cs
@@ -1,4 +1,3 @@
-#nullable enable
using System.Reflection;
namespace Il2CppInterop.Common.XrefScans;
diff --git a/Il2CppInterop.Common/XrefScans/XrefScanMethodDb.cs b/Il2CppInterop.Common/XrefScans/XrefScanMethodDb.cs
index 3958f4a7..c5d96b58 100644
--- a/Il2CppInterop.Common/XrefScans/XrefScanMethodDb.cs
+++ b/Il2CppInterop.Common/XrefScans/XrefScanMethodDb.cs
@@ -12,7 +12,7 @@ public static class XrefScanMethodDb
private static readonly MethodXrefScanCache XrefScanCache;
private static readonly long GameAssemblyBase;
- private static XrefScanUtil.InitMetadataForMethod ourMetadataInitForMethodDelegate;
+ private static XrefScanUtil.InitMetadataForMethod? ourMetadataInitForMethodDelegate;
static XrefScanMethodDb()
{
@@ -28,7 +28,7 @@ static XrefScanMethodDb()
}
}
- public static MethodBase TryResolvePointer(IntPtr methodStart)
+ public static MethodBase? TryResolvePointer(IntPtr methodStart)
{
return MethodMap.Lookup((long)methodStart - GameAssemblyBase);
}
diff --git a/Il2CppInterop.Common/XrefScans/XrefScanUtil.cs b/Il2CppInterop.Common/XrefScans/XrefScanUtil.cs
index 10f9b238..25680158 100644
--- a/Il2CppInterop.Common/XrefScans/XrefScanUtil.cs
+++ b/Il2CppInterop.Common/XrefScans/XrefScanUtil.cs
@@ -5,10 +5,10 @@ namespace Il2CppInterop.Common.XrefScans;
internal static class XrefScanUtil
{
- private static InitMetadataForMethod ourMetadataInitForMethodDelegate;
+ private static InitMetadataForMethod? ourMetadataInitForMethodDelegate;
private static IntPtr ourMetadataInitForMethodPointer;
- internal static event Func<(InitMetadataForMethod, IntPtr)> InitRuntimeUtils;
+ internal static event Func<(InitMetadataForMethod, IntPtr)>? InitRuntimeUtils;
internal static unsafe bool CallMetadataInitForMethod(MethodBase method)
{
@@ -43,7 +43,7 @@ internal static unsafe bool CallMetadataInitForMethod(MethodBase method)
if (Marshal.ReadByte(initFlagPointer) == 0)
{
- ourMetadataInitForMethodDelegate(Marshal.ReadInt32(tokenPointer));
+ ourMetadataInitForMethodDelegate?.Invoke(Marshal.ReadInt32(tokenPointer));
Marshal.WriteByte(initFlagPointer, 1);
}
diff --git a/Il2CppInterop.Common/XrefScans/XrefScannerManager.cs b/Il2CppInterop.Common/XrefScans/XrefScannerManager.cs
index 8025a517..aa727152 100644
--- a/Il2CppInterop.Common/XrefScans/XrefScannerManager.cs
+++ b/Il2CppInterop.Common/XrefScans/XrefScannerManager.cs
@@ -13,7 +13,7 @@ internal static class XrefScannerManagerExtensions
internal class XrefScannerManager : IHostComponent
{
- private static IXrefScannerImpl s_xrefScanner;
+ private static IXrefScannerImpl? s_xrefScanner;
public static IXrefScannerImpl Impl
{
diff --git a/Il2CppInterop.Generator/AsmResolverDllOutputFormatBinding.cs b/Il2CppInterop.Generator/AsmResolverDllOutputFormatBinding.cs
new file mode 100644
index 00000000..2411e89e
--- /dev/null
+++ b/Il2CppInterop.Generator/AsmResolverDllOutputFormatBinding.cs
@@ -0,0 +1,78 @@
+using AsmResolver.DotNet;
+using Cpp2IL.Core.Model.Contexts;
+using Cpp2IL.Core.OutputFormats;
+
+namespace Il2CppInterop.Generator;
+
+public class AsmResolverDllOutputFormatBinding : AsmResolverDllOutputFormatThrowNull
+{
+ public override string OutputFormatId => "dll_binding";
+
+ public override string OutputFormatName => "DLL files with method bodies containing a binding for modding.";
+
+ protected override void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext)
+ {
+ if (methodContext.TryGetExtraData(out TranslatedMethodBody? translatedBody))
+ {
+ translatedBody.FillMethodBody(methodDefinition);
+ methodContext.RemoveExtraData(); // Free up memory
+ }
+ else if (methodContext.TryGetExtraData(out NativeMethodBody? nativeBody))
+ {
+ nativeBody.FillMethodBody(methodDefinition);
+ methodContext.RemoveExtraData(); // Free up memory
+ }
+ else
+ {
+ // This gets called when the body of an unstripped method could not be translated.
+ // We could have it throw a custom exception with a message, but throwing null is sufficient for now.
+ base.FillMethodBody(methodDefinition, methodContext);
+ }
+ }
+
+ public override List BuildAssemblies(ApplicationAnalysisContext context)
+ {
+ var list = base.BuildAssemblies(context);
+
+ var referenceAssemblies = context.Assemblies.Where(a => a.IsReferenceAssembly).Select(a => a.Name).ToHashSet();
+
+ // Remove injected reference assemblies from the output
+ for (var i = list.Count - 1; i >= 0; i--)
+ {
+ if (referenceAssemblies.Contains(list[i].Name ?? ""))
+ list.RemoveAt(i);
+ }
+
+ // Replace mscorlib references with .NET Core references
+ var dotNetCorLib = KnownCorLibs.SystemRuntime_v10_0_0_0;
+ foreach (var assembly in list)
+ {
+ foreach (var module in assembly.Modules)
+ {
+ foreach (var reference in module.AssemblyReferences)
+ {
+ if (reference.Name == "mscorlib")
+ {
+ reference.Name = dotNetCorLib.Name;
+ reference.Version = dotNetCorLib.Version;
+ reference.Attributes = dotNetCorLib.Attributes;
+ reference.PublicKeyOrToken = dotNetCorLib.PublicKeyOrToken;
+ reference.HashValue = dotNetCorLib.HashValue;
+ reference.Culture = dotNetCorLib.Culture;
+ }
+ }
+
+ // Temporary fix
+ foreach (var type in module.GetAllTypes())
+ {
+ if (type.IsInterface)
+ {
+ type.BaseType = null;
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+}
diff --git a/Il2CppInterop.Generator/AsmResolverDllOutputFormatUnstripped.cs b/Il2CppInterop.Generator/AsmResolverDllOutputFormatUnstripped.cs
new file mode 100644
index 00000000..10341a63
--- /dev/null
+++ b/Il2CppInterop.Generator/AsmResolverDllOutputFormatUnstripped.cs
@@ -0,0 +1,24 @@
+using AsmResolver.DotNet;
+using Cpp2IL.Core.Model.Contexts;
+using Cpp2IL.Core.OutputFormats;
+
+namespace Il2CppInterop.Generator;
+
+public class AsmResolverDllOutputFormatUnstripped : AsmResolverDllOutputFormatThrowNull
+{
+ public override string OutputFormatId => "dll_unstripped";
+
+ public override string OutputFormatName => "DLL files with method bodies containing an unstripped implementation if available.";
+
+ protected override void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext)
+ {
+ if (methodContext.TryGetExtraData(out OriginalMethodBody? originalBody))
+ {
+ originalBody.FillMethodBody(methodDefinition);
+ }
+ else
+ {
+ base.FillMethodBody(methodDefinition, methodContext);
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/AttributeRemovalProcessingLayer.cs b/Il2CppInterop.Generator/AttributeRemovalProcessingLayer.cs
new file mode 100644
index 00000000..e9de3683
--- /dev/null
+++ b/Il2CppInterop.Generator/AttributeRemovalProcessingLayer.cs
@@ -0,0 +1,94 @@
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Logging;
+using Cpp2IL.Core.Model.Contexts;
+using Cpp2IL.Core.Model.CustomAttributes;
+
+namespace Il2CppInterop.Generator;
+
+public class AttributeRemovalProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Id => "attribute_removal";
+ public override string Name => "Attribute Removal";
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ var isUnmanagedAttributeType = appContext.GetAssemblyByName("mscorlib")?.GetTypeByFullName("System.Runtime.CompilerServices.IsUnmanagedAttribute");
+
+ if (isUnmanagedAttributeType is null)
+ {
+ Logger.WarnNewline("IsUnmanagedAttribute not found. They cannot be recovered.", nameof(AttributeRemovalProcessingLayer));
+ }
+
+ var isUnmanagedAttributeConstructor = isUnmanagedAttributeType?.Methods.First(m => m.Name == ".ctor" && m.Parameters.Count == 0);
+
+ foreach (var assembly in appContext.Assemblies)
+ {
+ ClearAttributes(assembly);
+ }
+
+ foreach (var type in appContext.AllTypes)
+ {
+ ClearAttributes(type);
+
+ foreach (var genericParameter in type.GenericParameters)
+ {
+ ClearGenericParameterAttributes(genericParameter, isUnmanagedAttributeConstructor);
+ }
+
+ foreach (var field in type.Fields)
+ {
+ ClearAttributes(field);
+ }
+
+ foreach (var method in type.Methods)
+ {
+ ClearAttributes(method);
+
+ foreach (var parameter in method.Parameters)
+ {
+ ClearAttributes(parameter);
+ }
+
+ foreach (var genericParameter in method.GenericParameters)
+ {
+ ClearGenericParameterAttributes(genericParameter, isUnmanagedAttributeConstructor);
+ }
+ }
+
+ foreach (var property in type.Properties)
+ {
+ ClearAttributes(property);
+ }
+
+ foreach (var @event in type.Events)
+ {
+ ClearAttributes(@event);
+ }
+ }
+ }
+
+ private static void ClearGenericParameterAttributes(GenericParameterTypeAnalysisContext genericParameter, MethodAnalysisContext? isUnmanagedAttributeConstructor)
+ {
+ if (isUnmanagedAttributeConstructor is not null && HasIsUnmanagedAttribute(genericParameter))
+ {
+ ClearAttributes(genericParameter);
+ genericParameter.CustomAttributes ??= new(1);
+ genericParameter.CustomAttributes.Add(new AnalyzedCustomAttribute(isUnmanagedAttributeConstructor));
+ }
+ else
+ {
+ ClearAttributes(genericParameter);
+ }
+
+ static bool HasIsUnmanagedAttribute(GenericParameterTypeAnalysisContext genericParameter)
+ {
+ return genericParameter.HasCustomAttributeWithFullName("System.Runtime.CompilerServices.IsUnmanagedAttribute")
+ || genericParameter.HasCustomAttributeWithFullName("Il2CppSystem.Runtime.CompilerServices.IsUnmanagedAttribute");
+ }
+ }
+
+ private static void ClearAttributes(HasCustomAttributes context)
+ {
+ context.AttributeTypes?.Clear();
+ context.CustomAttributes?.Clear();
+ }
+}
diff --git a/Il2CppInterop.Generator/AttributesOverrideProcessingLayer.cs b/Il2CppInterop.Generator/AttributesOverrideProcessingLayer.cs
new file mode 100644
index 00000000..b55aa3f4
--- /dev/null
+++ b/Il2CppInterop.Generator/AttributesOverrideProcessingLayer.cs
@@ -0,0 +1,136 @@
+using System.Reflection;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+
+namespace Il2CppInterop.Generator;
+
+public class AttributesOverrideProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Id => "attributes_override";
+ public override string Name => "Attributes Override";
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ foreach (var assembly in appContext.Assemblies)
+ {
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ {
+ continue;
+ }
+
+ assembly.Culture = null;
+ assembly.Version = new(0, 0, 0, 0);
+ assembly.PublicKey = null;
+ assembly.PublicKeyToken = null;
+
+ foreach (var type in assembly.Types)
+ {
+ if (type.IsInjected)
+ {
+ continue;
+ }
+
+ if (type.IsStatic)
+ {
+ type.OverrideAttributes = type.Attributes & ~TypeAttributes.Sealed;
+ }
+
+ // Remove bad flags from type
+ {
+#pragma warning disable SYSLIB0050 // Type or member is obsolete
+ const TypeAttributes TypeFlagsToRemove =
+ TypeAttributes.AutoClass |
+ TypeAttributes.HasSecurity |
+ TypeAttributes.Import |
+ TypeAttributes.RTSpecialName |
+ TypeAttributes.Serializable |
+ TypeAttributes.UnicodeClass |
+ TypeAttributes.WindowsRuntime;
+#pragma warning restore SYSLIB0050 // Type or member is obsolete
+
+ type.OverrideAttributes = type.Attributes & ~TypeFlagsToRemove;
+ }
+
+ if (!type.IsValueType)
+ {
+ type.OverrideAttributes = type.Attributes & ~TypeAttributes.LayoutMask;
+ }
+
+ foreach (var genericParameter in type.GenericParameters)
+ {
+ const GenericParameterAttributes GenericParamFlagsToRemove =
+ GenericParameterAttributes.Covariant |
+ GenericParameterAttributes.Contravariant;
+ genericParameter.OverrideAttributes = genericParameter.Attributes & ~GenericParamFlagsToRemove;
+ }
+
+ foreach (var method in type.Methods)
+ {
+ if (method.IsInjected)
+ {
+ continue;
+ }
+
+ const MethodAttributes FlagsToRemove =
+ MethodAttributes.HasSecurity |
+ MethodAttributes.RequireSecObject |
+ MethodAttributes.UnmanagedExport |
+ MethodAttributes.PinvokeImpl;
+
+ method.OverrideAttributes = method.Attributes & ~FlagsToRemove;
+
+ method.OverrideImplAttributes = MethodImplAttributes.Managed;
+
+ foreach (var parameter in method.Parameters)
+ {
+ const ParameterAttributes ParamFlagsToRemove =
+ ParameterAttributes.Optional |
+ ParameterAttributes.HasDefault |
+ ParameterAttributes.HasFieldMarshal;
+ parameter.OverrideAttributes = parameter.Attributes & ~ParamFlagsToRemove;
+ }
+ }
+
+ foreach (var field in type.Fields)
+ {
+ if (field.IsInjected)
+ {
+ continue;
+ }
+
+ if (field.Attributes.HasFlag(FieldAttributes.Literal))
+ {
+ // Change constant fields to static readonly
+ field.OverrideAttributes = (field.Attributes & ~FieldAttributes.Literal) | FieldAttributes.InitOnly;
+ }
+ else
+ {
+ // Remove readonly from non-constant fields
+ field.OverrideAttributes = field.Attributes & ~FieldAttributes.InitOnly;
+ }
+
+ const FieldAttributes FlagsToRemove =
+ FieldAttributes.HasFieldRVA |
+ FieldAttributes.HasDefault |
+ FieldAttributes.HasFieldMarshal;
+
+ field.OverrideAttributes = field.Attributes & ~FlagsToRemove;
+ }
+
+ foreach (var property in type.Properties)
+ {
+ if (property.IsInjected)
+ {
+ continue;
+ }
+
+ const PropertyAttributes FlagsToRemove =
+ PropertyAttributes.HasDefault;
+
+ property.OverrideAttributes = property.Attributes & ~FlagsToRemove;
+ }
+
+ // There are no event attributes that need to be modified.
+ }
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/ByRefParameterOverloadProcessingLayer.cs b/Il2CppInterop.Generator/ByRefParameterOverloadProcessingLayer.cs
new file mode 100644
index 00000000..7d5bdef4
--- /dev/null
+++ b/Il2CppInterop.Generator/ByRefParameterOverloadProcessingLayer.cs
@@ -0,0 +1,188 @@
+using System.Diagnostics;
+using System.Reflection;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+using Il2CppInterop.Generator.Operands;
+using Il2CppInterop.Generator.Visitors;
+using Il2CppInterop.Runtime.InteropTypes;
+
+namespace Il2CppInterop.Generator;
+
+public sealed class ByRefParameterOverloadProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "ByRef Parameter Overloads";
+ public override string Id => "byref_parameter_overloads";
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ var byReference = appContext.ResolveTypeOrThrow(typeof(ByReference<>));
+ var byReference_Constructor = byReference.GetMethodByName(".ctor");
+ var byReference_CopyFrom = byReference.GetMethodByName(nameof(ByReference<>.CopyFrom));
+ var byReference_CopyTo = byReference.GetMethodByName(nameof(ByReference<>.CopyTo));
+ var byReference_Clear = byReference.GetMethodByName(nameof(ByReference<>.Clear));
+
+ var il2CppTypeHelper = appContext.ResolveTypeOrThrow(typeof(Il2CppType));
+ var il2CppTypeHelper_SizeOf = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.SizeOf));
+
+ foreach (var assembly in appContext.Assemblies)
+ {
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ continue;
+
+ foreach (var type in assembly.Types)
+ {
+ if (type.IsInjected)
+ continue;
+
+ if (type.IsInterface)
+ {
+ continue; // We don't add method overloads to interfaces
+ }
+
+ // for instead of foreach because we might be modifying the collection
+ for (var methodIndex = 0; methodIndex < type.Methods.Count; methodIndex++)
+ {
+ var method = type.Methods[methodIndex];
+ if (method.IsInjected || !method.IsPublic || method.IsSpecialName)
+ continue;
+
+ if (!method.Parameters.Any(p => p.DefaultParameterType is ByRefTypeAnalysisContext))
+ continue;
+
+ var newMethod = new InjectedMethodAnalysisContext(type, method.Name, appContext.SystemTypes.SystemVoidType, method.Attributes, [])
+ {
+ IsInjected = true,
+ };
+ type.Methods.Add(newMethod);
+
+ Debug.Assert(method.MostUserFriendlyOverload == method);
+ method.MostUserFriendlyOverload = newMethod;
+
+ newMethod.CopyGenericParameters(method, true);
+
+ var visitor = TypeReplacementVisitor.CreateForMethodCopying(method, newMethod);
+
+ newMethod.SetDefaultReturnType(visitor.Replace(method.ReturnType));
+
+ foreach (var parameter in method.Parameters)
+ {
+ TypeAnalysisContext parameterType;
+ if (parameter.DefaultParameterType is ByRefTypeAnalysisContext)
+ {
+ Debug.Assert(parameter.ParameterType is GenericInstanceTypeAnalysisContext { GenericArguments.Count: 1 });
+ var underlyingType = ((GenericInstanceTypeAnalysisContext)parameter.ParameterType).GenericArguments[0];
+ parameterType = visitor.Replace(underlyingType).MakeByReferenceType();
+ }
+ else
+ {
+ parameterType = visitor.Replace(parameter.ParameterType);
+ }
+
+ var newParameter = new InjectedParameterAnalysisContext(parameter.Name, parameterType, parameter.Attributes, parameter.ParameterIndex, newMethod);
+ newMethod.Parameters.Add(newParameter);
+ }
+
+ List instructions = new();
+ List variables = new();
+
+ LocalVariable?[] variableMap = new LocalVariable?[newMethod.Parameters.Count];
+
+ for (var i = 0; i < newMethod.Parameters.Count; i++)
+ {
+ var parameter = newMethod.Parameters[i];
+ if (parameter.ParameterType is ByRefTypeAnalysisContext { ElementType: { } underlyingType })
+ {
+ LocalVariable local = new(byReference.MakeGenericInstanceType([underlyingType]));
+ variables.Add(local);
+
+ instructions.Add(new Instruction(CilOpCodes.Call, il2CppTypeHelper_SizeOf.MakeGenericInstanceMethod(underlyingType)));
+ instructions.Add(new Instruction(CilOpCodes.Conv_U));
+ instructions.Add(new Instruction(CilOpCodes.Localloc));
+ instructions.Add(new Instruction(CilOpCodes.Newobj, new ConcreteGenericMethodAnalysisContext(byReference_Constructor, [underlyingType], [])));
+ instructions.Add(new Instruction(CilOpCodes.Stloc, local));
+
+ if (parameter.Attributes.HasFlag(ParameterAttributes.Out))
+ {
+ instructions.Add(new Instruction(CilOpCodes.Ldloca, local));
+ instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(byReference_Clear, [underlyingType], [])));
+ }
+ else
+ {
+ instructions.Add(new Instruction(CilOpCodes.Ldloca, local));
+ instructions.Add(new Instruction(CilOpCodes.Ldarg, parameter));
+ instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(byReference_CopyFrom, [underlyingType], [])));
+ }
+
+ variableMap[i] = local;
+ }
+ }
+
+ if (!newMethod.IsStatic)
+ {
+ instructions.Add(new Instruction(CilOpCodes.Ldarg, This.Instance));
+ }
+
+ for (var i = 0; i < newMethod.Parameters.Count; i++)
+ {
+ var local = variableMap[i];
+ if (local is not null)
+ {
+ instructions.Add(new Instruction(CilOpCodes.Ldloc, local));
+ }
+ else
+ {
+ instructions.Add(new Instruction(CilOpCodes.Ldarg, newMethod.Parameters[i]));
+ }
+ }
+
+ instructions.Add(new Instruction(newMethod.IsStatic || type.IsValueType ? CilOpCodes.Call : CilOpCodes.Callvirt, method.MaybeMakeConcreteGeneric(type.GenericParameters, newMethod.GenericParameters)));
+
+ LocalVariable? resultLocal;
+ if (newMethod.IsVoid)
+ {
+ resultLocal = null;
+ }
+ else
+ {
+ resultLocal = new(newMethod.ReturnType);
+ variables.Add(resultLocal);
+ instructions.Add(new Instruction(CilOpCodes.Stloc, resultLocal));
+ }
+
+ for (var i = 0; i < newMethod.Parameters.Count; i++)
+ {
+ var local = variableMap[i];
+ if (local is null)
+ {
+ continue;
+ }
+
+ var parameter = newMethod.Parameters[i];
+ if (parameter.Attributes.HasFlag(ParameterAttributes.In))
+ {
+ continue;
+ }
+
+ var underlyingType = ((ByRefTypeAnalysisContext)parameter.ParameterType).ElementType;
+
+ instructions.Add(new Instruction(CilOpCodes.Ldloca, local));
+ instructions.Add(new Instruction(CilOpCodes.Ldarg, parameter));
+ instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(byReference_CopyTo, [underlyingType], [])));
+ }
+
+ if (resultLocal is not null)
+ {
+ instructions.Add(new Instruction(CilOpCodes.Ldloc, resultLocal));
+ }
+
+ instructions.Add(new Instruction(CilOpCodes.Ret));
+
+ newMethod.PutExtraData(new NativeMethodBody()
+ {
+ Instructions = instructions,
+ LocalVariables = variables,
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/CleanRenamingProcessingLayer.cs b/Il2CppInterop.Generator/CleanRenamingProcessingLayer.cs
new file mode 100644
index 00000000..cd9ec205
--- /dev/null
+++ b/Il2CppInterop.Generator/CleanRenamingProcessingLayer.cs
@@ -0,0 +1,278 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+
+namespace Il2CppInterop.Generator;
+
+public partial class CleanRenamingProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "Clean Name Changes";
+
+ public override string Id => "cleanrenamer";
+
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ for (var i = 0; i < appContext.Assemblies.Count; i++)
+ {
+ var assembly = appContext.Assemblies[i];
+
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ continue;
+
+ RenameTypes(assembly.TopLevelTypes);
+
+ foreach (var type in assembly.Types)
+ {
+ if (type.IsInjected)
+ continue;
+
+ RenameMembers(type);
+ }
+
+ progressCallback?.Invoke(i, appContext.Assemblies.Count);
+ }
+ }
+
+ private static void RenameTypes(IEnumerable types, string? declaringTypeName = null)
+ {
+ HashSet<(string, string)> reservedFullNames = [];
+ foreach (var type in types)
+ {
+ if (type.IsInjected)
+ {
+ reservedFullNames.Add((type.Namespace, type.Name));
+ continue;
+ }
+
+ if (GenericTypeName.TryMatch(type.Name, out var typeName, out var genericCount))
+ {
+ type.OverrideName = $"{typeName.MakeValidCSharpName()}`{genericCount}";
+ }
+ else if (type.IsModuleType)
+ {
+ type.OverrideName = $"Module_{type.DeclaringAssembly.DefaultName.MakeValidCSharpName()}";
+ }
+ else if (type.IsPrivateImplementationDetailsType)
+ {
+ type.OverrideName = $"PrivateImplementationDetails_{type.DeclaringAssembly.DefaultName.MakeValidCSharpName()}";
+ }
+ else
+ {
+ type.OverrideName = type.Name.MakeValidCSharpName();
+ }
+ }
+
+ // Resolve any name conflicts
+ foreach (var type in types)
+ {
+ if (type.IsInjected)
+ continue;
+
+ var @namespace = type.Namespace;
+ var name = type.Name;
+ while (name == declaringTypeName || !reservedFullNames.Add((@namespace, name)))
+ {
+ name = $"_{name}";
+ }
+
+ type.OverrideName = name;
+ }
+
+ foreach (var type in types)
+ {
+ RenameTypes(type.NestedTypes, type.Name);
+ }
+ }
+
+ private static void RenameMembers(TypeAnalysisContext type)
+ {
+ // Virtual method lookup depends on the name being consistent with the base type.
+ // Special names also have special meaning and should not be changed.
+ const MethodAttributes FlagsWhichRequireNameConsistency =
+ MethodAttributes.SpecialName |
+ MethodAttributes.Abstract |
+ MethodAttributes.Virtual |
+ MethodAttributes.RTSpecialName;
+
+ var typeName = GetCSharpName(type);
+
+ // Events and properties do not get renamed
+
+ // Collect all reserved names
+ HashSet reservedNames =
+ [
+ "",
+ "_",
+ typeName,
+ .. GetConflictingNames(type.Properties),
+ .. GetConflictingNames(type.Events),
+ ];
+ if (StartsWithGetSet(typeName))
+ {
+ reservedNames.Add(typeName.Substring(4));
+ }
+
+ HashSet<(string Name, int SignatureHash)> existingMethods = [];
+
+ // Injected and special methods
+ foreach (var method in type.Methods)
+ {
+ if (method.IsInjected)
+ {
+ existingMethods.Add((method.Name, GetMethodSignatureHash(method)));
+ }
+ else if ((method.Attributes & FlagsWhichRequireNameConsistency) != 0)
+ {
+ if (method.IsStaticConstructor)
+ {
+ // Rename static constructor, but allow it to be renamed again if needed.
+ // Also remove special name flags, since it's no longer a special name.
+ method.OverrideName = "StaticConstructor";
+ method.OverrideAttributes = method.Attributes & ~(MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
+ }
+ else
+ {
+ existingMethods.Add((method.Name, GetMethodSignatureHash(method)));
+ }
+ }
+ }
+
+ // Normal methods
+ foreach (var method in type.Methods)
+ {
+ if (method.IsInjected)
+ continue;
+
+ if ((method.Attributes & FlagsWhichRequireNameConsistency) != 0)
+ continue;
+
+ var methodName = method.Name.MakeValidCSharpName();
+ var signatureHash = GetMethodSignatureHash(method);
+
+ while (reservedNames.Contains(methodName) || !existingMethods.Add((methodName, signatureHash)))
+ {
+ methodName = $"_{methodName}";
+ }
+ method.OverrideName = methodName;
+ }
+
+ reservedNames.AddRange(GetConflictingNames(type.Methods));
+
+ // Fields
+ foreach (var field in type.Fields)
+ {
+ if (field.IsInjected)
+ continue;
+
+ string fieldName;
+ if (TryMatchPropertyBackingField(field.Name, out var propertyName))
+ {
+ fieldName = $"{propertyName}_BackingField";
+ }
+ else if (type.Events.Any(e => e.Name == field.Name))
+ {
+ fieldName = $"{field.Name}_BackingField";
+ }
+ else
+ {
+ fieldName = field.Name.MakeValidCSharpName();
+ }
+
+ while (reservedNames.Contains(fieldName))
+ {
+ fieldName = $"_{fieldName}";
+ }
+ field.OverrideName = fieldName;
+ reservedNames.Add(field.Name);
+ }
+
+ // Parameters
+ var parameterNames = new HashSet();
+ foreach (var method in type.Methods)
+ {
+ if (method.IsInjected)
+ continue;
+
+ if (method.Parameters.Count == 0)
+ continue;
+
+ parameterNames.Clear();
+
+ for (var i = 0; i < method.Parameters.Count; i++)
+ {
+ var parameter = method.Parameters[i];
+ var parameterName = string.IsNullOrEmpty(parameter.Name)
+ ? $"parameter{i}"
+ : parameter.Name.MakeValidCSharpName();
+
+ while (reservedNames.Contains(parameterName) || !parameterNames.Add(parameterName))
+ {
+ parameterName = $"_{parameterName}";
+ }
+ parameter.OverrideName = parameterName;
+ }
+ }
+ }
+
+ private static IEnumerable GetConflictingNames(IEnumerable members)
+ {
+ foreach (var member in members)
+ {
+ yield return member.Name;
+ if (StartsWithGetSet(member.Name))
+ {
+ yield return member.Name.Substring(4);
+ }
+ }
+ }
+
+ private static bool StartsWithGetSet(string name)
+ {
+ return name.StartsWith("get_", StringComparison.Ordinal)
+ || name.StartsWith("set_", StringComparison.Ordinal);
+ }
+
+ private static string GetCSharpName(TypeAnalysisContext type)
+ {
+ if (GenericTypeName.TryMatch(type.Name, out var typeName, out _))
+ {
+ return typeName;
+ }
+ else
+ {
+ return type.Name;
+ }
+ }
+
+ private static int GetMethodSignatureHash(MethodAnalysisContext method)
+ {
+ HashCode hash = new();
+ hash.Add(method.GenericParameters.Count);
+ hash.Add(TypeAnalysisContextEqualityComparer.Instance.GetHashCode(method.ReturnType));
+ foreach (var param in method.Parameters)
+ {
+ hash.Add(TypeAnalysisContextEqualityComparer.Instance.GetHashCode(param.ParameterType));
+ }
+ return hash.ToHashCode();
+ }
+
+ private static bool TryMatchPropertyBackingField(string fieldName, [NotNullWhen(true)] out string? propertyName)
+ {
+ var match = PropertyBackingFieldRegex.Match(fieldName);
+ if (match.Success)
+ {
+ propertyName = match.Groups[1].Value;
+ return true;
+ }
+ else
+ {
+ propertyName = null;
+ return false;
+ }
+ }
+
+ [GeneratedRegex(@"^<(\w+)>k__BackingField$")]
+ private static partial Regex PropertyBackingFieldRegex { get; }
+}
diff --git a/Il2CppInterop.Generator/ConflictRenamingProcessingLayer.cs b/Il2CppInterop.Generator/ConflictRenamingProcessingLayer.cs
new file mode 100644
index 00000000..ace52f64
--- /dev/null
+++ b/Il2CppInterop.Generator/ConflictRenamingProcessingLayer.cs
@@ -0,0 +1,116 @@
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+
+namespace Il2CppInterop.Generator;
+
+///
+/// 3 virtual methods in Il2CppSystem.Object conflict with their System.Object counterparts.
+///
+public partial class ConflictRenamingProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "Conflict Renaming";
+ public override string Id => "conflictrenamer";
+
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ // ToString => ToIl2CppString
+ // GetHashCode => GetIl2CppHashCode
+ // Finalize => Il2CppFinalize
+
+ var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object");
+
+ for (var i = 0; i < appContext.Assemblies.Count; i++)
+ {
+ var assembly = appContext.Assemblies[i];
+
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ continue;
+
+ foreach (var type in assembly.Types)
+ {
+ if (type.IsInjected)
+ continue;
+
+ MaybeAppendUnderscore(type.Name, type);
+
+ foreach (var field in type.Fields)
+ {
+ MaybeAppendUnderscore(field.Name, field);
+ }
+
+ foreach (var property in type.Properties)
+ {
+ MaybeAppendUnderscore(property.Name, property);
+ }
+
+ foreach (var @event in type.Events)
+ {
+ MaybeAppendUnderscore(@event.Name, @event);
+ }
+
+ foreach (var method in type.Methods)
+ {
+ var name = method.Name;
+ switch (name)
+ {
+ case "ToString":
+ if (method.Parameters.Count == 0 && method.GenericParameters.Count == 0)
+ {
+ Debug.Assert(!method.IsInjected);
+ method.Name = "ToIl2CppString";
+ }
+ break;
+ case "GetHashCode":
+ if (method.Parameters.Count == 0 && method.GenericParameters.Count == 0)
+ {
+ Debug.Assert(!method.IsInjected);
+ method.Name = "GetIl2CppHashCode";
+ }
+ break;
+ case "Finalize":
+ if (method.Parameters.Count == 0 && method.GenericParameters.Count == 0)
+ {
+ Debug.Assert(!method.IsInjected);
+ method.Name = "Il2CppFinalize";
+ method.Overrides.Clear(); // Since this is no longer the Finalize method, it shouldn't have an explicit override.
+ }
+ break;
+ default:
+ MaybeAppendUnderscore(name, method);
+ break;
+ }
+ }
+ }
+
+ progressCallback?.Invoke(i, appContext.Assemblies.Count);
+ }
+ }
+
+ private static void MaybeAppendUnderscore(string name, HasCustomAttributesAndName context)
+ {
+ // If the name matches any of the patterns, append an underscore to avoid conflicts.
+ if (ToStringRegex.IsMatch(name))
+ {
+ context.Name = $"{name}_";
+ }
+ else if (GetHashCodeRegex.IsMatch(name))
+ {
+ context.Name = $"{name}_";
+ }
+ else if (FinalizeRegex.IsMatch(name))
+ {
+ context.Name = $"{name}_";
+ }
+ }
+
+ [GeneratedRegex(@"^ToIl2CppString_*$")]
+ private static partial Regex ToStringRegex { get; }
+
+ [GeneratedRegex(@"^GetIl2CppHashCode_*$")]
+ private static partial Regex GetHashCodeRegex { get; }
+
+ [GeneratedRegex(@"^Il2CppFinalize_*$")]
+ private static partial Regex FinalizeRegex { get; }
+}
diff --git a/Il2CppInterop.Generator/ConstantInitializationProcessingLayer.cs b/Il2CppInterop.Generator/ConstantInitializationProcessingLayer.cs
new file mode 100644
index 00000000..5840c40a
--- /dev/null
+++ b/Il2CppInterop.Generator/ConstantInitializationProcessingLayer.cs
@@ -0,0 +1,72 @@
+using System.Diagnostics;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+using Il2CppInterop.Generator.Operands;
+
+namespace Il2CppInterop.Generator;
+
+public class ConstantInitializationProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "Constant Initialization Processor";
+ public override string Id => "constant_initialization_processor";
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ foreach (var assembly in appContext.Assemblies)
+ {
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ continue;
+ foreach (var type in assembly.Types)
+ {
+ if (type.IsInjected)
+ continue;
+
+ foreach (var field in type.Fields)
+ {
+ if (field.ConstantValue is null || field.IsInjected)
+ continue;
+
+ Debug.Assert(field.IsStatic);
+
+ var instructions = type.GetOrCreateStaticConstructorInstructions();
+
+ object operandCast;
+ unchecked
+ {
+ operandCast = field.ConstantValue switch
+ {
+ bool value => value ? 1 : 0,
+ char value => (int)value,
+ byte value => (int)value,
+ sbyte value => (int)value,
+ ushort value => (int)value,
+ short value => (int)value,
+ uint value => (int)value,
+ ulong value => (long)value,
+ _ => field.ConstantValue,
+ };
+ }
+
+ var opCode = operandCast switch
+ {
+ long => CilOpCodes.Ldc_I8,
+ float => CilOpCodes.Ldc_R4,
+ double => CilOpCodes.Ldc_R8,
+ string => CilOpCodes.Ldstr,
+ _ => CilOpCodes.Ldc_I4
+ };
+
+ instructions.Add(new Instruction(opCode, operandCast));
+ if (opCode == CilOpCodes.Ldstr)
+ {
+ MonoIl2CppConversion.AddMonoToIl2CppStringConversion(instructions, appContext);
+ }
+ else
+ {
+ MonoIl2CppConversion.AddMonoToIl2CppConversion(instructions, field.FieldType);
+ }
+ instructions.Add(new Instruction(CilOpCodes.Stsfld, field.MaybeMakeConcreteGeneric(type.GenericParameters)));
+ }
+ }
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/ContextResolver.cs b/Il2CppInterop.Generator/ContextResolver.cs
new file mode 100644
index 00000000..37abd641
--- /dev/null
+++ b/Il2CppInterop.Generator/ContextResolver.cs
@@ -0,0 +1,326 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using AsmResolver.DotNet;
+using AsmResolver.DotNet.Signatures;
+using Cpp2IL.Core.Model.Contexts;
+using Il2CppInterop.Generator.Operands;
+
+namespace Il2CppInterop.Generator;
+
+public readonly struct ContextResolver
+{
+ private readonly AssemblyAnalysisContext referencedFrom;
+ private readonly TypeAnalysisContext? referencingType;
+ private readonly MethodAnalysisContext? referencingMethod;
+ private readonly RuntimeContext runtimeContext;
+
+ public ContextResolver(AssemblyAnalysisContext referencedFrom, RuntimeContext runtimeContext)
+ {
+ this.referencedFrom = referencedFrom;
+ this.runtimeContext = runtimeContext;
+ }
+
+ public ContextResolver(TypeAnalysisContext referencingType, RuntimeContext runtimeContext)
+ {
+ if (referencingType is ReferencedTypeAnalysisContext)
+ throw new ArgumentException("Must be a simple type", nameof(referencingType));
+ referencedFrom = referencingType.DeclaringAssembly;
+ this.referencingType = referencingType;
+ this.runtimeContext = runtimeContext;
+ }
+
+ public ContextResolver(MethodAnalysisContext referencingMethod, RuntimeContext runtimeContext)
+ {
+ if (referencingMethod is ConcreteGenericMethodAnalysisContext)
+ throw new ArgumentException("Must be a simple method", nameof(referencingMethod));
+ referencedFrom = referencingMethod.CustomAttributeAssembly;
+ referencingType = referencingMethod.DeclaringType;
+ this.referencingMethod = referencingMethod;
+ this.runtimeContext = runtimeContext;
+ }
+
+ public TypeAnalysisContext? Resolve(TypeSignature? type) => type switch
+ {
+ // Ordered roughly by frequency
+ TypeDefOrRefSignature typeDefOrRef => Resolve(typeDefOrRef),
+ CorLibTypeSignature primitive => Resolve(primitive),
+ GenericInstanceTypeSignature genericInstance => Resolve(genericInstance),
+ SzArrayTypeSignature szArray => Resolve(szArray.BaseType)?.MakeSzArrayType(),
+ GenericParameterSignature genericParameter => genericParameter.ParameterType switch
+ {
+ GenericParameterType.Type => TryGetGenericParameter(referencingType?.GenericParameters, genericParameter.Index),
+ _ => TryGetGenericParameter(referencingMethod?.GenericParameters, genericParameter.Index),
+ },
+ ByReferenceTypeSignature byRef => Resolve(byRef.BaseType)?.MakeByReferenceType(),
+ PointerTypeSignature pointer => Resolve(pointer.BaseType)?.MakePointerType(),
+ ArrayTypeSignature array => Resolve(array.BaseType)?.MakeArrayType(array.Rank),
+ PinnedTypeSignature pinned => Resolve(pinned.BaseType)?.MakePinnedType(),
+ CustomModifierTypeSignature customModifier => Resolve(customModifier),
+ BoxedTypeSignature boxed => Resolve(boxed.BaseType)?.MakeBoxedType(),
+ SentinelTypeSignature => new SentinelTypeAnalysisContext(referencedFrom),
+ _ => null
+ };
+
+ private static GenericParameterTypeAnalysisContext? TryGetGenericParameter(List? genericParameters, int index)
+ {
+ if (genericParameters is null || index < 0 || index >= genericParameters.Count)
+ return null;
+ return genericParameters[index];
+ }
+
+ public bool TryResolve(TypeSignature? type, [NotNullWhen(true)] out TypeAnalysisContext? result)
+ {
+ result = Resolve(type);
+ return result is not null;
+ }
+
+ private GenericInstanceTypeAnalysisContext? Resolve(GenericInstanceTypeSignature genericInstance)
+ {
+ return TryResolve(genericInstance.GenericType, out var genericType) && TryResolve(genericInstance.TypeArguments, out var genericArguments)
+ ? new GenericInstanceTypeAnalysisContext(genericType, genericArguments, referencedFrom)
+ : null;
+ }
+
+ private CustomModifierTypeAnalysisContext? Resolve(CustomModifierTypeSignature customModifier)
+ {
+ return TryResolve(customModifier.BaseType, out var baseType) && TryResolve(customModifier.ModifierType, out var modifier)
+ ? new CustomModifierTypeAnalysisContext(baseType, modifier, customModifier.IsRequired, referencedFrom)
+ : null;
+ }
+
+ private TypeAnalysisContext? Resolve(TypeDefOrRefSignature typeDefOrRef)
+ {
+ return Resolve(typeDefOrRef.Type);
+ }
+
+ private TypeAnalysisContext? Resolve(ITypeDefOrRef typeDefOrRef)
+ {
+ if (typeDefOrRef is TypeSpecification typeSpecification)
+ {
+ return Resolve(typeSpecification.Signature);
+ }
+
+ if (typeDefOrRef.DeclaringType is not null)
+ {
+ if (!TryResolve(typeDefOrRef.DeclaringType, out var declaringType))
+ return null;
+
+ foreach (var nestedType in declaringType.NestedTypes)
+ {
+ if (nestedType.Name == typeDefOrRef.Name)
+ {
+ return nestedType;
+ }
+ }
+
+ return null;
+ }
+
+ if (typeDefOrRef is not TypeDefinition)
+ {
+ typeDefOrRef = typeDefOrRef.TryResolve(runtimeContext) ?? typeDefOrRef;
+ }
+
+ var assemblyName = GetName(typeDefOrRef.Scope);
+ if (assemblyName == null)
+ return null;
+
+ var targetAssembly = referencedFrom.AppContext.Assemblies.FirstOrDefault(a => a.Name == assemblyName);
+ if (targetAssembly == null)
+ return null;
+
+ return targetAssembly.GetTypeByFullName(typeDefOrRef.FullName);
+
+ static string? GetName(IResolutionScope? scope) => scope switch
+ {
+ ModuleDefinition module => module.Assembly!.Name,
+ AssemblyReference assembly => assembly.Name,
+ _ => throw new NotImplementedException(),
+ };
+ }
+
+ private TypeAnalysisContext? Resolve(CorLibTypeSignature corLibType)
+ {
+ return referencedFrom.AppContext.Mscorlib.GetTypeByFullName(corLibType.FullName);
+ }
+
+ public TypeAnalysisContext? Resolve(ITypeDescriptor? type)
+ {
+ return type switch
+ {
+ TypeSignature signature => Resolve(signature),
+ ITypeDefOrRef typeDefOrRef => Resolve(typeDefOrRef),
+ _ => null,
+ };
+ }
+
+ public bool TryResolve(ITypeDescriptor? type, [NotNullWhen(true)] out TypeAnalysisContext? result)
+ {
+ result = Resolve(type);
+ return result is not null;
+ }
+
+ public IEnumerable Resolve(IEnumerable types)
+ {
+ foreach (var type in types)
+ {
+ yield return Resolve(type);
+ }
+ }
+
+ public bool TryResolve(IEnumerable types, [NotNullWhen(true)] out List? result)
+ {
+ result = [];
+ foreach (var type in types)
+ {
+ if (TryResolve(type, out var resolvedType))
+ {
+ result.Add(resolvedType);
+ }
+ else
+ {
+ result = null;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public FieldAnalysisContext? Resolve(IFieldDescriptor fieldDescriptor)
+ {
+ var declaringType = Resolve(fieldDescriptor.DeclaringType);
+ if (declaringType is null)
+ return null;
+
+ if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
+ {
+ var baseField = genericInstanceType.GenericType.TryGetFieldByName(fieldDescriptor.Name);
+ if (baseField is null)
+ return null;
+
+ return new ConcreteGenericFieldAnalysisContext(baseField, genericInstanceType);
+ }
+ else
+ {
+ return declaringType.TryGetFieldByName(fieldDescriptor.Name);
+ }
+ }
+
+ public bool TryResolve(IFieldDescriptor fieldDescriptor, [NotNullWhen(true)] out FieldAnalysisContext? result)
+ {
+ result = Resolve(fieldDescriptor);
+ return result is not null;
+ }
+
+ public object? Resolve(IMethodDescriptor methodDescriptor)
+ {
+ if (methodDescriptor is MethodSpecification specification)
+ {
+ return Resolve(specification);
+ }
+
+ var methodDefOrRef = (IMethodDefOrRef)methodDescriptor;
+
+ if (!TryResolve(methodDefOrRef.DeclaringType, out var declaringType))
+ return null;
+
+ var nonGenericDeclaringType = (declaringType as GenericInstanceTypeAnalysisContext)?.GenericType ?? declaringType;
+
+ if (nonGenericDeclaringType is ArrayTypeAnalysisContext arrayDeclaringType)
+ {
+ Debug.Assert(nonGenericDeclaringType == declaringType, "Array types should not be generic instances");
+ return methodDefOrRef.Name?.Value switch
+ {
+ "Get" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Get),
+ "Set" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Set),
+ "Address" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Address),
+ ".ctor" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Constructor),
+ _ => null,
+ };
+ }
+
+ Debug.Assert(nonGenericDeclaringType is not ReferencedTypeAnalysisContext);
+
+ var targetMethod = new ContextResolver(nonGenericDeclaringType, runtimeContext).ResolveInType(methodDefOrRef);
+ if (targetMethod is null)
+ return null;
+
+ if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
+ {
+ return new ConcreteGenericMethodAnalysisContext(targetMethod, genericInstanceType.GenericArguments, []);
+ }
+ else
+ {
+ return targetMethod;
+ }
+ }
+
+ public bool TryResolve(IMethodDescriptor methodDescriptor, [NotNullWhen(true)] out object? result)
+ {
+ result = Resolve(methodDescriptor);
+ return result is not null;
+ }
+
+ public MethodAnalysisContext? Resolve(MethodDefinition methodDefinition)
+ {
+ // The declaring type can be resolved with nothing, but resolution for the method itself requires a context.
+ return TryResolve(methodDefinition.DeclaringType, out var declaringType) ? new ContextResolver(declaringType, runtimeContext).ResolveInType(methodDefinition) : null;
+ }
+
+ public MethodAnalysisContext? ResolveInType(IMethodDefOrRef methodDefOrRef)
+ {
+ if (referencingType is null)
+ throw new InvalidOperationException("Cannot resolve method in type without a referencing type");
+
+ if (methodDefOrRef.Signature is null || methodDefOrRef.Signature.SentinelParameterTypes.Count > 0)
+ return null;
+
+ foreach (var methodContext in referencingType.Methods)
+ {
+ if (methodContext.Name != methodDefOrRef.Name)
+ continue;
+
+ if (methodContext.Parameters.Count != methodDefOrRef.Signature.ParameterTypes.Count)
+ continue;
+
+ if (methodContext.GenericParameters.Count != methodDefOrRef.Signature.GenericParameterCount)
+ continue;
+
+ if (methodContext.IsStatic == methodDefOrRef.Signature.HasThis)
+ continue;
+
+ if (methodContext.IsVoid == methodDefOrRef.Signature.ReturnsValue)
+ continue;
+
+ // We need to use a resolver for the method context to resolve potential method generic parameters correctly.
+ var methodResolver = new ContextResolver(methodContext, runtimeContext);
+
+ if (!methodResolver.TryResolve(methodDefOrRef.Signature.ReturnType, out var returnType) ||
+ !TypeAnalysisContextEqualityComparer.Instance.Equals(methodContext.ReturnType, returnType))
+ continue;
+
+ if (!methodResolver.TryResolve(methodDefOrRef.Signature.ParameterTypes, out var parameterTypes) ||
+ !methodContext.Parameters.Select(p => p.ParameterType).SequenceEqual(parameterTypes, TypeAnalysisContextEqualityComparer.Instance))
+ continue;
+
+ return methodContext;
+ }
+
+ return null;
+ }
+
+ public ConcreteGenericMethodAnalysisContext? Resolve(MethodSpecification specification)
+ {
+ if (specification.Method is null || specification.Signature is null)
+ return null;
+
+ var baseMethod = (MethodAnalysisContext?)Resolve(specification.Method);
+ if (baseMethod is null or { DeclaringType: null })
+ return null;
+
+ if (!TryResolve(specification.Signature.TypeArguments, out var methodTypeArguments))
+ return null;
+
+ return baseMethod.MakeGenericInstanceMethod(methodTypeArguments);
+ }
+}
diff --git a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs
deleted file mode 100644
index a5e8f2fd..00000000
--- a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using System.Diagnostics;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-using Il2CppInterop.Generator.Extensions;
-using Il2CppInterop.Generator.Utils;
-
-namespace Il2CppInterop.Generator.Contexts;
-
-[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
-public class AssemblyRewriteContext
-{
- public readonly RewriteGlobalContext GlobalContext;
-
- public readonly RuntimeAssemblyReferences Imports;
- private readonly Dictionary myNameTypeMap = new();
- private readonly Dictionary myNewTypeMap = new();
-
- private readonly Dictionary myOldTypeMap = new();
- public readonly AssemblyDefinition NewAssembly;
-
- public readonly AssemblyDefinition OriginalAssembly;
-
- public AssemblyRewriteContext(RewriteGlobalContext globalContext, AssemblyDefinition originalAssembly,
- AssemblyDefinition newAssembly)
- {
- OriginalAssembly = originalAssembly;
- NewAssembly = newAssembly;
- GlobalContext = globalContext;
-
- Imports = globalContext.ImportsMap.GetOrCreate(newAssembly.ManifestModule!,
- mod => new RuntimeAssemblyReferences(mod, globalContext));
- }
-
- public IEnumerable Types => myOldTypeMap.Values;
-
- public TypeRewriteContext GetContextForOriginalType(TypeDefinition type)
- {
- return myOldTypeMap[type];
- }
-
- public TypeRewriteContext? TryGetContextForOriginalType(TypeDefinition type)
- {
- return myOldTypeMap.TryGetValue(type, out var result) ? result : null;
- }
-
- public TypeRewriteContext GetContextForNewType(TypeDefinition type)
- {
- return myNewTypeMap[type];
- }
-
- public void RegisterTypeRewrite(TypeRewriteContext context)
- {
- if (context.OriginalType != null)
- myOldTypeMap[context.OriginalType] = context;
- myNewTypeMap[context.NewType] = context;
- myNameTypeMap[(context.OriginalType ?? context.NewType).FullName] = context;
- }
-
- public IMethodDefOrRef RewriteMethodRef(IMethodDefOrRef methodRef)
- {
- var newType = GlobalContext.GetNewTypeForOriginal(methodRef.DeclaringType!.Resolve()!);
- var newMethod = newType.GetMethodByOldMethod(methodRef.Resolve()!).NewMethod;
- return NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(newMethod);
- }
-
- public ITypeDefOrRef RewriteTypeRef(ITypeDescriptor typeRef)
- {
- return RewriteTypeRef(typeRef?.ToTypeSignature()).ToTypeDefOrRef();
- }
-
- public TypeSignature RewriteTypeRef(TypeSignature? typeRef)
- {
- if (typeRef == null)
- return Imports.Il2CppObjectBase;
-
- var sourceModule = NewAssembly.ManifestModule!;
-
- if (typeRef is ArrayBaseTypeSignature arrayType)
- {
- if (arrayType.Rank != 1)
- return Imports.Il2CppObjectBase;
-
- var elementType = arrayType.BaseType;
- if (elementType.FullName == "System.String")
- return Imports.Il2CppStringArray;
-
- var convertedElementType = RewriteTypeRef(elementType);
- if (elementType is GenericParameterSignature)
- return new GenericInstanceTypeSignature(Imports.Il2CppArrayBase.ToTypeDefOrRef(), false, convertedElementType);
-
- return new GenericInstanceTypeSignature(convertedElementType.IsValueType()
- ? Imports.Il2CppStructArray.ToTypeDefOrRef()
- : Imports.Il2CppReferenceArray.ToTypeDefOrRef(), false, convertedElementType);
- }
-
- if (typeRef is GenericParameterSignature genericParameter)
- {
- return new GenericParameterSignature(sourceModule, genericParameter.ParameterType, genericParameter.Index);
- }
-
- if (typeRef is ByReferenceTypeSignature byRef)
- return new ByReferenceTypeSignature(RewriteTypeRef(byRef.BaseType));
-
- if (typeRef is PointerTypeSignature pointerType)
- return new PointerTypeSignature(RewriteTypeRef(pointerType.BaseType));
-
- 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;
- }
-
- if (typeRef.IsPrimitive() || typeRef.FullName == "System.TypedReference")
- return sourceModule.ImportCorlibReference(typeRef.FullName);
-
- if (typeRef.FullName == "System.Void")
- return Imports.Module.Void();
-
- if (typeRef.FullName == "System.String")
- return Imports.Module.String();
-
- if (typeRef.FullName == "System.Object")
- return sourceModule.DefaultImporter.ImportType(GlobalContext.GetAssemblyByName("mscorlib")
- .GetTypeByName("System.Object").NewType).ToTypeSignature();
-
- if (typeRef.FullName == "System.Attribute")
- return sourceModule.DefaultImporter.ImportType(GlobalContext.GetAssemblyByName("mscorlib")
- .GetTypeByName("System.Attribute").NewType).ToTypeSignature();
-
- var originalTypeDef = typeRef.Resolve()!;
- var targetAssembly = GlobalContext.GetNewAssemblyForOriginal(originalTypeDef.DeclaringModule!.Assembly!);
- var target = targetAssembly.GetContextForOriginalType(originalTypeDef).NewType;
-
- return sourceModule.DefaultImporter.ImportType(target).ToTypeSignature();
- }
-
- public TypeRewriteContext GetTypeByName(string name)
- {
- return myNameTypeMap[name];
- }
-
- public TypeRewriteContext? TryGetTypeByName(string name)
- {
- return myNameTypeMap.TryGetValue(name, out var result) ? result : null;
- }
-
- private string GetDebuggerDisplay()
- {
- return NewAssembly.FullName;
- }
-}
diff --git a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs b/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs
deleted file mode 100644
index a2420e68..00000000
--- a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System.Diagnostics;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.PE.DotNet.Metadata.Tables;
-using Il2CppInterop.Generator.Extensions;
-using Il2CppInterop.Generator.Utils;
-
-namespace Il2CppInterop.Generator.Contexts;
-
-[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
-public class FieldRewriteContext
-{
- private static readonly string[] MethodAccessTypeLabels =
- {"CompilerControlled", "Private", "FamAndAssem", "Internal", "Protected", "FamOrAssem", "Public"};
-
- public readonly TypeRewriteContext DeclaringType;
- public readonly FieldDefinition OriginalField;
-
- public readonly MemberReference PointerField;
- public readonly string UnmangledName;
-
- public FieldRewriteContext(TypeRewriteContext declaringType, FieldDefinition originalField,
- Dictionary? renamedFieldCounts = null)
- {
- DeclaringType = declaringType;
- OriginalField = originalField;
-
- UnmangledName = UnmangleFieldName(originalField, declaringType.AssemblyContext.GlobalContext.Options,
- renamedFieldCounts);
- var pointerField = new FieldDefinition("NativeFieldInfoPtr_" + UnmangledName,
- FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly,
- declaringType.AssemblyContext.Imports.Module.IntPtr());
-
- declaringType.NewType.Fields.Add(pointerField);
-
- Debug.Assert(pointerField.Signature is not null);
- PointerField = new MemberReference(DeclaringType.SelfSubstitutedRef, pointerField.Name, new FieldSignature(pointerField.Signature!.FieldType));
- }
-
- private string UnmangleFieldNameBase(FieldDefinition field, GeneratorOptions options)
- {
- if (options.PassthroughNames)
- return field.Name!;
-
- if (!field.Name.IsObfuscated(options))
- {
- return field.Name.MakeValidInSource();
- }
-
- Debug.Assert(field.Signature is not null);
- 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);
- }
-
- private string UnmangleFieldName(FieldDefinition field, GeneratorOptions options,
- Dictionary? renamedFieldCounts)
- {
- if (options.PassthroughNames)
- return field.Name!;
-
- if (!field.Name.IsObfuscated(options))
- {
- return field.Name.MakeValidInSource();
- }
-
- if (renamedFieldCounts == null) throw new ArgumentNullException(nameof(renamedFieldCounts));
-
- var unmangleFieldNameBase = UnmangleFieldNameBase(field, options);
-
- renamedFieldCounts.TryGetValue(unmangleFieldNameBase, out var count);
- renamedFieldCounts[unmangleFieldNameBase] = count + 1;
-
- unmangleFieldNameBase += "_" + count;
-
- if (DeclaringType.AssemblyContext.GlobalContext.Options.RenameMap.TryGetValue(
- DeclaringType.NewType.GetNamespacePrefix() + "." + DeclaringType.NewType.Name + "::" +
- unmangleFieldNameBase, out var newName))
- unmangleFieldNameBase = newName;
-
- return unmangleFieldNameBase;
- }
-
- private string GetDebuggerDisplay()
- {
- return DeclaringType.NewType.FullName + "::" + UnmangledName;
- }
-}
diff --git a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs b/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs
deleted file mode 100644
index efdcd85c..00000000
--- a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs
+++ /dev/null
@@ -1,316 +0,0 @@
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Text;
-using AsmResolver;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.PE.DotNet.Metadata.Tables;
-using Il2CppInterop.Common.XrefScans;
-using Il2CppInterop.Generator.Extensions;
-using Il2CppInterop.Generator.Passes;
-using Il2CppInterop.Generator.Utils;
-
-namespace Il2CppInterop.Generator.Contexts;
-
-[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
-public class MethodRewriteContext
-{
- private static readonly string[] MethodAccessTypeLabels =
- {"CompilerControlled", "Private", "FamAndAssem", "Internal", "Protected", "FamOrAssem", "Public"};
-
- private static readonly (MethodSemanticsAttributes, string)[] SemanticsToCheck =
- {
- (MethodSemanticsAttributes.Setter, "_set"),
- (MethodSemanticsAttributes.Getter, "_get"),
- (MethodSemanticsAttributes.Other, "_oth"),
- (MethodSemanticsAttributes.AddOn, "_add"),
- (MethodSemanticsAttributes.RemoveOn, "_rem"),
- (MethodSemanticsAttributes.Fire, "_fire")
- };
-
- public readonly TypeRewriteContext DeclaringType;
-
- public readonly long FileOffset;
- public readonly MethodDefinition NewMethod;
- public readonly MethodDefinition OriginalMethod;
-
- public readonly bool OriginalNameObfuscated;
- public readonly long Rva;
-
- public readonly List XrefScanResults = new();
-
- public long MetadataInitFlagRva;
- public long MetadataInitTokenRva;
-
- public MethodRewriteContext(TypeRewriteContext declaringType, MethodDefinition originalMethod)
- {
- DeclaringType = declaringType;
- OriginalMethod = originalMethod;
-
- var passthroughNames = declaringType.AssemblyContext.GlobalContext.Options.PassthroughNames;
-
- OriginalNameObfuscated = !passthroughNames &&
- (OriginalMethod.Name?.IsObfuscated(declaringType.AssemblyContext.GlobalContext
- .Options) ?? false);
-
- var newAttributes = AdjustAttributes(originalMethod.Attributes, originalMethod.Name == "Finalize");
- var newSignature = (newAttributes & MethodAttributes.Static) != 0
- ? MethodSignature.CreateStatic(declaringType.AssemblyContext.Imports.Module.Void(), originalMethod.GenericParameters.Count)
- : MethodSignature.CreateInstance(declaringType.AssemblyContext.Imports.Module.Void(), originalMethod.GenericParameters.Count);
- var newMethod = new MethodDefinition("", newAttributes, newSignature);
- newMethod.CilMethodBody = new();
- NewMethod = newMethod;
-
- HasExtensionAttribute =
- originalMethod.CustomAttributes.Any(x => x.AttributeType()?.FullName == typeof(ExtensionAttribute).FullName);
-
- if (HasExtensionAttribute)
- newMethod.CustomAttributes.Add(
- new CustomAttribute(declaringType.AssemblyContext.Imports.Module.ExtensionAttributeCtor()));
-
- if (originalMethod.HasGenericParameters())
- {
- var genericParams = originalMethod.GenericParameters;
-
- foreach (var oldParameter in genericParams)
- {
- newMethod.GenericParameters.Add(new GenericParameter(
- oldParameter.Name.MakeValidInSource(),
- oldParameter.Attributes.StripValueTypeConstraint()));
- }
- }
-
- if (!Pass15GenerateMemberContexts.HasObfuscatedMethods && !passthroughNames &&
- originalMethod.Name.IsObfuscated(declaringType.AssemblyContext.GlobalContext.Options))
- Pass15GenerateMemberContexts.HasObfuscatedMethods = true;
-
- FileOffset = originalMethod.ExtractOffset();
- // Workaround for garbage file offsets passed by Cpp2IL
- if (FileOffset < 0) FileOffset = 0;
- Rva = originalMethod.ExtractRva();
- if (FileOffset != 0)
- declaringType.AssemblyContext.GlobalContext.MethodStartAddresses.Add(FileOffset);
- }
-
- public Utf8String? UnmangledName { get; private set; }
- public string? UnmangledNameWithSignature { get; private set; }
-
- public TypeDefinition? GenericInstantiationsStore { get; private set; }
- public ITypeDefOrRef? GenericInstantiationsStoreSelfSubstRef { get; private set; }
- public ITypeDefOrRef? GenericInstantiationsStoreSelfSubstMethodRef { get; private set; }
- public MemberReference NonGenericMethodInfoPointerField { get; private set; } = null!; // Initialized in CtorPhase2
-
- public bool HasExtensionAttribute { get; }
-
- public void CtorPhase2()
- {
- UnmangledName = UnmangleMethodName();
- UnmangledNameWithSignature = UnmangleMethodNameWithSignature();
-
- NewMethod.Name = UnmangledName;
- NewMethod.Signature!.ReturnType = DeclaringType.AssemblyContext.RewriteTypeRef(OriginalMethod.Signature?.ReturnType);
-
- var nonGenericMethodInfoPointerField = new FieldDefinition(
- "NativeMethodInfoPtr_" + UnmangledNameWithSignature,
- FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly,
- DeclaringType.AssemblyContext.Imports.Module.IntPtr());
- DeclaringType.NewType.Fields.Add(nonGenericMethodInfoPointerField);
-
- NonGenericMethodInfoPointerField = new MemberReference(DeclaringType.SelfSubstitutedRef, nonGenericMethodInfoPointerField.Name,
- new FieldSignature(nonGenericMethodInfoPointerField.Signature!.FieldType));
-
- if (OriginalMethod.HasGenericParameters())
- {
- var genericParams = OriginalMethod.GenericParameters;
- var genericMethodInfoStoreType = new TypeDefinition("",
- "MethodInfoStoreGeneric_" + UnmangledNameWithSignature + "`" + genericParams.Count,
- TypeAttributes.NestedPrivate | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit,
- DeclaringType.AssemblyContext.Imports.Module.Object().ToTypeDefOrRef());
- DeclaringType.NewType.NestedTypes.Add(genericMethodInfoStoreType);
- GenericInstantiationsStore = genericMethodInfoStoreType;
-
- var selfSubstRef = new GenericInstanceTypeSignature(genericMethodInfoStoreType, false);
- var selfSubstMethodRef = new GenericInstanceTypeSignature(genericMethodInfoStoreType, false);
-
- for (var index = 0; index < genericParams.Count; index++)
- {
- var oldParameter = genericParams[index];
- var genericParameter = new GenericParameter(oldParameter.Name.MakeValidInSource());
- genericMethodInfoStoreType.GenericParameters.Add(genericParameter);
- selfSubstRef.TypeArguments.Add(genericParameter.ToTypeSignature());
- var newParameter = NewMethod.GenericParameters[index];
- selfSubstMethodRef.TypeArguments.Add(newParameter.ToTypeSignature());
-
- foreach (var oldConstraint in oldParameter.Constraints)
- {
- if (oldConstraint.IsSystemValueType() || oldConstraint.IsInterface())
- continue;
-
- if (oldConstraint.IsSystemEnum())
- {
- newParameter.Constraints.Add(new GenericParameterConstraint(
- DeclaringType.AssemblyContext.Imports.Module.Enum().ToTypeDefOrRef()));
- continue;
- }
-
- newParameter.Constraints.Add(new GenericParameterConstraint(
- DeclaringType.AssemblyContext.RewriteTypeRef(oldConstraint.Constraint?.ToTypeSignature()).ToTypeDefOrRef()));
- }
- }
-
- var pointerField = new FieldDefinition("Pointer", FieldAttributes.Assembly | FieldAttributes.Static,
- DeclaringType.AssemblyContext.Imports.Module.IntPtr());
- genericMethodInfoStoreType.Fields.Add(pointerField);
-
- GenericInstantiationsStoreSelfSubstRef = DeclaringType.NewType.DeclaringModule!.DefaultImporter.ImportType(selfSubstRef.ToTypeDefOrRef());
- GenericInstantiationsStoreSelfSubstMethodRef =
- DeclaringType.NewType.DeclaringModule.DefaultImporter.ImportType(selfSubstMethodRef.ToTypeDefOrRef());
- }
-
- DeclaringType.NewType.Methods.Add(NewMethod);
- }
-
- private MethodAttributes AdjustAttributes(MethodAttributes original, bool stripVirtual)
- {
- original &= ~MethodAttributes.MemberAccessMask; // todo: handle Object overload correctly
- original &= ~MethodAttributes.PInvokeImpl;
- original &= ~MethodAttributes.Abstract;
- if (stripVirtual) original &= ~MethodAttributes.Virtual;
- original &= ~MethodAttributes.Final;
- if (stripVirtual) original &= ~MethodAttributes.NewSlot;
- original &= ~MethodAttributes.ReuseSlot;
- original &= ~MethodAttributes.CheckAccessOnOverride;
- original |= MethodAttributes.Public;
- return original;
- }
-
- private string UnmangleMethodName()
- {
- var method = OriginalMethod;
-
- if (method.Name == "GetType" && method.Parameters.Count == 0)
- return "GetIl2CppType";
-
- if (DeclaringType.AssemblyContext.GlobalContext.Options.PassthroughNames)
- return method.Name!;
-
- if (method.Name == ".ctor")
- return ".ctor";
-
- if (method.Name.IsObfuscated(DeclaringType.AssemblyContext.GlobalContext.Options))
- return UnmangleMethodNameWithSignature();
-
- return method.Name.MakeValidInSource();
- }
-
- private string ProduceMethodSignatureBase()
- {
- var method = OriginalMethod;
-
- string name;
- if (method.Name.IsObfuscated(DeclaringType.AssemblyContext.GlobalContext.Options))
- name = "Method";
- else
- name = method.Name.MakeValidInSource();
-
- if (method.Name == "GetType" && method.Parameters.Count == 0)
- name = "GetIl2CppType";
-
- var builder = new StringBuilder();
- builder.Append(name);
- builder.Append('_');
- builder.Append(MethodAccessTypeLabels[(int)(method.Attributes & MethodAttributes.MemberAccessMask)]);
- if (method.IsAbstract) builder.Append("_Abstract");
- if (method.IsVirtual) builder.Append("_Virtual");
- if (method.IsStatic) builder.Append("_Static");
- if (method.IsFinal) builder.Append("_Final");
- if (method.IsNewSlot) builder.Append("_New");
- if (method.Semantics is not null)
- foreach (var (semantic, str) in SemanticsToCheck)
- if ((semantic & method.Semantics.Attributes) != 0)
- builder.Append(str);
-
- builder.Append('_');
- builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(method.Signature?.ReturnType).GetUnmangledName(method.DeclaringType, method));
-
- foreach (var param in method.Parameters)
- {
- builder.Append('_');
- builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(param.ParameterType).GetUnmangledName(method.DeclaringType, method));
- }
-
- var address = Rva;
- if (address != 0 && Pass15GenerateMemberContexts.HasObfuscatedMethods &&
- !Pass16ScanMethodRefs.NonDeadMethods.Contains(address)) builder.Append("_PDM");
-
- return builder.ToString();
- }
-
-
- private string UnmangleMethodNameWithSignature()
- {
- var unmangleMethodNameWithSignature = ProduceMethodSignatureBase() + "_" + DeclaringType.Methods
- .Where(ParameterSignatureMatchesThis).TakeWhile(it => it != this).Count();
-
- if (DeclaringType.AssemblyContext.GlobalContext.Options.RenameMap.TryGetValue(
- DeclaringType.NewType.GetNamespacePrefix() + "." + DeclaringType.NewType.Name + "::" + unmangleMethodNameWithSignature, out var newNameByType))
- {
- unmangleMethodNameWithSignature = newNameByType;
- }
- else if (DeclaringType.AssemblyContext.GlobalContext.Options.RenameMap.TryGetValue(
- DeclaringType.NewType.GetNamespacePrefix() + "::" + unmangleMethodNameWithSignature, out var newName))
- {
- unmangleMethodNameWithSignature = newName;
- }
-
- return unmangleMethodNameWithSignature;
- }
-
- private bool ParameterSignatureMatchesThis(MethodRewriteContext otherRewriteContext)
- {
- var aM = otherRewriteContext.OriginalMethod;
- var bM = OriginalMethod;
-
- if (!otherRewriteContext.OriginalNameObfuscated)
- return false;
-
- var comparisonMask = MethodAttributes.MemberAccessMask | MethodAttributes.Static | MethodAttributes.Final |
- MethodAttributes.Abstract | MethodAttributes.Virtual | MethodAttributes.NewSlot;
- if ((aM.Attributes & comparisonMask) !=
- (bM.Attributes & comparisonMask))
- return false;
-
- if (aM.Semantics?.Attributes != bM.Semantics?.Attributes)
- return false;
-
- if (aM.Signature?.ReturnType.FullName != bM.Signature?.ReturnType.FullName)
- return false;
-
- var a = aM.Parameters;
- var b = bM.Parameters;
-
- if (a.Count != b.Count)
- return false;
-
- for (var i = 0; i < a.Count; i++)
- if (a[i].ParameterType.FullName != b[i].ParameterType.FullName)
- return false;
-
- if (Pass15GenerateMemberContexts.HasObfuscatedMethods)
- {
- var addressA = otherRewriteContext.Rva;
- var addressB = Rva;
- if (addressA != 0 && addressB != 0)
- if (Pass16ScanMethodRefs.NonDeadMethods.Contains(addressA) !=
- Pass16ScanMethodRefs.NonDeadMethods.Contains(addressB))
- return false;
- }
-
- return true;
- }
-
- private string GetDebuggerDisplay()
- {
- return NewMethod.FullName;
- }
-}
diff --git a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs b/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs
deleted file mode 100644
index 76c1ea2b..00000000
--- a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs
+++ /dev/null
@@ -1,243 +0,0 @@
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.PE.DotNet.Metadata.Tables;
-using Il2CppInterop.Generator.Extensions;
-using Il2CppInterop.Generator.MetadataAccess;
-using Il2CppInterop.Generator.Utils;
-
-namespace Il2CppInterop.Generator.Contexts;
-
-public class RewriteGlobalContext : IDisposable
-{
- internal readonly List MethodStartAddresses = new();
-
- private readonly Dictionary myAssemblies = new();
- private readonly Dictionary myAssembliesByOld = new();
- private readonly Dictionary myAssembliesByNew = new();
- internal readonly Dictionary PreviousRenamedTypes = new();
- internal readonly Dictionary RenamedTypes = new();
-
- internal readonly Dictionary<(object?, string, int), List> RenameGroups = new();
-
- internal readonly Dictionary ImportsMap = new();
-
- public RewriteGlobalContext(GeneratorOptions options, IIl2CppMetadataAccess gameAssemblies,
- IMetadataAccess unityAssemblies)
- {
- Options = options;
- GameAssemblies = gameAssemblies;
- UnityAssemblies = unityAssemblies;
-
- Il2CppAssemblyResolver assemblyResolver = new();
-
- foreach (var sourceAssembly in gameAssemblies.Assemblies)
- {
- var assemblyName = sourceAssembly.Name!;
- if (assemblyName == "Il2CppDummyDll")
- {
- continue;
- }
-
- var newAssembly = new AssemblyDefinition(sourceAssembly.Name.UnSystemify(options), sourceAssembly.Version);
- var newModule = new ModuleDefinition(sourceAssembly.ManifestModule?.Name.UnSystemify(options), CorlibReferences.TargetCorlib);
- newAssembly.Modules.Add(newModule);
-
- newModule.MetadataResolver = new DefaultMetadataResolver(assemblyResolver);
- assemblyResolver.AddToCache(newAssembly);
-
- var assemblyRewriteContext = new AssemblyRewriteContext(this, sourceAssembly, newAssembly);
- AddAssemblyContext(assemblyName, assemblyRewriteContext);
- }
- }
-
- public GeneratorOptions Options { get; }
- public IIl2CppMetadataAccess GameAssemblies { get; }
- public IMetadataAccess UnityAssemblies { get; }
-
- public IEnumerable Assemblies => myAssemblies.Values;
- public AssemblyRewriteContext CorLib => myAssemblies["mscorlib"];
-
- internal bool HasGcWbarrierFieldWrite { get; set; }
-
- public void Dispose()
- {
- UnityAssemblies.Dispose();
- }
-
- internal void AddAssemblyContext(string assemblyName, AssemblyRewriteContext context)
- {
- myAssemblies[assemblyName] = context;
- if (context.OriginalAssembly != null)
- myAssembliesByOld[context.OriginalAssembly] = context;
- myAssembliesByNew[context.NewAssembly] = context;
- }
-
- public AssemblyRewriteContext GetNewAssemblyForOriginal(AssemblyDefinition oldAssembly)
- {
- return myAssembliesByOld[oldAssembly];
- }
-
- public TypeRewriteContext GetNewTypeForOriginal(TypeDefinition originalType)
- {
- return GetNewAssemblyForOriginal(originalType.DeclaringModule!.Assembly!)
- .GetContextForOriginalType(originalType);
- }
-
- public TypeRewriteContext? TryGetNewTypeForOriginal(TypeDefinition originalType)
- {
- if (!myAssembliesByOld.TryGetValue(originalType.DeclaringModule!.Assembly!, out var assembly))
- return null;
- return assembly.TryGetContextForOriginalType(originalType);
- }
-
- public TypeRewriteContext.TypeSpecifics JudgeSpecificsByOriginalType(TypeSignature typeRef)
- {
- if (typeRef.IsPrimitive() || typeRef is PointerTypeSignature || typeRef.FullName == "System.TypedReference")
- return TypeRewriteContext.TypeSpecifics.BlittableStruct;
- if (typeRef
- is CorLibTypeSignature { ElementType: ElementType.String or ElementType.Object }
- or ArrayBaseTypeSignature
- or ByReferenceTypeSignature
- or GenericParameterSignature
- or GenericInstanceTypeSignature)
- return TypeRewriteContext.TypeSpecifics.ReferenceType;
-
- var fieldTypeContext = GetNewTypeForOriginal(typeRef.Resolve() ?? throw new($"Could not resolve {typeRef.FullName}"));
- return fieldTypeContext.ComputedTypeSpecifics;
- }
-
- public AssemblyRewriteContext GetAssemblyByName(string name)
- {
- return myAssemblies[name];
- }
-
- public AssemblyRewriteContext? TryGetAssemblyByName(string? name)
- {
- if (name is null)
- return null;
-
- if (myAssemblies.TryGetValue(name, out var result))
- return result;
-
- if (name == "netstandard")
- return myAssemblies.TryGetValue("mscorlib", out var result2) ? result2 : null;
-
- return null;
- }
-
- public AssemblyRewriteContext GetContextForNewAssembly(AssemblyDefinition assembly)
- {
- return myAssembliesByNew[assembly];
- }
-
- public TypeRewriteContext GetContextForNewType(TypeDefinition type)
- {
- return GetContextForNewAssembly(type.DeclaringModule!.Assembly!).GetContextForNewType(type);
- }
-
- public MethodDefinition? CreateParamsMethod(MethodDefinition originalMethod, MethodDefinition newMethod,
- RuntimeAssemblyReferences imports, Func resolve)
- {
- if (newMethod.Name == "Invoke")
- return null;
-
- var paramsParameters = originalMethod.Parameters.Where(parameter =>
- parameter.IsParamsArray() && resolve(((ArrayBaseTypeSignature)parameter.ParameterType).BaseType) is not null and not GenericParameterSignature
- ).ToArray();
-
- if (paramsParameters.Any())
- {
- var paramsMethod = new MethodDefinition(newMethod.Name, newMethod.Attributes, MethodSignatureCreator.CreateMethodSignature(newMethod.Attributes, newMethod.Signature!.ReturnType, newMethod.Signature.GenericParameterCount));
- foreach (var genericParameter in originalMethod.GenericParameters)
- {
- var newGenericParameter = new GenericParameter(genericParameter.Name.MakeValidInSource(), genericParameter.Attributes);
-
- foreach (var constraint in genericParameter.Constraints)
- {
- var newConstraintType = constraint.Constraint != null ? resolve(constraint.Constraint.ToTypeSignature())?.ToTypeDefOrRef() : null;
- var newConstraint = new GenericParameterConstraint(newConstraintType);
-
- // We don't need to copy custom attributes on constraints for generic parameters because Il2Cpp doesn't support them.
-
- newGenericParameter.Constraints.Add(newConstraint);
- }
-
- // Similarly, custom attributes on generic parameters are also stripped by Il2Cpp, so we don't need to copy them.
-
- paramsMethod.GenericParameters.Add(newGenericParameter);
- }
-
- foreach (var originalParameter in originalMethod.Parameters)
- {
- var isParams = paramsParameters.Contains(originalParameter);
-
- TypeSignature? convertedType;
- if (isParams && originalParameter.ParameterType is ArrayBaseTypeSignature arrayType)
- {
- var resolvedElementType = resolve(arrayType.GetElementType());
- convertedType = arrayType is SzArrayTypeSignature
- ? resolvedElementType?.MakeSzArrayType()
- : resolvedElementType?.MakeArrayType(arrayType.Rank);
- }
- else
- {
- convertedType = resolve(originalParameter.ParameterType);
- }
-
- if (convertedType == null)
- {
- throw new($"Could not resolve parameter type {originalParameter.ParameterType.FullName}");
- }
-
- var parameter = paramsMethod.AddParameter(convertedType, originalParameter.Name, originalParameter.Definition?.Attributes ?? default);
-
- if (isParams)
- parameter.Definition!.CustomAttributes.Add(new CustomAttribute(newMethod.DeclaringModule!.ParamArrayAttributeCtor()));
- }
-
- paramsMethod.CilMethodBody = new();
- var body = paramsMethod.CilMethodBody.Instructions;
-
- if (!newMethod.IsStatic)
- {
- body.Add(OpCodes.Ldarg_0);
- }
-
- for (var i = 0; i < newMethod.Parameters.Count; i++)
- {
- body.Add(OpCodes.Ldarg, newMethod.Parameters[i]);
-
- var parameter = originalMethod.Parameters[i];
- if (paramsParameters.Contains(parameter))
- {
- var parameterType = (ArrayBaseTypeSignature)parameter.ParameterType;
-
- IMethodDescriptor constructorReference;
-
- var elementType = parameterType.GetElementType();
- if (elementType.FullName == "System.String")
- {
- constructorReference = imports.Il2CppStringArrayctor.Value;
- }
- else
- {
- var convertedElementType = resolve(elementType)!;
-
- constructorReference = imports.Module.DefaultImporter.ImportMethod(convertedElementType.IsValueType()
- ? imports.Il2CppStructArrayctor.Get(convertedElementType)
- : imports.Il2CppRefrenceArrayctor.Get(convertedElementType));
- }
-
- body.Add(OpCodes.Newobj, constructorReference);
- }
- }
-
- body.Add(OpCodes.Call, newMethod);
- body.Add(OpCodes.Ret);
-
- return paramsMethod;
- }
-
- return null;
- }
-}
diff --git a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs b/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs
deleted file mode 100644
index 23b9e8b9..00000000
--- a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-using System.Diagnostics;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.PE.DotNet.Metadata.Tables;
-using Il2CppInterop.Generator.Extensions;
-using Il2CppInterop.Generator.Utils;
-
-namespace Il2CppInterop.Generator.Contexts;
-
-[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
-public class TypeRewriteContext
-{
- public enum TypeSpecifics
- {
- NotComputed,
- Computing,
- ReferenceType,
- BlittableStruct,
- NonBlittableStruct
- }
-
- public readonly AssemblyRewriteContext AssemblyContext;
-
- private readonly Dictionary myFieldContexts = new();
- private readonly Dictionary myMethodContexts = new();
- private readonly Dictionary myMethodContextsByName = new();
- public readonly TypeDefinition NewType;
-
- public readonly bool OriginalNameWasObfuscated;
-#nullable disable
- // OriginalType is null for unstripped types, but we don't want to warn anywhere,
- // including in the constructor, so we disable all null tracking for this field.
- public readonly TypeDefinition OriginalType;
-#nullable enable
-
- public TypeSpecifics ComputedTypeSpecifics;
-
- public TypeRewriteContext(AssemblyRewriteContext assemblyContext, TypeDefinition? originalType,
- TypeDefinition newType)
- {
- AssemblyContext = assemblyContext ?? throw new ArgumentNullException(nameof(assemblyContext));
- OriginalType = originalType;
- NewType = newType ?? throw new ArgumentNullException(nameof(newType));
-
- if (OriginalType == null) return;
-
- OriginalNameWasObfuscated = OriginalType.Name != NewType.Name;
- if (OriginalNameWasObfuscated)
- NewType.CustomAttributes.Add(new CustomAttribute(
- (ICustomAttributeType)assemblyContext.Imports.ObfuscatedNameAttributector.Value,
- new CustomAttributeSignature(new CustomAttributeArgument(assemblyContext.Imports.Module.String(), OriginalType.FullName))));
-
- if (!OriginalType.IsValueType())
- 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.
- public IFieldDescriptor ClassPointerFieldRef { get; private set; } = null!;
- public ITypeDefOrRef SelfSubstitutedRef { get; private set; } = null!;
-
- public IEnumerable Fields => myFieldContexts.Values;
- public IEnumerable Methods => myMethodContexts.Values;
-
- public void AddMembers()
- {
- if (NewType.HasGenericParameters())
- {
- var genericInstanceType = new GenericInstanceTypeSignature(NewType, NewType.IsValueType());
- foreach (var newTypeGenericParameter in NewType.GenericParameters)
- genericInstanceType.TypeArguments.Add(newTypeGenericParameter.ToTypeSignature());
- SelfSubstitutedRef = NewType.DeclaringModule!.DefaultImporter.ImportTypeSignature(genericInstanceType).ToTypeDefOrRef();
- var genericTypeRef = new GenericInstanceTypeSignature(
- AssemblyContext.Imports.Il2CppClassPointerStore.ToTypeDefOrRef(),
- AssemblyContext.Imports.Il2CppClassPointerStore.IsValueType(),
- SelfSubstitutedRef.ToTypeSignature());
- ClassPointerFieldRef = ReferenceCreator.CreateFieldReference("NativeClassPtr", AssemblyContext.Imports.Module.IntPtr(),
- NewType.DeclaringModule.DefaultImporter.ImportType(genericTypeRef.ToTypeDefOrRef()));
- }
- else
- {
- SelfSubstitutedRef = NewType;
- var genericTypeRef = new GenericInstanceTypeSignature(
- AssemblyContext.Imports.Il2CppClassPointerStore.ToTypeDefOrRef(),
- AssemblyContext.Imports.Il2CppClassPointerStore.IsValueType());
- if (OriginalType.ToTypeSignature().IsPrimitive() || OriginalType.FullName == "System.String")
- genericTypeRef.TypeArguments.Add(
- NewType.DeclaringModule!.ImportCorlibReference(OriginalType.FullName));
- else
- genericTypeRef.TypeArguments.Add(SelfSubstitutedRef.ToTypeSignature());
- ClassPointerFieldRef = ReferenceCreator.CreateFieldReference("NativeClassPtr", AssemblyContext.Imports.Module.IntPtr(),
- NewType.DeclaringModule!.DefaultImporter.ImportType(genericTypeRef.ToTypeDefOrRef()));
- }
-
- if (OriginalType.IsEnum) return;
-
- var renamedFieldCounts = new Dictionary();
-
- foreach (var originalTypeField in OriginalType.Fields)
- myFieldContexts[originalTypeField] = new FieldRewriteContext(this, originalTypeField, renamedFieldCounts);
-
- var hasExtensionMethods = false;
-
- foreach (var originalTypeMethod in OriginalType.Methods)
- {
- if (originalTypeMethod.IsStatic && originalTypeMethod.IsConstructor)
- continue;
- if (originalTypeMethod.IsConstructor
- && originalTypeMethod.Parameters is [{ ParameterType: CorLibTypeSignature { ElementType: ElementType.I } }])
- continue;
- var modules = this.AssemblyContext.GlobalContext.Assemblies.Select(a => a.OriginalAssembly.ManifestModule!);
-
- var methodRewriteContext = new MethodRewriteContext(this, originalTypeMethod);
- myMethodContexts[originalTypeMethod] = methodRewriteContext;
- myMethodContextsByName[originalTypeMethod.Name!] = methodRewriteContext;
-
- if (methodRewriteContext.HasExtensionAttribute) hasExtensionMethods = true;
- }
-
- if (hasExtensionMethods)
- NewType.CustomAttributes.Add(new CustomAttribute(AssemblyContext.Imports.Module.ExtensionAttributeCtor()));
- }
-
- public FieldRewriteContext GetFieldByOldField(FieldDefinition field)
- {
- return myFieldContexts[field];
- }
-
- public MethodRewriteContext GetMethodByOldMethod(MethodDefinition method)
- {
- return myMethodContexts[method];
- }
-
- public MethodRewriteContext? TryGetMethodByOldMethod(MethodDefinition method)
- {
- return myMethodContexts.TryGetValue(method, out var result) ? result : null;
- }
-
- public MethodRewriteContext? TryGetMethodByName(string name)
- {
- return myMethodContextsByName.TryGetValue(name, out var result) ? result : null;
- }
-
- public MethodRewriteContext? TryGetMethodByUnityAssemblyMethod(MethodDefinition method)
- {
- foreach (var methodRewriteContext in myMethodContexts)
- {
- var originalMethod = methodRewriteContext.Value.OriginalMethod;
- if (originalMethod.Name != method.Name) continue;
- if (originalMethod.Parameters.Count != method.Parameters.Count) continue;
- var badMethod = false;
- for (var i = 0; i < originalMethod.Parameters.Count; i++)
- if (originalMethod.Parameters[i].ParameterType.FullName != method.Parameters[i].ParameterType.FullName)
- {
- badMethod = true;
- break;
- }
-
- if (badMethod) continue;
-
- return methodRewriteContext.Value;
- }
-
- return null;
- }
-
- public FieldRewriteContext? TryGetFieldByUnityAssemblyField(FieldDefinition field)
- {
- foreach (var fieldRewriteContext in myFieldContexts)
- {
- var originalField = fieldRewriteContext.Value.OriginalField;
- if (originalField.Name != field.Name) continue;
-
- if (originalField.Signature?.FieldType.FullName != field.Signature?.FieldType.FullName)
- continue;
-
- return fieldRewriteContext.Value;
- }
-
- return null;
- }
-
- private string GetDebuggerDisplay()
- {
- return NewType.FullName;
- }
-}
diff --git a/Il2CppInterop.Generator/DelegateConversionProcessingLayer.cs b/Il2CppInterop.Generator/DelegateConversionProcessingLayer.cs
new file mode 100644
index 00000000..1b96ad77
--- /dev/null
+++ b/Il2CppInterop.Generator/DelegateConversionProcessingLayer.cs
@@ -0,0 +1,317 @@
+using System.Reflection;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+using Il2CppInterop.Generator.Operands;
+using Il2CppInterop.Generator.Visitors;
+using Il2CppInterop.Runtime;
+
+namespace Il2CppInterop.Generator;
+
+public class DelegateConversionProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "Delegate Conversion";
+
+ public override string Id => "delegate_conversion";
+
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ PolyfillActionFuncDelegates(appContext);
+
+ var multicastDelegateType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.MulticastDelegate");
+ var asyncCallbackType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.AsyncCallback");
+ var iasyncResultType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.IAsyncResult");
+
+ var delegateSupportType = appContext.ResolveTypeOrThrow(typeof(DelegateSupport));
+ var delegateSupportMethod = delegateSupportType.GetMethodByName(nameof(DelegateSupport.ConvertDelegate));
+
+ var il2CppSystemDelegate = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Delegate");
+ var il2CppSystemDelegateCombine = il2CppSystemDelegate.Methods.Single(m => m.Name == "Combine" && m.Parameters.Count == 2);
+ var il2CppSystemDelegateRemove = il2CppSystemDelegate.GetMethodByName("Remove");
+
+ foreach (var assembly in appContext.Assemblies)
+ {
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ continue;
+
+ // for rather than foreach, as we will be adding items to the collection
+ for (var typeIndex = 0; typeIndex < assembly.Types.Count; typeIndex++)
+ {
+ var type = assembly.Types[typeIndex];
+ if (type.BaseType is not { Namespace: "Il2CppSystem", Name: "MulticastDelegate" })
+ continue;
+
+ // Remove variance on generic parameters because only interfaces and (real) delegates can have variance.
+ // https://github.com/dotnet/csharplang/discussions/2498
+ // https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance
+ {
+ // Before: Func
+ // After: Func
+ //
+ // This inherently makes some code invalid, both normal and unstripped.
+ // There's probably no (good) way to fix that because the runtime doesn't actually change the type.
+ // https://lab.razor.fyi/#hZDBSsRADIaRetCcxCfIcXrpA7giqIsFURFcEDw5zoYamM6UmbSwLD36BB68-wC-nyels3VVFM0hhPzk-38CzxnAZfBV0HVh4u5j1kZ2FV4tolA9AWjaO8sGjdUx4pGOhEvov2-nFLijOe596ith5MISEBGjaGGDnec5nmt2Kk_rlTjUsXfRWyquAwudsSNVkoxwlRclyWzRkMqLk9baC11TPkm3fepN4E4LfdgcGmHv9odEB1iSDMMPx0DSBodTP7tnV_1PG8Mk4DrY78y15R_U9IrRXKXfiQ4VyVdkDz2cZqF1N9tbLw9vT6-0s3m78Q4
+ // One way the problem might be solved is to:
+ // * Generate a nested interface for every Il2Cpp delegate type, eg IFunc.
+ // * Replace all occurances of the class type with the interface type, everywhere.
+ // * During unstripping, redirect references to the class methods to the interface methods.
+
+ foreach (var genericParameter in type.GenericParameters)
+ {
+ genericParameter.OverrideAttributes = genericParameter.Attributes & ~GenericParameterAttributes.VarianceMask;
+ }
+ }
+
+ var invokeMethod = type.TryGetMethodByName("Invoke");
+ if (invokeMethod is null)
+ {
+ // The delegate does not have an Invoke method.
+ // This can happen if the delegate is unstripped, but a parameter type could not unstripped.
+ continue;
+ }
+
+ TypeAnalysisContext managedDelegateType;
+
+ if (invokeMethod.Parameters.Count <= 16
+ && invokeMethod.ReturnType is not PointerTypeAnalysisContext and not ByRefTypeAnalysisContext
+ && invokeMethod.Parameters.All(p => p.ParameterType is not PointerTypeAnalysisContext and not ByRefTypeAnalysisContext))
+ {
+ // We can use a System delegate
+
+ if (!invokeMethod.IsVoid)
+ {
+ var systemType = invokeMethod.Parameters.Count switch
+ {
+ 0 => typeof(Func<>),
+ 1 => typeof(Func<,>),
+ 2 => typeof(Func<,,>),
+ 3 => typeof(Func<,,,>),
+ 4 => typeof(Func<,,,,>),
+ 5 => typeof(Func<,,,,,>),
+ 6 => typeof(Func<,,,,,,>),
+ 7 => typeof(Func<,,,,,,,>),
+ 8 => typeof(Func<,,,,,,,,>),
+ 9 => typeof(Func<,,,,,,,,,>),
+ 10 => typeof(Func<,,,,,,,,,,>),
+ 11 => typeof(Func<,,,,,,,,,,,>),
+ 12 => typeof(Func<,,,,,,,,,,,,>),
+ 13 => typeof(Func<,,,,,,,,,,,,,>),
+ 14 => typeof(Func<,,,,,,,,,,,,,,>),
+ 15 => typeof(Func<,,,,,,,,,,,,,,,>),
+ 16 => typeof(Func<,,,,,,,,,,,,,,,,>),
+ _ => default!, // unreachable
+ };
+ managedDelegateType = appContext.ResolveTypeOrThrow(systemType)
+ .MakeGenericInstanceType(invokeMethod.Parameters.Select(p => p.ParameterType).Append(invokeMethod.ReturnType));
+ }
+ else if (invokeMethod.Parameters.Count > 0)
+ {
+ var systemType = invokeMethod.Parameters.Count switch
+ {
+ 1 => typeof(Action<>),
+ 2 => typeof(Action<,>),
+ 3 => typeof(Action<,,>),
+ 4 => typeof(Action<,,,>),
+ 5 => typeof(Action<,,,,>),
+ 6 => typeof(Action<,,,,,>),
+ 7 => typeof(Action<,,,,,,>),
+ 8 => typeof(Action<,,,,,,,>),
+ 9 => typeof(Action<,,,,,,,,>),
+ 10 => typeof(Action<,,,,,,,,,>),
+ 11 => typeof(Action<,,,,,,,,,,>),
+ 12 => typeof(Action<,,,,,,,,,,,>),
+ 13 => typeof(Action<,,,,,,,,,,,,>),
+ 14 => typeof(Action<,,,,,,,,,,,,,>),
+ 15 => typeof(Action<,,,,,,,,,,,,,,>),
+ 16 => typeof(Action<,,,,,,,,,,,,,,,>),
+ _ => default!, // unreachable
+ };
+ managedDelegateType = appContext.ResolveTypeOrThrow(systemType).MakeGenericInstanceType(invokeMethod.Parameters.Select(p => p.ParameterType));
+ }
+ else
+ {
+ managedDelegateType = appContext.ResolveTypeOrThrow(typeof(Action));
+ }
+ }
+ else
+ {
+ // We need to create a new delegate type
+
+ var name = type.Name is "Delegate" ? "Converted" : "Delegate"; // Name can't be the same as the declaring type
+ managedDelegateType = type.InjectNestedType(
+ name,
+ multicastDelegateType);
+
+ managedDelegateType.CopyGenericParameters(type, true);
+
+ TypeAnalysisContext returnType;
+ List parameterTypes = invokeMethod.Parameters.Select(p => p.ParameterType).ToList();
+ {
+ var genericParameterDictionary = Enumerable.Range(0, type.GenericParameters.Count)
+ .ToDictionary(i => type.GenericParameters[i], i => managedDelegateType.GenericParameters[i]);
+ var replacementVisitor = new TypeReplacementVisitor(genericParameterDictionary);
+ replacementVisitor.Modify(parameterTypes);
+ returnType = replacementVisitor.Replace(invokeMethod.ReturnType);
+ }
+
+ // Constructor
+ {
+ managedDelegateType.Methods.Add(new InjectedMethodAnalysisContext(
+ managedDelegateType,
+ ".ctor",
+ appContext.SystemTypes.SystemVoidType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+ [appContext.SystemTypes.SystemObjectType, appContext.SystemTypes.SystemIntPtrType],
+ defaultImplAttributes: MethodImplAttributes.Runtime));
+ }
+
+ // Invoke
+ {
+ managedDelegateType.Methods.Add(new InjectedMethodAnalysisContext(
+ managedDelegateType,
+ "Invoke",
+ returnType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
+ parameterTypes.ToArray(),
+ defaultImplAttributes: MethodImplAttributes.Runtime));
+ }
+
+ // BeginInvoke
+ {
+ managedDelegateType.Methods.Add(new InjectedMethodAnalysisContext(
+ managedDelegateType,
+ "BeginInvoke",
+ iasyncResultType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
+ parameterTypes.Append(asyncCallbackType).Append(appContext.SystemTypes.SystemObjectType).ToArray(),
+ defaultImplAttributes: MethodImplAttributes.Runtime));
+ }
+
+ // EndInvoke
+ {
+ managedDelegateType.Methods.Add(new InjectedMethodAnalysisContext(
+ managedDelegateType,
+ "EndInvoke",
+ returnType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
+ [iasyncResultType],
+ defaultImplAttributes: MethodImplAttributes.Runtime));
+ }
+ }
+
+ var concreteType = type.HasGenericParameters ? type.MakeGenericInstanceType(type.GenericParameters) : type;
+ var explicitConversion = new InjectedMethodAnalysisContext(
+ type,
+ "op_Explicit",
+ concreteType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
+ [managedDelegateType]);
+ type.Methods.Add(explicitConversion);
+ explicitConversion.PutExtraData(new()
+ {
+ Instructions =
+ [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Call, delegateSupportMethod.MakeGenericInstanceMethod(concreteType)),
+ new Instruction(CilOpCodes.Ret),
+ ]
+ });
+
+ var addition = new InjectedMethodAnalysisContext(
+ type,
+ "op_Addition",
+ concreteType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
+ [concreteType, concreteType]);
+ type.Methods.Add(addition);
+ addition.PutExtraData(new()
+ {
+ Instructions =
+ [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Ldarg_1),
+ new Instruction(CilOpCodes.Call, il2CppSystemDelegateCombine),
+ new Instruction(CilOpCodes.Castclass, concreteType),
+ new Instruction(CilOpCodes.Ret),
+ ]
+ });
+
+ var subtraction = new InjectedMethodAnalysisContext(
+ type,
+ "op_Subtraction",
+ concreteType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
+ [concreteType, concreteType]);
+ type.Methods.Add(subtraction);
+ subtraction.PutExtraData(new()
+ {
+ Instructions =
+ [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Ldarg_1),
+ new Instruction(CilOpCodes.Call, il2CppSystemDelegateRemove),
+ new Instruction(CilOpCodes.Castclass, concreteType),
+ new Instruction(CilOpCodes.Ret),
+ ]
+ });
+ }
+ }
+ }
+
+ ///
+ /// mscorlib only contains Action and Func delegates with up to 8 parameters.
+ /// However, System.Private.CoreLib contains Action and Func delegates with up to 16 parameters.
+ /// This method will create the Action and Func delegates with more than 8 parameters,
+ /// so that they can be used when the mscorlib assembly references are replaced with System.Private.CoreLib references.
+ ///
+ ///
+ private static void PolyfillActionFuncDelegates(ApplicationAnalysisContext appContext)
+ {
+ var mscorlib = appContext.AssembliesByName["mscorlib"];
+
+ ReadOnlySpan types =
+ [
+ typeof(Action),
+ typeof(Action<>),
+ typeof(Action<,>),
+ typeof(Action<,,>),
+ typeof(Action<,,,>),
+ typeof(Action<,,,,>),
+ typeof(Action<,,,,,>),
+ typeof(Action<,,,,,,>),
+ typeof(Action<,,,,,,,>),
+ typeof(Action<,,,,,,,,>),
+ typeof(Action<,,,,,,,,,>),
+ typeof(Action<,,,,,,,,,,>),
+ typeof(Action<,,,,,,,,,,,>),
+ typeof(Action<,,,,,,,,,,,,>),
+ typeof(Action<,,,,,,,,,,,,,>),
+ typeof(Action<,,,,,,,,,,,,,,>),
+ typeof(Action<,,,,,,,,,,,,,,,>),
+ typeof(Func<>),
+ typeof(Func<,>),
+ typeof(Func<,,>),
+ typeof(Func<,,,>),
+ typeof(Func<,,,,>),
+ typeof(Func<,,,,,>),
+ typeof(Func<,,,,,,>),
+ typeof(Func<,,,,,,,>),
+ typeof(Func<,,,,,,,,>),
+ typeof(Func<,,,,,,,,,>),
+ typeof(Func<,,,,,,,,,,>),
+ typeof(Func<,,,,,,,,,,,>),
+ typeof(Func<,,,,,,,,,,,,>),
+ typeof(Func<,,,,,,,,,,,,,>),
+ typeof(Func<,,,,,,,,,,,,,,>),
+ typeof(Func<,,,,,,,,,,,,,,,>),
+ typeof(Func<,,,,,,,,,,,,,,,,>),
+ ];
+
+ foreach (var type in types)
+ {
+ if (mscorlib.GetTypeByFullName(type.FullName!) is not null)
+ continue;
+
+ mscorlib.InjectType(type).InjectContentFromSourceType();
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/EnumProcessingLayer.cs b/Il2CppInterop.Generator/EnumProcessingLayer.cs
new file mode 100644
index 00000000..c0195472
--- /dev/null
+++ b/Il2CppInterop.Generator/EnumProcessingLayer.cs
@@ -0,0 +1,287 @@
+using System.Diagnostics;
+using System.Reflection;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+using Il2CppInterop.Generator.Operands;
+
+namespace Il2CppInterop.Generator;
+
+public class EnumProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "Enum Processor";
+ public override string Id => "enum_processor";
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object");
+ var il2CppSystemValueType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.ValueType");
+ var il2CppSystemEnum = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Enum");
+
+ foreach (var assembly in appContext.Assemblies)
+ {
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ continue;
+
+ foreach (var type in assembly.Types)
+ {
+ if (type.DefaultBaseType != il2CppSystemEnum)
+ continue;
+
+ // Sequential layout is required to ensure that this struct has the same memory layout as a C# enum.
+ type.OverrideAttributes = (type.Attributes & ~TypeAttributes.LayoutMask) | TypeAttributes.SequentialLayout;
+
+ var valueField = type.Fields.First(f => f.Name == "value__");
+
+ valueField.OverrideAttributes = FieldAttributes.Private;
+
+ Debug.Assert(valueField.FieldType == valueField.DefaultFieldType, "Field type should not be overriden.");
+
+ var il2CppType = valueField.FieldType;
+ var monoType = appContext.Mscorlib.GetTypeByFullNameOrThrow($"System.{il2CppType.Name}");
+
+ Debug.Assert(monoType != il2CppType);
+
+ type.EnumIl2CppUnderlyingType = il2CppType;
+ type.EnumMonoUnderlyingType = monoType;
+
+ // Constructor
+ var constructor = new InjectedMethodAnalysisContext(
+ type,
+ ".ctor",
+ appContext.SystemTypes.SystemVoidType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+ [il2CppType],
+ ["value"])
+ {
+ IsInjected = true,
+ };
+ {
+ type.Methods.Add(constructor);
+
+ var methodBody = new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Ldarg_1),
+ new Instruction(CilOpCodes.Stfld, valueField),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ };
+
+ constructor.PutExtraData(methodBody);
+ }
+
+ #region Conversions
+ // Conversion: enum -> il2cpp
+ var conversionEnumIl2Cpp = new InjectedMethodAnalysisContext(
+ type,
+ "op_Explicit",
+ il2CppType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
+ [type],
+ ["value"])
+ {
+ IsInjected = true,
+ };
+ {
+ type.Methods.Add(conversionEnumIl2Cpp);
+ var methodBody = new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarga, conversionEnumIl2Cpp.Parameters[0]),
+ new Instruction(CilOpCodes.Ldfld, valueField),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ };
+ conversionEnumIl2Cpp.PutExtraData(methodBody);
+ }
+
+ // Conversion: il2cpp -> enum
+ var conversionIl2CppEnum = new InjectedMethodAnalysisContext(
+ type,
+ "op_Explicit",
+ type,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
+ [il2CppType],
+ ["value"])
+ {
+ IsInjected = true,
+ };
+ {
+ type.Methods.Add(conversionIl2CppEnum);
+ var methodBody = new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Newobj, constructor),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ };
+ conversionIl2CppEnum.PutExtraData(methodBody);
+ }
+
+ // Conversion: enum -> mono
+ var conversionEnumMono = new InjectedMethodAnalysisContext(
+ type,
+ "op_Explicit",
+ monoType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
+ [type],
+ ["value"])
+ {
+ IsInjected = true,
+ };
+ {
+ type.Methods.Add(conversionEnumMono);
+ var methodBody = new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Call, conversionEnumIl2Cpp),
+ new Instruction(CilOpCodes.Call, il2CppType.GetImplicitConversionTo(monoType)),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ };
+ conversionEnumMono.PutExtraData(methodBody);
+ }
+
+ // Conversion: mono -> enum
+ var conversionMonoEnum = new InjectedMethodAnalysisContext(
+ type,
+ "op_Explicit",
+ type,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
+ [monoType],
+ ["value"])
+ {
+ IsInjected = true,
+ };
+ {
+ type.Methods.Add(conversionMonoEnum);
+ var methodBody = new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Call, il2CppType.GetImplicitConversionFrom(monoType)),
+ new Instruction(CilOpCodes.Call, conversionIl2CppEnum),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ };
+ conversionMonoEnum.PutExtraData(methodBody);
+ }
+ #endregion
+
+ #region Bitwise Operators
+ // &
+ {
+ var method = new InjectedMethodAnalysisContext(
+ type,
+ "op_BitwiseAnd",
+ type,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
+ [type, type])
+ {
+ IsInjected = true,
+ };
+
+ type.Methods.Add(method);
+
+ method.PutExtraData(new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Call, conversionEnumMono),
+ new Instruction(CilOpCodes.Ldarg_1),
+ new Instruction(CilOpCodes.Call, conversionEnumMono),
+ new Instruction(CilOpCodes.And),
+ new Instruction(CilOpCodes.Call, conversionMonoEnum),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ });
+ }
+
+ // |
+ {
+ var method = new InjectedMethodAnalysisContext(
+ type,
+ "op_BitwiseOr",
+ type,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
+ [type, type])
+ {
+ IsInjected = true,
+ };
+
+ type.Methods.Add(method);
+
+ method.PutExtraData(new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Call, conversionEnumMono),
+ new Instruction(CilOpCodes.Ldarg_1),
+ new Instruction(CilOpCodes.Call, conversionEnumMono),
+ new Instruction(CilOpCodes.Or),
+ new Instruction(CilOpCodes.Call, conversionMonoEnum),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ });
+ }
+
+ // ^
+ {
+ var method = new InjectedMethodAnalysisContext(
+ type,
+ "op_ExclusiveOr",
+ type,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
+ [type, type])
+ {
+ IsInjected = true,
+ };
+
+ type.Methods.Add(method);
+
+ method.PutExtraData(new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Call, conversionEnumMono),
+ new Instruction(CilOpCodes.Ldarg_1),
+ new Instruction(CilOpCodes.Call, conversionEnumMono),
+ new Instruction(CilOpCodes.Xor),
+ new Instruction(CilOpCodes.Call, conversionMonoEnum),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ });
+ }
+
+ // ~
+ {
+ var method = new InjectedMethodAnalysisContext(
+ type,
+ "op_OnesComplement",
+ type,
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
+ [type])
+ {
+ IsInjected = true,
+ };
+
+ type.Methods.Add(method);
+
+ method.PutExtraData(new NativeMethodBody()
+ {
+ Instructions = [
+ new Instruction(CilOpCodes.Ldarg_0),
+ new Instruction(CilOpCodes.Call, conversionEnumMono),
+ new Instruction(CilOpCodes.Not),
+ new Instruction(CilOpCodes.Call, conversionMonoEnum),
+ new Instruction(CilOpCodes.Ret)
+ ]
+ });
+ }
+ #endregion
+ }
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/EventProcessingLayer.cs b/Il2CppInterop.Generator/EventProcessingLayer.cs
new file mode 100644
index 00000000..9d04b70a
--- /dev/null
+++ b/Il2CppInterop.Generator/EventProcessingLayer.cs
@@ -0,0 +1,66 @@
+using System.Reflection;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+using Cpp2IL.Core.Model.CustomAttributes;
+using Il2CppInterop.Common.Attributes;
+
+namespace Il2CppInterop.Generator;
+
+public class EventProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "Event Processor";
+
+ public override string Id => "event_processor";
+
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ // C# requires events to have a delegate type and will not allow event syntax without it.
+ // https://github.com/ds5678/Il2CppEventTest
+ // We remove the event definitions and add attributes to the add/remove/invoke methods instead.
+
+ var il2CppEventAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppEventAttribute));
+ var il2CppEventAttributeConstructor = il2CppEventAttribute.GetMethodByName(".ctor");
+
+ var il2CppMemberAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppMemberAttribute));
+ var il2CppMemberAttributeName = il2CppMemberAttribute.GetPropertyByName(nameof(Il2CppMemberAttribute.Name));
+
+ foreach (var assembly in appContext.Assemblies)
+ {
+ if (assembly.IsReferenceAssembly || assembly.IsInjected)
+ continue;
+
+ foreach (var type in assembly.Types)
+ {
+ if (type.IsInjected)
+ continue;
+
+ for (var i = 0; i < type.Events.Count; i++)
+ {
+ var @event = type.Events[i];
+ if (@event.IsInjected)
+ continue;
+
+ AddAttribute(@event.Adder, @event.DefaultName, il2CppEventAttributeConstructor, il2CppMemberAttributeName);
+ AddAttribute(@event.Remover, @event.DefaultName, il2CppEventAttributeConstructor, il2CppMemberAttributeName);
+ AddAttribute(@event.Invoker, @event.DefaultName, il2CppEventAttributeConstructor, il2CppMemberAttributeName);
+
+ type.Events.RemoveAt(i);
+ }
+ }
+ }
+ }
+
+ private static void AddAttribute(MethodAnalysisContext? method, string name, MethodAnalysisContext il2CppEventAttributeConstructor, PropertyAnalysisContext il2CppMemberAttributeName)
+ {
+ if (method is not null)
+ {
+ var attribute = new AnalyzedCustomAttribute(il2CppEventAttributeConstructor);
+ var parameter = new CustomAttributePrimitiveParameter(name, attribute, CustomAttributeParameterKind.Property, 0);
+ attribute.Properties.Add(new CustomAttributeProperty(il2CppMemberAttributeName, parameter));
+ method.CustomAttributes ??= new(1);
+ method.CustomAttributes.Add(attribute);
+
+ method.OverrideAttributes = method.Attributes & ~MethodAttributes.SpecialName;
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/ExceptionHierarchyProcessingLayer.cs b/Il2CppInterop.Generator/ExceptionHierarchyProcessingLayer.cs
new file mode 100644
index 00000000..da424e28
--- /dev/null
+++ b/Il2CppInterop.Generator/ExceptionHierarchyProcessingLayer.cs
@@ -0,0 +1,190 @@
+using System.Diagnostics;
+using System.Reflection;
+using Cpp2IL.Core.Api;
+using Cpp2IL.Core.Model.Contexts;
+using Il2CppInterop.Generator.Visitors;
+using Il2CppInterop.Runtime.Exceptions;
+
+namespace Il2CppInterop.Generator;
+
+public class ExceptionHierarchyProcessingLayer : Cpp2IlProcessingLayer
+{
+ public override string Name => "Exception Hierarchy";
+ public override string Id => "exception_hierarchy";
+
+ public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null)
+ {
+ var il2CppSystemExceptionType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Exception");
+
+ var il2CppExceptionInterface = appContext.ResolveTypeOrThrow(typeof(IIl2CppException));
+ var il2CppExceptionInterfaceMethod = il2CppExceptionInterface.GetMethodByName(nameof(IIl2CppException.CreateSystemException));
+ var il2CppExceptionInterfaceMethodName = $"{il2CppExceptionInterface.FullName}.{il2CppExceptionInterfaceMethod.Name}";
+
+ var exceptionTypes = GetExceptionTypes(appContext);
+
+ foreach (var exceptionType in exceptionTypes)
+ {
+ var nestedExceptionType = exceptionType.InjectNestedType(GetUniqueNameForNestedClass(exceptionType), null, TypeAttributes.NestedPublic);
+ nestedExceptionType.IsInjected = true;
+ exceptionType.SystemExceptionType = nestedExceptionType;
+
+ // Copy generic parameters
+ nestedExceptionType.CopyGenericParameters(exceptionType, true);
+
+ // Inject constructor
+ nestedExceptionType.InjectMethodContext(
+ ".ctor",
+ appContext.SystemTypes.SystemVoidType,
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+ il2CppSystemExceptionType);
+ }
+
+ foreach (var exceptionType in exceptionTypes)
+ {
+ var nestedExceptionType = exceptionType.SystemExceptionType!;
+
+ // Base type
+ Debug.Assert(exceptionType.BaseType is not null);
+ if (exceptionType == il2CppSystemExceptionType)
+ {
+ nestedExceptionType.SetDefaultBaseType(appContext.ResolveTypeOrThrow(typeof(Il2CppException)));
+ }
+ else if (exceptionType.BaseType is GenericInstanceTypeAnalysisContext exceptionBaseTypeGenericInstance)
+ {
+ // Build replacements dictionary
+ Dictionary replacements = new();
+ for (var i = 0; i < exceptionType.GenericParameters.Count; i++)
+ {
+ replacements.Add(exceptionType.GenericParameters[i], nestedExceptionType.GenericParameters[i]);
+ }
+ replacements.Add(exceptionBaseTypeGenericInstance.GenericType, exceptionBaseTypeGenericInstance.GenericType.SystemExceptionType!);
+
+ nestedExceptionType.SetDefaultBaseType(new TypeReplacementVisitor(replacements).Replace(exceptionBaseTypeGenericInstance));
+ }
+ else
+ {
+ nestedExceptionType.SetDefaultBaseType(exceptionType.BaseType.SystemExceptionType);
+ }
+
+ // Constructor
+ Debug.Assert(nestedExceptionType.Methods.Count is 1);
+ var constructor = nestedExceptionType.Methods[0];
+ Debug.Assert(nestedExceptionType.BaseType is not null);
+ MethodAnalysisContext baseConstructor;
+ if (nestedExceptionType.BaseType is GenericInstanceTypeAnalysisContext nestedExceptionBaseTypeGenericInstance)
+ {
+ baseConstructor = new ConcreteGenericMethodAnalysisContext(
+ nestedExceptionBaseTypeGenericInstance.GenericType.GetMethodByName(".ctor"),
+ nestedExceptionBaseTypeGenericInstance.GenericArguments,
+ []);
+ }
+ else
+ {
+ baseConstructor = nestedExceptionType.BaseType.GetMethodByName(".ctor");
+ }
+ constructor.PutExtraData(new NativeMethodBody()
+ {
+ Instructions =
+ [
+ new(CilOpCodes.Ldarg_0), // this
+ new(CilOpCodes.Ldarg_1), // object
+ new(CilOpCodes.Call, baseConstructor),
+ new(CilOpCodes.Ret),
+ ],
+ });
+
+ // Interface implementation
+ exceptionType.InterfaceContexts.Add(il2CppExceptionInterface);
+ {
+ var interfaceMethod = new InjectedMethodAnalysisContext(
+ exceptionType,
+ il2CppExceptionInterfaceMethodName,
+ il2CppExceptionInterfaceMethod.ReturnType,
+ MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot,
+ [])
+ {
+ IsInjected = true
+ };
+ exceptionType.Methods.Add(interfaceMethod);
+ interfaceMethod.PutExtraData(new NativeMethodBody()
+ {
+ Instructions =
+ [
+ new(CilOpCodes.Ldarg_0), // this
+ new(CilOpCodes.Newobj, exceptionType.HasGenericParameters ? new ConcreteGenericMethodAnalysisContext(constructor, exceptionType.GenericParameters, []) : constructor),
+ new(CilOpCodes.Ret),
+ ],
+ });
+ interfaceMethod.Overrides.Add(il2CppExceptionInterfaceMethod);
+ }
+ }
+ }
+
+ private static HashSet GetExceptionTypes(ApplicationAnalysisContext appContext)
+ {
+ var exceptionTypes = new HashSet();
+ var nonExceptionTypes = new HashSet();
+
+ // Add Il2CppSystem.Exception
+ {
+ exceptionTypes.Add(appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Exception"));
+ }
+
+ // Add all derived types of Il2CppSystem.Exception
+ foreach (var type in appContext.AllTypes)
+ {
+ AddTypeToSet(type, exceptionTypes, nonExceptionTypes);
+ }
+
+ return exceptionTypes;
+
+ static bool AddTypeToSet(TypeAnalysisContext? type, HashSet exceptionTypes, HashSet nonExceptionTypes)
+ {
+ if (type is GenericInstanceTypeAnalysisContext genericInstance)
+ {
+ return AddTypeToSet(genericInstance.GenericType, exceptionTypes, nonExceptionTypes);
+ }
+ Debug.Assert(type is not ReferencedTypeAnalysisContext);
+ if (type == null)
+ {
+ return false;
+ }
+ if (exceptionTypes.Contains(type))
+ {
+ return true;
+ }
+ if (nonExceptionTypes.Contains(type))
+ {
+ return false;
+ }
+ var isException = AddTypeToSet(type.BaseType, exceptionTypes, nonExceptionTypes);
+ if (isException)
+ {
+ exceptionTypes.Add(type);
+ }
+ else
+ {
+ nonExceptionTypes.Add(type);
+ }
+ return isException;
+ }
+ }
+
+ private static string GetUniqueNameForNestedClass(TypeAnalysisContext declaringType)
+ {
+ var genericParameterCount = declaringType.GenericParameters.Count;
+ var nonGenericName = "Exception";
+ while (true)
+ {
+ var name = genericParameterCount > 0 ? $"{nonGenericName}`{genericParameterCount}" : nonGenericName;
+ if (declaringType.Name == name || declaringType.NestedTypes.Any(t => t.Name == name))
+ {
+ nonGenericName += "_";
+ }
+ else
+ {
+ return name;
+ }
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/Extensions/ApplicationAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/ApplicationAnalysisContextExtensions.cs
new file mode 100644
index 00000000..7f69a0a2
--- /dev/null
+++ b/Il2CppInterop.Generator/Extensions/ApplicationAnalysisContextExtensions.cs
@@ -0,0 +1,59 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using Cpp2IL.Core.Model.Contexts;
+using LibCpp2IL;
+
+namespace Il2CppInterop.Generator.Extensions;
+
+internal static class ApplicationAnalysisContextExtensions
+{
+ extension(ApplicationAnalysisContext appContext)
+ {
+ public InjectedAssemblyAnalysisContext InjectAssembly(Assembly assembly)
+ {
+ return appContext.InjectAssembly(assembly.GetName());
+ }
+
+ public InjectedAssemblyAnalysisContext InjectAssembly(AssemblyName assemblyName)
+ {
+#pragma warning disable SYSLIB0037 // Type or member is obsolete
+ return appContext.InjectAssembly(
+ assemblyName.Name!,
+ assemblyName.Version,
+ (uint)assemblyName.HashAlgorithm,
+ (uint)assemblyName.Flags,
+ assemblyName.CultureName,
+ assemblyName.GetPublicKeyToken(),
+ assemblyName.GetPublicKey());
+#pragma warning restore SYSLIB0037 // Type or member is obsolete
+ }
+
+ public TypeAnalysisContext ResolveTypeOrThrow(Type? type)
+ {
+ return appContext.ResolveType(type) ?? throw new($"Unable to resolve type {type?.FullName}");
+ }
+
+ public TypeAnalysisContext? ResolveType(Type? type)
+ {
+ if (type is null)
+ return null;
+
+ var assemblyName = type.Assembly.GetName().Name!;
+ if (assemblyName == "System.Private.CoreLib")
+ assemblyName = "mscorlib";
+ var assembly = appContext.GetAssemblyByName(assemblyName);
+ return assembly?.GetTypeByFullName(type.FullName!);
+ }
+
+ public AssemblyAnalysisContext Mscorlib => appContext.AssembliesByName["mscorlib"];
+ public AssemblyAnalysisContext Il2CppMscorlib => appContext.AssembliesByName["Il2Cppmscorlib"];
+
+ [return: NotNullIfNotNull(nameof(methodReference))]
+ public ConcreteGenericMethodAnalysisContext? ResolveContextForMethod(Cpp2IlMethodRef? methodReference)
+ {
+ return methodReference is not null
+ ? appContext.ConcreteGenericMethodsByRef.TryGetValue(methodReference, out var context) ? context : new(methodReference, appContext)
+ : null;
+ }
+ }
+}
diff --git a/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs b/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs
index 7fc9f869..15b221b8 100644
--- a/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs
+++ b/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs
@@ -1,107 +1,19 @@
using AsmResolver.DotNet;
-using AsmResolver.DotNet.Collections;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.PE.DotNet.Metadata.Tables;
namespace Il2CppInterop.Generator.Extensions;
internal static class AsmResolverExtensions
{
- ///
- /// Boolean, Char, or numeric type
- ///
- public static bool IsPrimitive(this TypeSignature type)
+ extension(CilOpCode opCode)
{
- return (type as CorLibTypeSignature)?.ElementType.IsPrimitive() ?? false;
+ public bool IsLoadConstantI4
+ {
+ get => opCode.Code is (>= CilCode.Ldc_I4_M1 and <= CilCode.Ldc_I4_8) or CilCode.Ldc_I4_S or CilCode.Ldc_I4;
+ }
}
- ///
- /// Boolean, Char, or numeric type
- ///
- public static bool IsPrimitive(this ElementType elementType)
+ public static TypeDefinition? TryResolve(this ITypeDescriptor typeDescriptor, RuntimeContext? runtimeContext)
{
- return elementType is >= ElementType.Boolean and <= ElementType.R8 or ElementType.I or ElementType.U;
- }
-
- public static TypeSignature GetElementType(this TypeSignature type) => type switch
- {
- TypeSpecificationSignature specification => specification.BaseType,
- _ => type,
- };
-
- public static void AddLoadArgument(this ILProcessor instructions, int argumentIndex)
- {
- instructions.Add(OpCodes.Ldarg, instructions.GetArgument(argumentIndex));
- }
-
- public static void AddLoadArgumentAddress(this ILProcessor instructions, int argumentIndex)
- {
- instructions.Add(OpCodes.Ldarga, instructions.GetArgument(argumentIndex));
- }
-
- private static Parameter GetArgument(this ILProcessor instructions, int argumentIndex)
- {
- var method = instructions.Owner.Owner;
- return method.IsStatic
- ? method.Parameters[argumentIndex]
- : argumentIndex == 0
- ? method.Parameters.ThisParameter!
- : method.Parameters[argumentIndex - 1];
- }
-
- public static bool IsReferenceType(this TypeDefinition type) => !type.IsValueType();
-
- public static bool HasGenericParameters(this TypeDefinition type) => type.GenericParameters.Count > 0;
-
- public static bool HasGenericParameters(this MethodDefinition method) => method.GenericParameters.Count > 0;
-
- public static bool HasConstant(this FieldDefinition field) => field.Constant is not null;
-
- public static bool IsInstance(this FieldDefinition field) => !field.IsStatic;
-
- public static bool HasMethods(this TypeDefinition type) => type.Methods.Count > 0;
-
- public static bool HasFields(this TypeDefinition type) => type.Fields.Count > 0;
-
- public static bool IsNested(this ITypeDefOrRef type) => type.DeclaringType is not null;
-
- public static bool IsNested(this TypeSignature type) => type.DeclaringType is not null;
-
- public static bool IsPointerLike(this TypeSignature type) => type is PointerTypeSignature or ByReferenceTypeSignature;
-
- public static bool IsValueTypeLike(this TypeSignature type) => type.IsValueType() || type.IsPointerLike();
-
- public static bool IsSystemEnum(this GenericParameterConstraint constraint) => constraint.Constraint?.FullName is "System.Enum";
-
- public static bool IsSystemValueType(this GenericParameterConstraint constraint) => constraint.Constraint?.FullName is "System.ValueType";
-
- public static bool IsInterface(this GenericParameterConstraint constraint) => constraint.Constraint?.Resolve()?.IsInterface == true;
-
- public static ITypeDefOrRef? AttributeType(this CustomAttribute attribute) => attribute.Constructor?.DeclaringType;
-
- public static Parameter AddParameter(this MethodDefinition method, TypeSignature parameterSignature, string parameterName, ParameterAttributes parameterAttributes = default)
- {
- var parameter = method.AddParameter(parameterSignature);
- var parameterDefinition = parameter.GetOrCreateDefinition();
- parameterDefinition.Name = parameterName;
- parameterDefinition.Attributes = parameterAttributes;
- return parameter;
- }
-
- public static Parameter AddParameter(this MethodDefinition method, TypeSignature parameterSignature)
- {
- method.Signature!.ParameterTypes.Add(parameterSignature);
- method.Parameters.PullUpdatesFromMethodSignature();
- return method.Parameters[method.Parameters.Count - 1];
- }
-
- public static TypeDefinition GetType(this ModuleDefinition module, string fullName)
- {
- return module.TopLevelTypes.First(t => t.FullName == fullName);
- }
-
- public static GenericParameterSignature ToTypeSignature(this GenericParameter genericParameter)
- {
- return new GenericParameterSignature(genericParameter.Owner is ITypeDescriptor ? GenericParameterType.Type : GenericParameterType.Method, genericParameter.Number);
+ return typeDescriptor.TryResolve(runtimeContext, out var resolvedType) ? resolvedType : null;
}
}
diff --git a/Il2CppInterop.Generator/Extensions/AssemblyAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/AssemblyAnalysisContextExtensions.cs
new file mode 100644
index 00000000..eb530e84
--- /dev/null
+++ b/Il2CppInterop.Generator/Extensions/AssemblyAnalysisContextExtensions.cs
@@ -0,0 +1,47 @@
+using Cpp2IL.Core.Model.Contexts;
+using LibCpp2IL.BinaryStructures;
+
+namespace Il2CppInterop.Generator.Extensions;
+
+internal static class AssemblyAnalysisContextExtensions
+{
+ extension(AssemblyAnalysisContext assembly)
+ {
+ public bool IsReferenceAssembly
+ {
+ get => assembly.GetExtraData