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 a4e0afb4..411d224a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,17 +5,16 @@
../bin/NuGet
../bin/$(MSBuildProjectName)
latest
- disable
+ enable
true
- Il2CppInterop
latest
logo_icon.png
LGPL-3.0-only
README.md
true
- true
embedded
enable
+ true
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..16bdc86c
--- /dev/null
+++ b/Documentation/Design/Fields.md
@@ -0,0 +1,34 @@
+# Fields
+
+## Generated code
+
+```cs
+// Reference type
+public FieldType fieldName
+{
+ get => throw null;
+ set => throw null;
+}
+public static ByReference GetFieldAddress_fieldName(Class obj) => throw null;
+private static readonly IntPtr FieldInfoPtr_fieldIndex;
+
+// Value type
+public FieldType fieldName;
+public static ByReference GetFieldAddress_fieldName(ByReference obj) => throw null;
+private static readonly IntPtr FieldInfoPtr_fieldIndex;
+
+// Static field
+public static FieldType staticFieldName
+{
+ get => throw null;
+ set => throw null;
+}
+public static ByReference GetFieldAddress_staticFieldName() => throw null;
+private static readonly IntPtr FieldInfoPtr_fieldIndex;
+```
+
+## Field addresses
+
+The address accessors translate `ldflda` and `ldsflda` in unstripped method bodies, such as the accessors of events, which call `Interlocked.CompareExchange` on the event's field.
+
+A static field's address is in the static field data of its class, which `il2cpp_class_get_static_field_data` returns. Il2Cpp exports it from Unity 2021, so static address accessors are only generated for games that export it. Getting the address runs the class's static constructor first, as any access to a static field does. Thread static fields have no address there.
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..9bbbde2a
--- /dev/null
+++ b/Documentation/Design/MethodInvokers.md
@@ -0,0 +1,186 @@
+# 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.
+
+## Predefined implementations
+
+Some methods cannot be invoked in Il2Cpp, so their unsafe implementation is predefined instead. It replaces a translated implementation as well.
+
+* `Interlocked.CompareExchange` and `Interlocked.Exchange`: Il2Cpp compiles them inline at each call, so their generic instances have no code. The compiler implements the accessors of field-like events with `CompareExchange`. If the game's build includes the `Interlocked` methods for `object`, the implementations call `GenerationInternals.InterlockedCompareExchangeWithObjectSupport` and `InterlockedExchangeWithObjectSupport`, which call those methods for a reference type: like Il2Cpp's inlined code, they mark the location for an incremental collection after the atomic operation. Otherwise, they call the variants without object support, which throw `NotSupportedException` for a reference type. Both variants perform the atomic operation with .NET's `Interlocked` on a value type that Il2Cpp lays out as .NET does, as primitive types and enums, with a size of 1, 2, 4 or 8 bytes and no references.
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/Documentation/Integration-API.md b/Documentation/Integration-API.md
index df443b0e..ef2b4434 100644
--- a/Documentation/Integration-API.md
+++ b/Documentation/Integration-API.md
@@ -5,33 +5,63 @@
## Generating proxy assemblies in loaders
-> TODO: Expand
-
Assembly generation can be done either via [the CLI](Command-Line-Usage.md) or directly via [Il2CppInterop.Generator](https://nuget.bepinex.dev/packages/Il2CppInterop.Generator) package.
-Example of Il2CppInterop.Generator usage:
+The generator is a list of Cpp2IL processing layers. `Il2CppGame.Process` loads the game's binary and metadata with Cpp2IL, runs the layers, and writes the assemblies with an output format:
```cs
-var opts = new GeneratorOptions
-{
- GameAssemblyPath = GameAssemblyPath, // Path to GameAssembly.dll
- Source = sourceAssemblies, // List of Cpp2Il dummy assemblies loaded into Cecil
- OutputDir = IL2CPPInteropAssemblyPath, // Path to which generate the assemblies
- UnityBaseLibsDir = Directory.Exists(UnityBaseLibsDirectory) ? UnityBaseLibsDirectory : null // Path to managed Unity core libraries (UnityEngine.dll etc)
-};
-
-Il2CppInteropGenerator.Create(opts)
- .AddInteropAssemblyGenerator()
- .Run();
-
-// Dispose of sourceAssemblies, etc cleanup
+Il2CppGame.Process(
+ gameExePath, // Path to the game's executable, next to GameAssembly.dll
+ outputDirectory, // Path to which generate the assemblies
+ new AsmResolverDllOutputFormatBinding(),
+ Il2CppGame.GetDefaultProcessingLayers(),
+ [new(UnstripBaseProcessingLayer.DirectoryKey, unityLibrariesDirectory)]);
```
-Logging is supported via `.AddLogger(ILogger)` helper function.
+Unstripping restores members that the game's build removed, from Unity's managed libraries (`UnityEngine.dll` etc.).
+`UnstripBaseProcessingLayer.DirectoryKey` names a directory that contains them. To pass assemblies that are already loaded instead,
+a processing layer can put an `IReadOnlyList` into the application context under
+`UnstripBaseProcessingLayer.AssembliesKey` in its `PreProcess` method. Without either, the `UnstripProcessingLayer` only
+logs a warning, so remove it from the list.
+
+The unstrip directory must include all files recursively contained in both of these Unity directories (shown for Windows):
+
+- `Editor\Data\MonoBleedingEdge\lib\mono\unityaot-win32`
+- `Editor\Data\PlaybackEngines\windowsstandalonesupport\Variations\win64_player_nondevelopment_il2cpp\Data\Managed`
+
+These paths are relative to the Unity installation. The corresponding paths differ on other platforms.
+
+The generator logs through Cpp2IL's logger (`Cpp2IL.Core.Logging.Logger.InfoLog`, `WarningLog`, `ErrorLog` and `VerboseLog`).
## Initializing the runtime
Once all assemblies are generated and loaded, you can initialize the Il2CppInterop runtime.
The runtime is available via [Il2CppInterop.Runtime](https://nuget.bepinex.dev/packages/Il2CppInterop.Runtime) package.
+Start it after Il2Cpp is initialized (`il2cpp_init` has returned):
+
+```cs
+Il2CppInteropRuntime.Create(new RuntimeConfiguration
+ {
+ UnityVersion = unityVersion, // The game's Unity version
+ DetourProvider = detourProvider, // Your IDetourProvider
+ })
+ .AddLogger(logger) // Optional: a Microsoft.Extensions.Logging.ILogger
+ .Start();
+```
+
+The runtime installs native detours on a few Il2Cpp functions for class injection. `IDetourProvider.Create` creates one
+and returns the trampoline that calls the original function. The runtime does not keep the returned `IDisposable`,
+so the provider has to keep each detour alive for as long as the game runs.
+
+## Patching interop methods with Harmony
+
+[Il2CppInterop.HarmonySupport](https://nuget.bepinex.dev/packages/Il2CppInterop.HarmonySupport) lets HarmonyX patch the methods of interop types.
+`Il2CppInteropDetourFactory` creates a native detour on the Il2Cpp method for those, and hands all other methods to the fallback factory.
+HarmonyX gets its detour factory from MonoMod's global detour context, so install it there:
+
+```cs
+DetourContext.SetGlobalContext(new DetourFactoryContext(new Il2CppInteropDetourFactory()));
+```
-Example:
\ No newline at end of file
+Do not make it `DetourFactory.Current` with `DetourFactory.SetCurrentFactory`. `Il2CppInteropDetour` creates its own
+detours through `DetourFactory.Current`, so it would call itself.
diff --git a/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj b/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj
index 1e5a507e..917b009d 100644
--- a/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj
+++ b/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net10.0
Exe
true
il2cppinterop
@@ -9,20 +9,11 @@
Il2CppInterop.CLI
BepInEx
CLI tool for generating managed proxy assemblies for Il2Cpp
- Debug;Release
- AnyCPU
- enable
+ true
-
-
-
-
-
-
-
-
+
diff --git a/Il2CppInterop.CLI/Program.cs b/Il2CppInterop.CLI/Program.cs
index 5796435c..06045404 100644
--- a/Il2CppInterop.CLI/Program.cs
+++ b/Il2CppInterop.CLI/Program.cs
@@ -1,316 +1,32 @@
-using System.CommandLine;
-using System.CommandLine.NamingConventionBinder;
-using System.Text.RegularExpressions;
-using Il2CppInterop;
-using Il2CppInterop.Common;
+using Cpp2IL.Core.Logging;
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
+// For other platforms, the paths will be slightly different.
+
+Logger.InfoLog += Console.WriteLine;
+Logger.WarningLog += Console.WriteLine;
+Logger.ErrorLog += Console.WriteLine;
+Logger.VerboseLog += Console.WriteLine;
+
+Il2CppGame.Process(
+ gameExePath,
+ outputFolder,
+ new AsmResolverDllOutputFormatBinding(),
+ Il2CppGame.GetDefaultProcessingLayers(),
+ [new(UnstripBaseProcessingLayer.DirectoryKey, unstripDirectory)]);
+Console.WriteLine("Done!");
+
+/*
+Todo
+- Add attributes to "Unsafe" methods so that users cannot see them
+- Lazily create field offset accessors
+- Reduce number of implementation methods
+- Make generic inflation more robust
+*/
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/CachedScanResultsAttribute.cs b/Il2CppInterop.Common/Attributes/CachedScanResultsAttribute.cs
deleted file mode 100644
index 4f2396d8..00000000
--- a/Il2CppInterop.Common/Attributes/CachedScanResultsAttribute.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Il2CppInterop.Common.Attributes;
-
-[AttributeUsage(AttributeTargets.Method, Inherited = false)]
-public class CachedScanResultsAttribute : Attribute
-{
- // Data for metadata init call
- public long MetadataInitFlagRva;
- public long MetadataInitTokenRva;
- public int RefRangeEnd;
-
- // Methods that call this method
- public int RefRangeStart;
-
- public int XrefRangeEnd;
-
- // Items that this method calls/uses
- public int XrefRangeStart;
-}
diff --git a/Il2CppInterop.Common/Attributes/CallerCountAttribute.cs b/Il2CppInterop.Common/Attributes/CallerCountAttribute.cs
deleted file mode 100644
index ebb1043a..00000000
--- a/Il2CppInterop.Common/Attributes/CallerCountAttribute.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Il2CppInterop.Common.Attributes;
-
-[AttributeUsage(AttributeTargets.Method, Inherited = false)]
-public class CallerCountAttribute : Attribute
-{
- public readonly int Count;
-
- public CallerCountAttribute(int count)
- {
- Count = count;
- }
-}
diff --git a/Il2CppInterop.Common/Attributes/Il2CppAssemblyAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppAssemblyAttribute.cs
new file mode 100644
index 00000000..8f25e9f9
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppAssemblyAttribute.cs
@@ -0,0 +1,7 @@
+namespace Il2CppInterop.Common.Attributes;
+
+[AttributeUsage(AttributeTargets.Assembly)]
+public sealed class Il2CppAssemblyAttribute : Attribute
+{
+ public string? Name { get; init; }
+}
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..430c0da6
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/Il2CppTypeAttribute.cs
@@ -0,0 +1,15 @@
+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;
+ ///
+ /// The name of the type. If not specified, it will be inferred from the managed type's name.
+ ///
+ public string? Name { get; init; }
+ ///
+ /// The namespace of the type. If not specified, it will be inferred from the managed type's namespace.
+ ///
+ public string? Namespace { get; init; }
+}
diff --git a/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs b/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs
new file mode 100644
index 00000000..6413ed69
--- /dev/null
+++ b/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs
@@ -0,0 +1,17 @@
+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 namespace of the type. If not specified, it will be inferred from the managed type's namespace.
+ ///
+ public string? Namespace { 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..78d3b1ef
--- /dev/null
+++ b/Il2CppInterop.Common/IIl2CppType.cs
@@ -0,0 +1,121 @@
+namespace Il2CppInterop.Common;
+
+public interface IIl2CppType
+{
+ IntPtr ObjectClass { get; }
+ ///
+ /// Box this managed object as a native Il2Cpp object
+ ///
+ ///
+ /// For classes, this returns a pointer to the existing Il2Cpp object.
+ /// For value types, this creates a new boxed Il2Cpp object.
+ ///
+ /// The pointer to the boxed Il2Cpp object
+ ObjectPointer BoxNative();
+ ///
+ /// Box this managed object
+ ///
+ ///
+ /// This method is necessary for semantically accurate boxing of Il2CppSystem.Nullable<T> in unstripped code.
+ /// All other types are expected to use the default implementation.
+ ///
+ /// The boxed object
+ object Box() => this;
+}
+public interface IIl2CppType : IIl2CppType where TSelf : notnull, IIl2CppType
+{
+ ///
+ /// The native size of the type in bytes
+ ///
+ static virtual int Size
+ {
+ get
+ {
+ if (typeof(TSelf).IsValueType)
+ {
+ throw new NotImplementedException($"{typeof(TSelf).FullName} did not implement this property.");
+ }
+ else
+ {
+ // All reference types are the size of a pointer in native code
+ return IntPtr.Size;
+ }
+ }
+ }
+ ///
+ /// 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 virtual void WriteToSpan(TSelf? value, Span span)
+ {
+ if (typeof(TSelf).IsValueType)
+ {
+ throw new NotImplementedException($"{typeof(TSelf).FullName} did not implement this method.");
+ }
+ else
+ {
+ Il2CppType.WriteReference(value, 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 virtual TSelf? ReadFromSpan(ReadOnlySpan span)
+ {
+ if (typeof(TSelf).IsValueType)
+ {
+ throw new NotImplementedException($"{typeof(TSelf).FullName} did not implement this method.");
+ }
+ else
+ {
+ return Il2CppType.ReadReference(span);
+ }
+ }
+ ///
+ /// Unboxes the provided native Il2Cpp object to the type
+ ///
+ ///
+ /// This always returns -typed objects, not subclasses of .
+ /// Callers are expected to know that this is the exact type of the object being unboxed.
+ /// Otherwise, they should use and .
+ ///
+ /// The pointer to the native Il2Cpp object.
+ /// The unboxed value.
+ /// is an interface or abstract class.
+ /// did not implement this method.
+ static virtual unsafe TSelf? UnboxNative(ObjectPointer pointer)
+ {
+ if (typeof(TSelf).IsValueType)
+ {
+ var unboxed = IL2CPP.il2cpp_object_unbox((nint)pointer);
+ return TSelf.ReadFromSpan(new ReadOnlySpan((void*)unboxed, TSelf.Size));
+ }
+ else if (typeof(TSelf).IsInterface)
+ {
+ throw new NotSupportedException("Interfaces cannot be unboxed because they are not concrete types.");
+ }
+ else if (typeof(TSelf).IsAbstract)
+ {
+ throw new NotSupportedException("Abstract classes cannot be unboxed because they are not concrete types.");
+ }
+ else
+ {
+ throw new NotImplementedException($"{typeof(TSelf).FullName} did not implement this method.");
+ }
+ }
+ ///
+ /// Unboxes the provided object to the type
+ ///
+ ///
+ /// This is only intended to be overridden by Il2CppSystem.Nullable<T>, which has special unboxing behavior.
+ ///
+ /// The object to unbox.
+ /// The unboxed value.
+ static virtual TSelf? Unbox(object? obj)
+ {
+ return (TSelf?)obj;
+ }
+}
diff --git a/Il2CppInterop.Common/IL2CPP.cs b/Il2CppInterop.Common/IL2CPP.cs
new file mode 100644
index 00000000..55545f33
--- /dev/null
+++ b/Il2CppInterop.Common/IL2CPP.cs
@@ -0,0 +1,898 @@
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Il2CppInterop.Common;
+
+///
+/// IL2CPP Functions
+///
+public static unsafe partial class IL2CPP
+{
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_init(nint domain_name);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_init_utf16(nint domain_name);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_shutdown();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_config_dir(nint config_path);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_data_dir(nint data_path);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_temp_dir(nint temp_path);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_commandline_arguments(int argc, nint argv, nint basedir);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_commandline_arguments_utf16(int argc, nint argv, nint basedir);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_config_utf16(nint executablePath);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_config(nint executablePath);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_memory_callbacks(nint callbacks);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_get_corlib();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_add_internal_call(nint name, nint method);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_alloc(uint size);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_free(nint ptr);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_array_class_get(nint element_class, uint rank);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_array_length(nint array);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_array_get_byte_length(nint array);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_array_new(nint elementTypeInfo, ulong length);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_array_new_specific(nint arrayTypeInfo, ulong length);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_array_new_full(nint array_class, ulong* lengths, ulong* lower_bounds);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_bounded_array_class_get(nint element_class, uint rank, [MarshalAs(UnmanagedType.I1)] bool bounded);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_array_element_size(nint array_class);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_assembly_get_image(nint assembly);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_enum_basetype(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_generic(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_inflated(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_assignable_from(nint klass, nint oklass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_subclass_of(nint klass, nint klassc, [MarshalAs(UnmanagedType.I1)] bool check_interfaces);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_has_parent(nint klass, nint klassc);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_from_il2cpp_type(nint type);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_from_name(nint image, [MarshalAs(UnmanagedType.LPUTF8Str)] string namespaze, [MarshalAs(UnmanagedType.LPUTF8Str)] string name);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_from_system_type(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_element_class(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_events(nint klass, ref nint iter);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_fields(nint klass, ref nint iter);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_nested_types(nint klass, ref nint iter);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_interfaces(nint klass, ref nint iter);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_properties(nint klass, ref nint iter);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_property_from_name(nint klass, nint name);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_field_from_name(nint klass, [MarshalAs(UnmanagedType.LPUTF8Str)] string name);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_methods(nint klass, ref nint iter);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_method_from_name(nint klass, [MarshalAs(UnmanagedType.LPUTF8Str)] string name, int argsCount);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_class_get_name(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_class_get_namespace(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_parent(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_declaring_type(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_class_instance_size(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_class_num_fields(nint enumKlass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_valuetype(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_class_value_size(nint klass, out uint align);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_blittable(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_class_get_flags(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_abstract(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_interface(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_class_array_element_size(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_from_type(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_type(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_class_get_type_token(nint klass);
+
+ ///
+ /// Exported from Unity 2021.
+ ///
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_static_field_data(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_has_attribute(nint klass, nint attr_class);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_has_references(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_class_is_enum(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_image(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_class_get_assemblyname(nint klass);
+
+ public static string? il2cpp_class_get_assemblyname_(nint klass)
+ => Marshal.PtrToStringUTF8(il2cpp_class_get_assemblyname(klass));
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_class_get_rank(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_class_get_bitmap_size(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_class_get_bitmap(nint klass, ref uint bitmap);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_stats_dump_to_file(nint path);
+
+ //[LibraryImport("GameAssembly")]
+ //[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ //public static partial ulong il2cpp_stats_get_value(IL2CPP_Stat stat);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_domain_get();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_domain_assembly_open(nint domain, nint name);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint* il2cpp_domain_get_assemblies(nint domain, out uint size);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_raise_exception(nint ex);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_exception_from_name_msg(nint image, nint name_space, nint name, nint msg);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_get_exception_argument_null(nint arg);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_format_exception(nint ex, void* message, int message_size);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_format_stack_trace(nint ex, void* output, int output_size);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_unhandled_exception(nint ex);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_field_get_flags(nint field);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_field_get_name(nint field);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_field_get_parent(nint field);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_field_get_offset(nint field);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_field_get_type(nint field);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_field_get_value(nint obj, nint field, void* value);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_field_get_value_object(nint field, nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_field_has_attribute(nint field, nint attr_class);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_field_set_value(nint obj, nint field, void* value);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_field_static_get_value(nint field, void* value);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_field_static_set_value(nint field, void* value);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_field_set_value_object(nint instance, nint field, nint value);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_gc_collect(int maxGenerations);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_gc_collect_a_little();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_gc_disable();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_gc_enable();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_gc_is_disabled();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial long il2cpp_gc_get_used_size();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial long il2cpp_gc_get_heap_size();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_gc_wbarrier_set_field(nint obj, nint targetAddress, nint gcObj);
+
+ ///
+ /// Whether a reference that is stored in Il2Cpp's heap goes through , which it
+ /// does whenever the game exports that function.
+ ///
+ ///
+ /// An incremental collection finds a reference that was stored while it ran only if the store went through the write
+ /// barrier, as it does in Il2Cpp's own code. Il2Cpp collects incrementally if the game's build enables it (Unity 2019 and later);
+ /// otherwise, the barrier only stores the reference.
+ ///
+ public static bool UsesWriteBarriers { get; } = HasExport(nameof(il2cpp_gc_wbarrier_set_field));
+
+ private static bool HasExport(string name)
+ {
+ if (!NativeLibrary.TryLoad("GameAssembly", out var handle))
+ return false;
+ var result = NativeLibrary.TryGetExport(handle, name, out _);
+ NativeLibrary.Free(handle);
+ return result;
+ }
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_gchandle_new(nint obj, [MarshalAs(UnmanagedType.I1)] bool pinned);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_gchandle_new_weakref(nint obj, [MarshalAs(UnmanagedType.I1)] bool track_resurrection);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_gchandle_get_target(nint gchandle);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_gchandle_free(nint gchandle);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_unity_liveness_calculation_begin(nint filter, int max_object_count, nint callback, nint userdata, nint onWorldStarted, nint onWorldStopped);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_unity_liveness_calculation_end(nint state);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_unity_liveness_calculation_from_root(nint root, nint state);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_unity_liveness_calculation_from_statics(nint state);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_method_get_return_type(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_method_get_declaring_type(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_method_get_name(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_method_get_from_reflection(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_method_get_object(nint method, nint refclass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_method_is_generic(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_method_is_inflated(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_method_is_instance(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_method_get_param_count(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_method_get_param(nint method, uint index);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_method_get_class(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_method_has_attribute(nint method, nint attr_class);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_method_get_flags(nint method, ref uint iflags);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_method_get_token(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_method_get_param_name(nint method, uint index);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_profiler_install(nint prof, nint shutdown_callback);
+
+ //[LibraryImport("GameAssembly")]
+ //[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ //public static partial void il2cpp_profiler_set_events(IL2CPP_ProfileFlags events);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_profiler_install_enter_leave(nint enter, nint fleave);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_profiler_install_allocation(nint callback);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_profiler_install_gc(nint callback, nint heap_resize_callback);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_profiler_install_fileio(nint callback);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_profiler_install_thread(nint start, nint end);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_property_get_flags(nint prop);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_property_get_get_method(nint prop);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_property_get_set_method(nint prop);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_property_get_name(nint prop);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_property_get_parent(nint prop);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_object_get_class(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_object_get_size(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_object_get_virtual_method(nint obj, nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_object_new(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_object_unbox(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_value_box(nint klass, nint data);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_monitor_enter(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_monitor_try_enter(nint obj, uint timeout);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_monitor_exit(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_monitor_pulse(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_monitor_pulse_all(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_monitor_wait(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_monitor_try_wait(nint obj, uint timeout);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_runtime_invoke(nint method, nint obj, void** param, ref nint exc);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ // param can be of Il2CppObject*
+ public static partial nint il2cpp_runtime_invoke_convert_args(nint method, nint obj, void** param, int paramCount, ref nint exc);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_runtime_class_init(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_runtime_object_init(nint obj);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_runtime_object_init_exception(nint obj, ref nint exc);
+
+ //[LibraryImport("GameAssembly")]
+ //[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ //public static partial void il2cpp_runtime_unhandled_exception_policy_set(IL2CPP_RuntimeUnhandledExceptionPolicy value);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_string_length(nint str);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial char* il2cpp_string_chars(nint str);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_string_new(string str);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_string_new_len(string str, uint length);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_string_new_utf16(char* text, int len);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_string_new_wrapper(string str);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_string_intern(string str);
+
+ [LibraryImport("GameAssembly", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(System.Runtime.InteropServices.Marshalling.AnsiStringMarshaller))]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_string_is_interned(string str);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_thread_current();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_thread_attach(nint domain);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_thread_detach(nint thread);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void** il2cpp_thread_get_all_attached_threads(ref uint size);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_is_vm_thread(nint thread);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_current_thread_walk_frame_stack(nint func, nint user_data);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_thread_walk_frame_stack(nint thread, nint func, nint user_data);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_current_thread_get_top_frame(nint frame);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_thread_get_top_frame(nint thread, nint frame);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_current_thread_get_frame_at(int offset, nint frame);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_thread_get_frame_at(nint thread, int offset, nint frame);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_current_thread_get_stack_depth();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_thread_get_stack_depth(nint thread);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_type_get_object(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial int il2cpp_type_get_type(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_type_get_class_or_element_class(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_type_get_name(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_type_is_byref(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_type_get_attrs(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_type_equals(nint type, nint otherType);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_type_get_assembly_qualified_name(nint type);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_image_get_assembly(nint image);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_image_get_name(nint image);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalUsing(typeof(Il2CppStringMarshaller))]
+ public static partial string il2cpp_image_get_filename(nint image);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_image_get_entry_point(nint image);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial uint il2cpp_image_get_class_count(nint image);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_image_get_class(nint image, uint index);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_capture_memory_snapshot();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_free_captured_memory_snapshot(nint snapshot);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_set_find_plugin_callback(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_register_log_callback(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_debugger_set_agent_options(nint options);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_is_debugger_attached();
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_unity_install_unitytls_interface(void* unitytlsInterfaceStruct);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_custom_attrs_from_class(nint klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_custom_attrs_from_method(nint method);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_custom_attrs_get_attr(nint ainfo, nint attr_klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool il2cpp_custom_attrs_has_attr(nint ainfo, nint attr_klass);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial nint il2cpp_custom_attrs_construct(nint cinfo);
+
+ [LibraryImport("GameAssembly")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ public static partial void il2cpp_custom_attrs_free(nint ainfo);
+
+ [CustomMarshaller(typeof(string), MarshalMode.ManagedToUnmanagedOut, typeof(Il2CppStringMarshaller))]
+ private static class Il2CppStringMarshaller
+ {
+ public static string? ConvertToManaged(byte* unmanaged)
+ => Marshal.PtrToStringUTF8((nint)unmanaged);
+
+ public static void Free(byte* unmanaged)
+ {
+ // Intentionally empty, il2cpp owns this pointer
+ _ = unmanaged;
+ }
+ }
+}
diff --git a/Il2CppInterop.Common/Il2CppInternalsAccess.cs b/Il2CppInterop.Common/Il2CppInternalsAccess.cs
new file mode 100644
index 00000000..dfa37d92
--- /dev/null
+++ b/Il2CppInterop.Common/Il2CppInternalsAccess.cs
@@ -0,0 +1,65 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using Il2CppInterop.Common.Attributes;
+
+namespace Il2CppInterop.Common;
+
+[RequiresUnreferencedCode("This API uses reflection to access fields in the corresponding Il2CppInternals type.")]
+[RequiresDynamicCode("This API generically instantiates the corresponding Il2CppInternals type if necessary.")]
+public static class Il2CppInternalsAccess
+{
+ private static FieldInfo? GetFieldInfo(Type declaringType, string prefix, int index)
+ {
+ 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)
+ {
+ var attr = declaringType.GetCustomAttribute();
+ if (attr == null)
+ return null;
+
+ var internals = attr.Internals;
+
+ if (internals.IsGenericTypeDefinition && declaringType.IsConstructedGenericType)
+ {
+ internals = internals.MakeGenericType(declaringType.GetGenericArguments());
+ }
+
+ return internals;
+ }
+ }
+
+ public static FieldInfo? GetIl2CppMethodInfoPointerFieldForGeneratedMethod(MethodBase method)
+ {
+ var declaringType = method.DeclaringType;
+ if (declaringType == null)
+ return null;
+
+ var index = method.GetCustomAttribute()?.Index ?? -1;
+
+ return GetFieldInfo(declaringType, "MethodInfoPtr_", index);
+ }
+
+ [RequiresUnreferencedCode("This API uses reflection to access fields in the corresponding Il2CppInternals type and to access property metadata for the method's declaring type.")]
+ public static FieldInfo? GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(MethodBase method)
+ {
+ 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/Il2CppInterop.Common.csproj b/Il2CppInterop.Common/Il2CppInterop.Common.csproj
index d9320b4d..49b354c6 100644
--- a/Il2CppInterop.Common/Il2CppInterop.Common.csproj
+++ b/Il2CppInterop.Common/Il2CppInterop.Common.csproj
@@ -1,16 +1,15 @@
-
+
- netstandard2.0
- BepInEx, knah et al.
+ Il2CppInterop.Common
+ net10.0
Common API for Il2CppInterop packages
- Il2CppInterop.Common
+ true
-
-
-
+
+
diff --git a/Il2CppInterop.Common/Il2CppInteropUtils.cs b/Il2CppInterop.Common/Il2CppInteropUtils.cs
deleted file mode 100644
index 99948d3c..00000000
--- a/Il2CppInterop.Common/Il2CppInteropUtils.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Reflection;
-using System.Reflection.Emit;
-
-namespace Il2CppInterop.Common;
-
-public static class Il2CppInteropUtils
-{
- private static FieldInfo? GetFieldInfoFromMethod(MethodBase method, string prefix)
- {
- 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 (opCode != OpCodes.Ldsfld) continue;
-
- var fieldInfo = methodModule.ResolveField((int)opArg, method.DeclaringType.GenericTypeArguments, method.GetGenericArguments());
- if (fieldInfo?.FieldType != typeof(IntPtr)) continue;
-
- if (fieldInfo.Name.StartsWith(prefix)) return fieldInfo;
-
- // Resolve generic method info pointer fields
- if (method.IsGenericMethod && fieldInfo.DeclaringType.Name.StartsWith("MethodInfoStoreGeneric_") && fieldInfo.Name == "Pointer") return fieldInfo;
- }
-
- return null;
- }
-
- public static FieldInfo GetIl2CppMethodInfoPointerFieldForGeneratedMethod(MethodBase method)
- {
- return GetFieldInfoFromMethod(method, "NativeMethodInfoPtr_");
- }
-
- public static FieldInfo GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(MethodBase method)
- {
- return GetFieldInfoFromMethod(method, "NativeFieldInfoPtr_");
- }
-}
diff --git a/Il2CppInterop.Common/Il2CppObjectPool.cs b/Il2CppInterop.Common/Il2CppObjectPool.cs
new file mode 100644
index 00000000..e4187ece
--- /dev/null
+++ b/Il2CppInterop.Common/Il2CppObjectPool.cs
@@ -0,0 +1,103 @@
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace Il2CppInterop.Common;
+
+public static class Il2CppObjectPool
+{
+ private static readonly ConcurrentDictionary> s_cache = new();
+
+ private static readonly ConcurrentDictionary s_initializers = new();
+
+ public static void Remove(nint ptr)
+ {
+ s_cache.TryRemove(ptr, out _);
+ }
+
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "This will never be called in a trimmed context.")]
+ [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "This will never be called in an AOT context.")]
+ public static object? Get(nint ptr)
+ {
+ if (ptr == nint.Zero)
+ return null;
+
+ if (s_cache.TryGetValue(ptr, out var reference) && reference.TryGetTarget(out var cachedObject))
+ {
+ return cachedObject;
+ }
+
+ var ownClass = IL2CPP.il2cpp_object_get_class(ptr);
+ if (!s_initializers.TryGetValue(ownClass, out var initializer))
+ {
+ var systemType = EnsureClassInitialized(ownClass);
+ if (!s_initializers.TryGetValue(ownClass, out initializer))
+ {
+ throw new InvalidOperationException($"No object initializer found for class {systemType}");
+ }
+ }
+
+ var newObj = initializer.Invoke((ObjectPointer)ptr);
+ if (!newObj.GetType().IsValueType)
+ {
+ s_cache[ptr] = new WeakReference