diff --git a/.editorconfig b/.editorconfig index 3478ef3a..b6c6c3fb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,10 +11,29 @@ insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true +end_of_line = lf # Generated code [*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] generated_code = true +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +end_of_line = lf +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion # C# files [*.cs] @@ -54,7 +73,7 @@ dotnet_style_predefined_type_for_member_access = true:suggestion # name all constant fields using PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case @@ -62,7 +81,7 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case # static fields should have s_ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected @@ -72,7 +91,7 @@ dotnet_naming_style.static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_style.camel_case_underscore_style.required_prefix = _ @@ -150,6 +169,11 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion # C++ Files [*.{cpp,h,in}] diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 04038ad1..fb4a849a 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - global-json-file: global.json + dotnet-version: 10.0.x - name: Extract Release Version id: get_version diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml index ac60880e..bacf0397 100644 --- a/.github/workflows/format_check.yml +++ b/.github/workflows/format_check.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - global-json-file: global.json + dotnet-version: 10.0.x - name: Check format run: | diff --git a/Directory.Build.props b/Directory.Build.props index c121dfab..dbdd5b64 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..aa93e62f --- /dev/null +++ b/Documentation/Design/Fields.md @@ -0,0 +1,19 @@ +# Fields + +## Generated code + +```cs +// Reference type +public FieldType fieldName +{ + get => throw null; + set => throw null; +} +public static ByReference UnsafeField_fieldName(Class obj) => throw null; +private static readonly IntPtr FieldInfoPtr_fieldIndex; + +// Value type +public FieldType fieldName; +public static ByReference UnsafeField_fieldName(ByRerefence obj) => throw null; +private static readonly IntPtr FieldInfoPtr_fieldIndex; +``` diff --git a/Documentation/Design/GenericMethods.md b/Documentation/Design/GenericMethods.md new file mode 100644 index 00000000..00c7e4a5 --- /dev/null +++ b/Documentation/Design/GenericMethods.md @@ -0,0 +1,22 @@ +# Generic Methods + +## Pointers + +Pointers are resolved as needed using Il2Cpp reflection. It's fine to do this initialization lazily because all generic type instances are known in advance. + +```cs +private static class MethodInfoStoreGeneric_Aggregate +{ + internal static System.IntPtr Pointer = IL2CPP.il2cpp_method_get_from_reflection(IL2CPP.Il2CppObjectBaseToPtrNotNull(new MethodInfo(IL2CPP.il2cpp_method_get_object(NativeMethodInfoPtr_Aggregate, Il2CppClassPointerStore.NativeClassPtr)) + .MakeGenericMethod(new Il2CppReferenceArray(new Type[2] + { + Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr)), + Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr)) + })))); +} +``` + +If a modder tries to use a generic method instantiation that doesn't exist, an exception is thrown. + +* +* diff --git a/Documentation/Design/GenericTypes.md b/Documentation/Design/GenericTypes.md new file mode 100644 index 00000000..04efa215 --- /dev/null +++ b/Documentation/Design/GenericTypes.md @@ -0,0 +1,10 @@ +# Generic Types + +```cs +Il2CppClassPointerStore>.NativeClassPtr = IL2CPP.il2cpp_class_from_type(Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(IL2CPP.GetIl2CppClass("mscorlib.dll", "System.Collections.Generic", "Dictionary`2"))).MakeGenericType(new Il2CppReferenceArray(new Type[2] +{ + Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr)), + Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr)) +})).TypeHandle.value); +IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore>.NativeClassPtr); +``` diff --git a/Documentation/Design/Initialization.md b/Documentation/Design/Initialization.md new file mode 100644 index 00000000..0333624d --- /dev/null +++ b/Documentation/Design/Initialization.md @@ -0,0 +1,11 @@ +# Il2Cpp Initialization + +Every Il2Cpp class has a generated `Il2CppInternals` class. + +## Module Initialization + +The static constructor for a type triggers its Il2Cpp initialization. + +## Global Initialization + +A global initialization assembly ensures that all types get initialized on startup. This is essential for ensuring that the object pool can create objects. diff --git a/Documentation/Design/Injection.md b/Documentation/Design/Injection.md new file mode 100644 index 00000000..fcf88489 --- /dev/null +++ b/Documentation/Design/Injection.md @@ -0,0 +1,113 @@ +# Injection + +## Major Flaw + +Current injection has a major flaw. It waits until the last minute to allocate a new type. This can cause problems with: + +* Cyclical field types (two classes have a field on each other) +* Self-referential field type +* Injected type used in base type or interfaces + +## Class + +```cs +// User code + +// This attribute is for source generation and has no runtime impact. +[InjectInIl2Cpp(Assembly = "OptionalAssemblyName")] // By default, types are injected into Assembly-CSharp +public partial class MyMonoBehaviour : MonoBehaviour +{ + [Il2CppField] + public static partial Int32 staticField { get; set; } + [Il2CppField] + public partial Boolean instanceField { get; set; } + + // Note: Injected classes cannot have uninjected instance fields and static fields are always ignored. + + [Il2CppMethod] + public static void DoSomething(String s) + { + } + + public static void DoAnything(string s) // If this wasn't hidden, it would be invalid because string is not a valid Il2Cpp parameter type. + { + } + + [Il2CppProperty] + public static Int32 MyProperty { get => default; set {} } +} + +// Source generated + +partial class MyMonoBehaviour +{ + static MyMonoBehaviour() + { + ClassInjector.RegisterTypeInIl2Cpp(); + } + + // Required + public MyMonoBehaviour(ObjectPointer ptr) : base(ptr) + { + } + + public static partial int staticField { get => Il2CppInternals.staticField.Get(); set => Il2CppInternals.staticField.Set(value); } + public partial bool instanceField { get => Il2CppInternals.instanceField.Get(this); set => Il2CppInternals.instanceField.Set(this, value); } +} +file static class Il2CppInternals // If the injected class is generic, this will also be generic. +{ + internal static readonly Il2CppStaticField staticField = new("staticField", GetClassPointer); + internal static readonly Il2CppField instanceField = new("instanceField", GetClassPointer); + private static IntPtr GetClassPointer() => Il2CppClassPointerStore.NativeClassPtr; +} +``` + +## Struct + +```cs +// User code + +[InjectInIl2Cpp] +public partial struct MyStruct +{ + [Il2CppField] + public static partial T staticField { get; set; } + + // [Il2CppField] is optional for struct instance fields. + public bool instanceField; +} + +// Source generated + +partial struct MyStruct : IIl2CppType> + where T : IIl2CppType +{ + static MyStruct() + { + ClassInjector.RegisterTypeInIl2Cpp()); + } + + public static partial T staticField { get => Il2CppInternals.staticField.Get(); set => Il2CppInternals.staticField.Set(value); } +} +file static class Il2CppInternals +{ + internal static readonly Il2CppStaticField staticField = new("staticField", GetClassPointer); + private static IntPtr GetClassPointer() => Il2CppClassPointerStore.NativeClassPtr; +} +``` + +## New Design + +### Design Constraints + +A class pointer needs registered in `Il2CppClassPointerStore` before anything else can reference this type. In other words, the class pointer needs registered before setting: + +* Declaring type +* Nested types +* Base type +* Interface implementations +* Types in member signatures + +In addition, we need to know the virtual table slot count when allocating data for the class because the virtual table is always positioned in method directly after the class struct. + +Ideally, no Il2Cpp APIs should be called while class structs are partially initialized. diff --git a/Documentation/Design/Interfaces.md b/Documentation/Design/Interfaces.md new file mode 100644 index 00000000..4900224f --- /dev/null +++ b/Documentation/Design/Interfaces.md @@ -0,0 +1,3 @@ +# Interfaces + +Interfaces are generated as actual interfaces. diff --git a/Documentation/Design/Marshalling.md b/Documentation/Design/Marshalling.md new file mode 100644 index 00000000..0560b8cc --- /dev/null +++ b/Documentation/Design/Marshalling.md @@ -0,0 +1,3 @@ +# Marshalling + +All Il2Cpp types implement the `Il2CppType<>` interface, which enables marshalling. diff --git a/Documentation/Design/MethodInvokers.md b/Documentation/Design/MethodInvokers.md new file mode 100644 index 00000000..f2a6e8d4 --- /dev/null +++ b/Documentation/Design/MethodInvokers.md @@ -0,0 +1,180 @@ +# Method Invokers + +Every normal (user-facing) Il2Cpp method has an unsafe implementation. + +* Implementation methods are static. +* Implementation methods are private, so they can only be called from within the type. +* If the Il2Cpp method is instance, the first parameter is the object. +* All other implementation parameter types are wrapped in `ByReference<>`, including parameters that are already `ByReference<>`. +* Similarly, local variable types are also wrapped in `ByReference<>`. +* The data for local variables is stack allocated at the beginning of the method. + +Some methods have an unsafe invoker. + +* Invoker methods are static. +* Invoker methods are public, so they can be called from other generated assemblies where desirable. + +This can be verbose, but it preserves semantics exactly for indirect memory access of reference types. + +## Example Output + +```cs +// Original +public string GetString(int param1, T param2, IInterface param3); + +// Il2Cpp +public String GetString(Int32 param1, T param2, IInterface param3) where T : IIl2CppType +{ + ByReference data_this = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + + // Value type + data_this.CopyFrom(this); + + // Reference type + data_this.SetValue(this); + + String result = UnsafeInvoke_GetString(data_this, param1, param2, param3); + + // Value type + data_this.CopyTo(this); + + return result; +} + +// Invoker +public static String UnsafeInvoke_GetString(ByReference @this, Int32 param1, T param2, IInterface param3) where T : IIl2CppType +{ + // Param 1 + ByReference data_param1 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param1.SetValue(param1); + + // Param 2 + ByReference data_param2 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param2.SetValue(param2); + + // Param 3 + ByReference data_param3 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param3.SetValue(param3); + + return UnsafeImplementation_GetString(@this, data_param1, data_param2, data_param3); +} +private static String UnsafeImplementation_GetString(ByReference @this, ByReference param1, ByReference param2, ByReference param3) where T : IIl2CppType +{ + IntPtr* arguments = stackalloc IntPtr[3]; + arguments[0] = RuntimeInvokeHelper.GetPointerForParameter(param1); + arguments[1] = RuntimeInvokeHelper.GetPointerForParameter(param2); + arguments[2] = RuntimeInvokeHelper.GetPointerForParameter(param3); + return RuntimeInvokeHelper.InvokeFunction(/* method info */, RuntimeInvokeHelper.GetPointerForThis(@this), (void**)arguments); +} + +// Unstripped Original +public static bool Compare(Self param1, Self param2) +{ + string local1 = param1.GetString(0, 0, null); + string local2 = param2.GetString(0, 0, null); + return local1 == local2; +} + +// Unstripped Invoker +public static Boolean Compare(Self param1, Self param2) +{ + ByReference data_param1 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param1.SetValue(param1); + ByReference data_param2 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param1.SetValue(param2); + + return UnsafeImplementation_Compare(data_param1, data_param2) +} +private static Boolean UnsafeImplementation_Compare(ByReference param1, ByReference param2) +{ + ByReference data_local1 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + ByReference data_local2 = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + + data_local1.SetValue(param1.GetValue().GetString((Int32)0, (Int64)0, (IInterface?)null)); + data_local2.SetValue(param2.GetValue().GetString((Int32)0, (Int64)0, (IInterface?)null)); + + return data_local1.GetValue() == data_local2.GetValue(); +} +``` + +## Unstripped Instruction Translations + +### ldind_Ref + +* ldind_I +* `Il2CppObjectPool.Get` + +### stind_Ref + +* `Box` +* stind_I + +### ldarga + +* ldarg +* `ByReference.ToPointer()` + +### ldarg + +* ldarg +* `ByReference.GetValue()` + +### starg + +* ldarg +* `ByReference.SetValue` + +### ldloca + +* ldloc +* `ByReference.ToPointer()` + +### ldloc + +* ldloc +* `ByReference.GetValue()` + +### stloc + +* ldloc +* `ByReference.SetValue` + +### call / callvirt + +Since argument data is handled by the caller, additional locals need to be created to store those arguments. + +* Arguments are converted from Mono to Il2Cpp and popped off 1 by 1 into the data. +* Data variables are loaded onto the stack. +* Call the target's invoker method + +## `ref` parameters + +```cs +// Il2Cpp method +public ByReference Method(Int32 param_normal, [In] ByReference param_in, ByReference param_ref, [Out] ByReference param_out); + +// Injected overload +public ByReference Method(Int32 param_normal, in Int32 param_in, ref Int32 param_ref, out Int32 param_out) +{ + ByReference data_param_in = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param_in.CopyFrom(in param_in); + + ByReference data_param_ref = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param_ref.CopyFrom(ref param_ref); + + ByReference data_param_out = new ByReference(stackalloc byte[Il2CppTypeHelper.SizeOf()]); + data_param_out.Clear(); + + ByReference result = Method(param_normal, data_param_in, data_param_ref, data_param_out); + + data_param_ref.CopyTo(out param_ref); + data_param_out.CopyTo(out param_out); + + return result; +} +``` + +Return types are unchanged for two reasons: + +* There's no way to convert `ByReference` to a managed reference for all type parameters. +* Overloads can't differ only on the return type. diff --git a/Documentation/Design/NativeStaticConstructors.md b/Documentation/Design/NativeStaticConstructors.md new file mode 100644 index 00000000..447e39ef --- /dev/null +++ b/Documentation/Design/NativeStaticConstructors.md @@ -0,0 +1,7 @@ +# Native Static Constructors + +We give users a way to interact with static constructors. + +* They are renamed from `.cctor` to `StaticConstructor`. If this is unavailable, additional underscores are added. +* Like other methods, they are publicized. +* The `specialname` and `rtspecialname` attributes are removed. diff --git a/Documentation/Design/NestedTypes.md b/Documentation/Design/NestedTypes.md new file mode 100644 index 00000000..d7f6dbf8 --- /dev/null +++ b/Documentation/Design/NestedTypes.md @@ -0,0 +1,6 @@ +# Nested Types + +```cs +Il2CppClassPointerStore.NativeClassPtr = IL2CPP.GetIl2CppNestedType(Il2CppClassPointerStore.NativeClassPtr, "NestedClass"); +IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore.NativeClassPtr); +``` diff --git a/Documentation/Design/ReturnObjects.md b/Documentation/Design/ReturnObjects.md new file mode 100644 index 00000000..803f8511 --- /dev/null +++ b/Documentation/Design/ReturnObjects.md @@ -0,0 +1,34 @@ +# Return Objects + +## Value Types + +These should be returned "as-is". + +```cs +return *(bool*)IL2CPP.il2cpp_object_unbox(intPtr); +``` + +## Sealed Classes + +Since these can't be inherited from, we can call the pointer constructor directly. However, we may want to treat all classes the same, for simplicity. + +## Normal Classes + +During initialization, we cache delegates (or function pointers) for creating an object. + +```cs +private static object Create(ObjectPointer ptr) => new Class(ptr); +internal static void Initialize() +{ + Il2CppObjectPool.RegisterFactoryMethod(Il2CppClassPointerStore.NativeClassPtr, (Func)Create); +} +``` + +When returning from a method, we check the pool to see if the object already exists. If the object doesn't yet exist in managed code, we use the cached factory method to create a new one. + +```cs +// Maybe instead Il2CppObjectPool.Get should perform this null check? +return (ReturnType?)Il2CppObjectPool.Get(intPtr); +``` + +In the event that a factory method has not been registered, we throw an exception. If we pre-register factory methods for all generic type instances this should never happen. diff --git a/Documentation/Design/SimpleClasses.md b/Documentation/Design/SimpleClasses.md new file mode 100644 index 00000000..5d390006 --- /dev/null +++ b/Documentation/Design/SimpleClasses.md @@ -0,0 +1,59 @@ +# Simple Classes + +This document outlines the high level emission of simple classes. + +## Universal Base Type + +All reference classes inherit from `Il2CppObjectBase`. + +## Constructors + +All reference classes have an injected primary constructor. This carries the object pointer from derived classes to their base + +```cs +public class Derived(ObjectPointer ptr) : MonoBehaviour(ptr) +{ + static Derived() + { + Il2CppClassPointerStore.NativeClassPtr = IL2CPP.GetIl2CppClass("Assembly-CSharp.dll", "", "Derived"); + IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore.NativeClassPtr); + NativeMethodInfoPtr__ctor_Public_Void_0 = IL2CPP.GetIl2CppMethodByToken(Il2CppClassPointerStore.NativeClassPtr, 100666213); + } + + public unsafe Derived() : this(IL2CPP.il2cpp_object_new(Il2CppClassPointerStore.NativeClassPtr)) + { + Unsafe.SkipInit(out IntPtr intPtr2); + IntPtr intPtr = IL2CPP.il2cpp_runtime_invoke(NativeMethodInfoPtr__ctor_Public_Void_0, IL2CPP.Il2CppObjectBaseToPtrNotNull(this), (void**)null, ref intPtr2); + Il2CppException.RaiseExceptionIfNecessary(intPtr2); + } +} +``` + +A wrapper struct in the common library prevents conflicts. + +```cs +public readonly record struct ObjectPointer(IntPtr Value) +{ + public static implicit operator ObjectPointer(IntPtr value) => new(value); + public static implicit operator IntPtr(ObjectPointer value) => value.Value; +} +``` + +## Static classes + +We make the class abstract but not sealed and inject a private constructor. + +```cs +public abstract class StaticClass +{ + static StaticClass() + { + Il2CppClassPointerStore.NativeClassPtr = IL2CPP.GetIl2CppClass("Assembly-CSharp.dll", "", "StaticClass"); + IL2CPP.il2cpp_runtime_class_init(Il2CppClassPointerStore.NativeClassPtr); + } + + private StaticClass() + { + } +} +``` diff --git a/Documentation/Design/Unstripping.md b/Documentation/Design/Unstripping.md new file mode 100644 index 00000000..cb8e2d01 --- /dev/null +++ b/Documentation/Design/Unstripping.md @@ -0,0 +1,92 @@ +# Unstripping + +Certain assemblies can be unstripped because we have source code available, eg mscorlib and UnityEngine. + +## Injection + +Unstripped types and members get injected, similar to custom user classes. + +## Replace Native Implementation + +If the success rate for unstripping is high, and if a certain method can be unstripped, its implementation should be moved to managed land. This enables several things: + +* Transpilers (assuming that native is patched to use this implementation) +* Generic instances not present in the game + +However, unstripping semantics would have to be perfect. Little flaws are unacceptable. + +```cs +static IntPtr GeneratedDynamicMethod(IntPtr @this, FixedSizeStruct_8b param0) +{ + IntPtr result; + try + { + result = Il2CppTypeHelper.ReadFromPointer(&@this).ManagedMethod(Il2CppTypeHelper.ReadFromPointer(¶m0)).Box(); + } + catch (Il2CppException e) + { + result = default; + il2cpp_raise_exception(e.Il2CppObject.Pointer) + } + return result; +} +``` + +## ICalls + +Unity internal calls can be recovered. + +```cs +// Ignore the use of string, rather than Il2CppSystem.String +public static class SceneUtility +{ + private delegate IntPtr GetScenePathByBuildIndexDelegate(int buildIndex); + + private delegate int GetBuildIndexByScenePathDelegate(IntPtr scenePath); + + private static readonly GetScenePathByBuildIndexDelegate GetScenePathByBuildIndexDelegateField = IL2CPP.ResolveICall("UnityEngine.SceneManagement.SceneUtility::GetScenePathByBuildIndex"); + + private static readonly GetBuildIndexByScenePathDelegate GetBuildIndexByScenePathDelegateField = IL2CPP.ResolveICall("UnityEngine.SceneManagement.SceneUtility::GetBuildIndexByScenePath"); + + public static string GetScenePathByBuildIndex(int buildIndex) + { + return IL2CPP.Il2CppStringToManaged(GetScenePathByBuildIndexDelegateField(buildIndex)); + } + + public static int GetBuildIndexByScenePath(string scenePath) + { + return GetBuildIndexByScenePathDelegateField(IL2CPP.ManagedStringToIl2Cpp(scenePath)); + } +} +``` + +## callvirt + +```cs +public static T* ThrowIfNull(this T* pointer) where T : struct +{ + if (pointer is null) + { + throw new Il2CppSystem.NullReferenceException(); + } + return pointer; +} +public static T ThrowIfNull(this T obj) where T : class +{ + if (obj is null) + { + throw new Il2CppSystem.NullReferenceException(); + } + return obj; +} +``` + +Inserting this before any `callvirt` instruction ensures that correct semantics are maintained. + +## ldelem/stelem/ldlen + +Null checking and bounds checking are handled in the array helper methods. + +## Numeric op codes + +These are untyped, so some rudimentary analysis is needed to determine the type. diff --git a/Documentation/Design/ValueTypes.md b/Documentation/Design/ValueTypes.md new file mode 100644 index 00000000..4e0bc3ef --- /dev/null +++ b/Documentation/Design/ValueTypes.md @@ -0,0 +1,9 @@ +# Value Types + +## `readonly` + +Structs that were originally readonly no longer are. + +## `IValueType` + +All value types implement this interface. It replaces `ValueType` anywhere that would normally be used, like in generic constraints. diff --git a/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj b/Il2CppInterop.CLI/Il2CppInterop.CLI.csproj index 1e5a507e..acf4371b 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,17 @@ 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/Il2CppEventAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppEventAttribute.cs new file mode 100644 index 00000000..e4fb36a9 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/Il2CppEventAttribute.cs @@ -0,0 +1,9 @@ +namespace Il2CppInterop.Common.Attributes; + +/// +/// Indicates that the attributed method is associated with an event in the IL2CPP runtime. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +public sealed class Il2CppEventAttribute : Il2CppMemberAttribute +{ +} diff --git a/Il2CppInterop.Common/Attributes/Il2CppFieldAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppFieldAttribute.cs new file mode 100644 index 00000000..e9cdf889 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/Il2CppFieldAttribute.cs @@ -0,0 +1,10 @@ +namespace Il2CppInterop.Common.Attributes; + +/// +/// Indicates that the attributed field is associated with a field in the IL2CPP runtime. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +public sealed class Il2CppFieldAttribute : Il2CppMemberAttribute +{ + public int Index { get; init; } = -1; +} diff --git a/Il2CppInterop.Common/Attributes/Il2CppFinalizerAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppFinalizerAttribute.cs new file mode 100644 index 00000000..c6ed8772 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/Il2CppFinalizerAttribute.cs @@ -0,0 +1,9 @@ +namespace Il2CppInterop.Common.Attributes; + +/// +/// Indicates that the attributed method should be called in the source generated finalizer for an injected class. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +public sealed class Il2CppFinalizerAttribute : Attribute +{ +} diff --git a/Il2CppInterop.Common/Attributes/Il2CppMemberAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppMemberAttribute.cs new file mode 100644 index 00000000..80151a27 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/Il2CppMemberAttribute.cs @@ -0,0 +1,9 @@ +namespace Il2CppInterop.Common.Attributes; + +public abstract class Il2CppMemberAttribute : Attribute +{ + public string? Name { get; init; } + private protected Il2CppMemberAttribute() + { + } +} diff --git a/Il2CppInterop.Common/Attributes/Il2CppMethodAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppMethodAttribute.cs new file mode 100644 index 00000000..d140b768 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/Il2CppMethodAttribute.cs @@ -0,0 +1,10 @@ +namespace Il2CppInterop.Common.Attributes; + +/// +/// Indicates that the attributed method is associated with a method in the IL2CPP runtime. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +public sealed class Il2CppMethodAttribute : Il2CppMemberAttribute +{ + public int Index { get; init; } = -1; +} diff --git a/Il2CppInterop.Common/Attributes/Il2CppPropertyAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppPropertyAttribute.cs new file mode 100644 index 00000000..aca20c33 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/Il2CppPropertyAttribute.cs @@ -0,0 +1,12 @@ +namespace Il2CppInterop.Common.Attributes; + +/// +/// Indicates that the attributed property is associated with a property in the IL2CPP runtime. +/// +/// +/// Marking the accessor methods with is optional. +/// +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +public sealed class Il2CppPropertyAttribute : Il2CppMemberAttribute +{ +} diff --git a/Il2CppInterop.Common/Attributes/Il2CppTypeAttribute.cs b/Il2CppInterop.Common/Attributes/Il2CppTypeAttribute.cs new file mode 100644 index 00000000..b4ca89a2 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/Il2CppTypeAttribute.cs @@ -0,0 +1,7 @@ +namespace Il2CppInterop.Common.Attributes; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] +public sealed class Il2CppTypeAttribute(Type internals) : Attribute +{ + public Type Internals { get; } = internals; +} diff --git a/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs b/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs new file mode 100644 index 00000000..c2989a5d --- /dev/null +++ b/Il2CppInterop.Common/Attributes/InjectedTypeAttribute.cs @@ -0,0 +1,18 @@ +namespace Il2CppInterop.Common.Attributes; + +/// +/// Indicates that a type should be source generated with Il2Cpp injection code. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] +public sealed class InjectedTypeAttribute : Attribute +{ + /// + /// The name of the type. If not specified, it will be inferred from the managed type's name. + /// + public string? Name { get; init; } + /// + /// The file name of the assembly that the type is defined in. + /// If not specified, the assembly name will be inferred from the type's containing assembly. + /// + public string? Assembly { get; init; } +} diff --git a/Il2CppInterop.Common/Attributes/ManagedFieldAttribute.cs b/Il2CppInterop.Common/Attributes/ManagedFieldAttribute.cs new file mode 100644 index 00000000..3d5b3ff6 --- /dev/null +++ b/Il2CppInterop.Common/Attributes/ManagedFieldAttribute.cs @@ -0,0 +1,14 @@ +namespace Il2CppInterop.Common.Attributes; + +/// +/// Indicates that the attributed partial property is an injected field with an uninjected reference type. +/// Code will be source generated to properly reference it using a managed GCHandle, which is freed when the object is collected by the Il2Cpp GC. +/// +/// +/// Storing an Il2Cpp object in this field can cause memory leaks in some cases. +/// +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +public sealed class ManagedFieldAttribute : Attribute +{ + public string? Name { get; init; } +} diff --git a/Il2CppInterop.Common/Attributes/ObfuscatedNameAttribute.cs b/Il2CppInterop.Common/Attributes/ObfuscatedNameAttribute.cs deleted file mode 100644 index a0b6c54f..00000000 --- a/Il2CppInterop.Common/Attributes/ObfuscatedNameAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Il2CppInterop.Common.Attributes; - -[AttributeUsage(AttributeTargets.All, Inherited = false)] -public class ObfuscatedNameAttribute : Attribute -{ - public readonly string ObfuscatedName; - - public ObfuscatedNameAttribute(string obfuscatedName) - { - ObfuscatedName = obfuscatedName; - } -} diff --git a/Il2CppInterop.Common/Attributes/OriginalNameAttribute.cs b/Il2CppInterop.Common/Attributes/OriginalNameAttribute.cs deleted file mode 100644 index ff798fca..00000000 --- a/Il2CppInterop.Common/Attributes/OriginalNameAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Il2CppInterop.Common.Attributes; - -// This attribute is only applied to enums so the class pointer -// can be resolved correctly at runtime -// https://github.com/BepInEx/Il2CppInterop/issues/66 -[AttributeUsage(AttributeTargets.Enum, Inherited = false)] -public class OriginalNameAttribute : Attribute -{ - public readonly string AssemblyName; - public readonly string Namespace; - public readonly string Name; - - public OriginalNameAttribute(string assemblyName, string @namespace, string name) - { - AssemblyName = assemblyName; - Namespace = @namespace; - Name = name; - } -} diff --git a/Il2CppInterop.Common/Host/BaseHost.cs b/Il2CppInterop.Common/Host/BaseHost.cs index eee92bc2..f95652d3 100644 --- a/Il2CppInterop.Common/Host/BaseHost.cs +++ b/Il2CppInterop.Common/Host/BaseHost.cs @@ -2,7 +2,7 @@ public abstract class BaseHost : IDisposable { - private static BaseHost s_instance; + private static BaseHost? s_instance; protected static T GetInstance() where T : BaseHost { diff --git a/Il2CppInterop.Common/IIl2CppType.cs b/Il2CppInterop.Common/IIl2CppType.cs new file mode 100644 index 00000000..cb6bc49f --- /dev/null +++ b/Il2CppInterop.Common/IIl2CppType.cs @@ -0,0 +1,148 @@ +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; + } + } + } + /// + /// The file name of the assembly that the type is defined in + /// + /// + /// Technically, this is the image name, not the assembly name. + /// In practice, the assembly name has no file extension, whereas the image name does. + /// + static virtual string AssemblyName + { + get + { + var result = typeof(TSelf).Assembly.GetName().Name; + return string.IsNullOrEmpty(result) + ? "Assembly-CSharp.dll" + : result.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) + ? result + : $"{result}.dll"; + } + } + /// + /// The namespace of type + /// + static virtual string Namespace => typeof(TSelf).Namespace ?? ""; + /// + /// The class name of the type + /// + static virtual string Name => typeof(TSelf).Name; + /// + /// Writes the native representation of the value to the provided span. The span is required to be at least bytes long. + /// + /// The value to write. + /// The span to write the value to. + static 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..c7994ba7 --- /dev/null +++ b/Il2CppInterop.Common/IL2CPP.cs @@ -0,0 +1,871 @@ +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); + + [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); + + [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..2ffa912e 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..514fcc45 --- /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 initializer found for class {systemType}"); + } + } + + var newObj = initializer.Invoke((ObjectPointer)ptr); + if (!newObj.GetType().IsValueType) + { + s_cache[ptr] = new WeakReference(newObj); + } + + return newObj; + } + + private static void RegisterInitializer(nint classPtr, InitializerFunction initializer) + { + ArgumentOutOfRangeException.ThrowIfZero(classPtr); + if (!s_initializers.TryAdd(classPtr, initializer)) + { + var className = IL2CPP.il2cpp_class_get_name(classPtr); + throw new InvalidOperationException($"Initializer for class {className} is already registered"); + } + } + + internal static unsafe void RegisterInitializer() where T : IIl2CppType + { + RegisterInitializer(Il2CppType.GetClassPointer(), new InitializerFunction(&TypeInitializer)); + + static object TypeInitializer(ObjectPointer obj) + { + return T.UnboxNative(obj)!; + } + } + + private readonly unsafe struct InitializerFunction + { + private readonly delegate* initializer; + + public InitializerFunction(delegate* initializer) + { + this.initializer = initializer; + } + + public object Invoke(ObjectPointer obj) + { + return initializer(obj); + } + } + + [RequiresDynamicCode("")] + [RequiresUnreferencedCode("")] + private static Type EnsureClassInitialized(nint classPointer) + { + ArgumentOutOfRangeException.ThrowIfZero(classPointer); + var il2CppSystemType = Il2CppSystemTypeFromClassPointer(null, classPointer); + var systemType = Il2CppSystemTypeToSystemType(null, il2CppSystemType); + RuntimeHelpers.RunClassConstructor(systemType.TypeHandle); + return systemType; + + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "FromClassPointer")] + [return: UnsafeAccessorType("Il2CppSystem.Type, Il2Cppmscorlib")] + static extern object Il2CppSystemTypeFromClassPointer([UnsafeAccessorType("Il2CppInterop.Runtime.Extensions.Il2CppSystemTypeExtensions, Il2CppInterop.Runtime")] object? obj, nint classPointer); + + [RequiresDynamicCode("")] + [RequiresUnreferencedCode("")] + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ToSystemType")] + static extern Type Il2CppSystemTypeToSystemType([UnsafeAccessorType("Il2CppInterop.Runtime.Extensions.Il2CppSystemTypeExtensions, Il2CppInterop.Runtime")] object? obj, [UnsafeAccessorType("Il2CppSystem.Type, Il2Cppmscorlib")] object il2CppSystemType); + } +} diff --git a/Il2CppInterop.Common/Il2CppType.cs b/Il2CppInterop.Common/Il2CppType.cs new file mode 100644 index 00000000..bdaa03ce --- /dev/null +++ b/Il2CppInterop.Common/Il2CppType.cs @@ -0,0 +1,196 @@ +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Il2CppInterop.Common; + +public static class Il2CppType +{ + private static readonly nint Il2CppSystemVoidClassPointer = GetIl2CppSystemVoidClassPointer(); + + private static unsafe nint GetIl2CppSystemVoidClassPointer() + { + var domain = IL2CPP.il2cpp_domain_get(); + if (domain == nint.Zero) + { + return nint.Zero; + } + + var assemblies = IL2CPP.il2cpp_domain_get_assemblies(domain, out var assembliesCount); + for (var i = 0; i < assembliesCount; i++) + { + var image = IL2CPP.il2cpp_assembly_get_image(assemblies[i]); + var imageName = IL2CPP.il2cpp_image_get_name(image); + if (imageName == "mscorlib.dll") + { + return IL2CPP.il2cpp_class_from_name(image, "System", "Void"); + } + } + return nint.Zero; + } + + public static int SizeOf() where T : IIl2CppType + { + return T.Size; + } + + public static unsafe void InitializeObject(void* ptr) where T : IIl2CppType + { + default(T).WriteToPointer(ptr); + } + + public static unsafe void CopyObject(void* source, void* destination) where T : IIl2CppType + { + Buffer.MemoryCopy(source, destination, T.Size, T.Size); + } + + public static unsafe void StoreObject(void* ptr, T? value) where T : IIl2CppType + { + value.WriteToPointer(ptr); + } + + public static string GetAssemblyName() where T : IIl2CppType + { + return T.AssemblyName; + } + + public static string GetNamespace() where T : IIl2CppType + { + return T.Namespace; + } + + public static string GetName() where T : IIl2CppType + { + return T.Name; + } + + public static void WriteToSpan(this T? value, Span span) where T : IIl2CppType + { + T.WriteToSpan(value, span); + } + + public static T? ReadFromSpan(ReadOnlySpan span) where T : IIl2CppType + { + return T.ReadFromSpan(span); + } + + public static void WriteToSpanAtOffset(this T? value, Span span, int offset) where T : IIl2CppType + { + T.WriteToSpan(value, span.Slice(offset, T.Size)); + } + + public static T? ReadFromSpanAtOffset(ReadOnlySpan span, int offset) where T : IIl2CppType + { + return T.ReadFromSpan(span.Slice(offset, T.Size)); + } + + public static void WriteToSpanBlittable(T value, Span span) where T : unmanaged + { + MemoryMarshal.Write(span, in value); + } + + public static T ReadFromSpanBlittable(ReadOnlySpan span) where T : unmanaged + { + return MemoryMarshal.Read(span); + } + + public static unsafe void WriteToPointer(this T? value, void* ptr) where T : IIl2CppType + { + T.WriteToSpan(value, new Span(ptr, T.Size)); + } + + public static unsafe T? ReadFromPointer(void* ptr) where T : IIl2CppType + { + return T.ReadFromSpan(new ReadOnlySpan(ptr, T.Size)); + } + + public static T? ReadReference(ReadOnlySpan span) where T : IIl2CppType + { + return T.Unbox(Il2CppObjectPool.Get(ReadPointer(span))); + } + + public static void WriteReference(T? value, Span span) where T : IIl2CppType + { + var objectPointer = value?.BoxNative() ?? ObjectPointer.Null; + WritePointer((nint)objectPointer, span); + } + + public static nint ReadPointer(ReadOnlySpan span) + { + if (BitConverter.IsLittleEndian) + { + return BinaryPrimitives.ReadIntPtrLittleEndian(span); + } + else + { + return BinaryPrimitives.ReadIntPtrBigEndian(span); + } + } + + public static void WritePointer(nint pointer, Span span) + { + if (BitConverter.IsLittleEndian) + { + BinaryPrimitives.WriteIntPtrLittleEndian(span, pointer); + } + else + { + BinaryPrimitives.WriteIntPtrBigEndian(span, pointer); + } + } + + [RequiresDynamicCode("")] + public static nint GetClassPointer(Type type) + { + if (type == typeof(void)) + return Il2CppSystemVoidClassPointer; + + return (nint)typeof(Il2CppType) + .GetMethod(nameof(GetClassPointer), 1, [])! + .MakeGenericMethod(type) + .Invoke(null, null)!; + } + + [SuppressMessage("Trimming", "IL2059:The type passed to the RunClassConstructor is not statically known, Trimmer can't make sure that its static constructor is available.", Justification = "This will never be called in a trimmed context.")] + public static nint GetClassPointer() where T : IIl2CppType + { + RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); + return Il2CppClassPointerStore.NativeClassPointer; + } + + [RequiresDynamicCode("")] + public static void SetClassPointer(Type type, nint classPointer) + { + typeof(Il2CppType) + .GetMethod(nameof(SetClassPointer), 1, [typeof(nint)])! + .MakeGenericMethod(type) + .Invoke(null, [classPointer]); + } + + [SuppressMessage("Trimming", "IL2059:The type passed to the RunClassConstructor is not statically known, Trimmer can't make sure that its static constructor is available.", Justification = "This will never be called in a trimmed context.")] + public static void SetClassPointer(nint classPointer) where T : IIl2CppType + { + RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); + if (Il2CppClassPointerStore.NativeClassPointer != classPointer) + { + if (Il2CppClassPointerStore.NativeClassPointer == nint.Zero) + { + Il2CppClassPointerStore.NativeClassPointer = classPointer; + if (!typeof(T).IsAbstract && !typeof(T).IsInterface) + { + Il2CppObjectPool.RegisterInitializer(); + } + } + else + { + throw new InvalidOperationException($"Class pointer for type {typeof(T).FullName} has already been set."); + } + } + } + + private static class Il2CppClassPointerStore where T : IIl2CppType + { + public static nint NativeClassPointer; + } +} diff --git a/Il2CppInterop.Common/Logger.cs b/Il2CppInterop.Common/Logger.cs index cc5cafe6..8c2b74a4 100644 --- a/Il2CppInterop.Common/Logger.cs +++ b/Il2CppInterop.Common/Logger.cs @@ -4,30 +4,30 @@ namespace Il2CppInterop.Common; -public static class LoggerExtensions +public static class Logger { + public static ILogger Instance { get; private set; } = NullLogger.Instance; + public static T AddLogger(this T host, ILogger logger) where T : BaseHost { - host.AddComponent(new Logger(logger)); + host.AddComponent(new LoggerComponent(logger)); return host; } -} - -internal class Logger : IHostComponent -{ - public static ILogger Instance { get; private set; } = NullLogger.Instance; - public Logger(ILogger logger) + private sealed class LoggerComponent : IHostComponent { - Instance = logger; - } + public LoggerComponent(ILogger logger) + { + Instance = logger; + } - public void Start() - { - } + public void Start() + { + } - public void Dispose() - { - Instance = NullLogger.Instance; + public void Dispose() + { + Instance = NullLogger.Instance; + } } } diff --git a/Il2CppInterop.Common/Maps/MethodAddressToTokenMap.cs b/Il2CppInterop.Common/Maps/MethodAddressToTokenMap.cs deleted file mode 100644 index b993e551..00000000 --- a/Il2CppInterop.Common/Maps/MethodAddressToTokenMap.cs +++ /dev/null @@ -1,22 +0,0 @@ -#nullable enable - -using System.Reflection; - -namespace Il2CppInterop.Common.Maps; - -public class MethodAddressToTokenMap : MethodAddressToTokenMapBase -{ - public MethodAddressToTokenMap(string filePath) : base(filePath) - { - } - - protected override Assembly LoadAssembly(string assemblyName) - { - return Assembly.Load(assemblyName); - } - - protected override MethodBase? ResolveMethod(Assembly? assembly, int token) - { - return assembly?.ManifestModule.ResolveMethod(token); - } -} diff --git a/Il2CppInterop.Common/Maps/MethodAddressToTokenMapBase.cs b/Il2CppInterop.Common/Maps/MethodAddressToTokenMapBase.cs deleted file mode 100644 index b57b66f8..00000000 --- a/Il2CppInterop.Common/Maps/MethodAddressToTokenMapBase.cs +++ /dev/null @@ -1,197 +0,0 @@ -#nullable enable - -using System.Collections; -using System.IO.MemoryMappedFiles; -using System.Runtime.InteropServices; -using System.Text; - -namespace Il2CppInterop.Common.Maps; - -public abstract class MethodAddressToTokenMapBase : IDisposable, IEnumerable<(long, TMethod?)> -{ - public const int Magic = 0x4D544D55; // UMTM - public const int Version = 1; - public const string FileName = "MethodAddressToToken.db"; - private readonly MemoryMappedViewAccessor? myAccessor; - private readonly List myAssemblyList = new(); - - protected readonly string myFilePath; - - private readonly MethodAddressToTokenMapFileHeader myHeader; - - private readonly MemoryMappedFile? myMapFile; - - private unsafe long* myPointers; - private unsafe int* myValues; - - public MethodAddressToTokenMapBase(string filePath) - { - myFilePath = filePath; - myMapFile = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); - - var headerView = myMapFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); - myAccessor = headerView; - - headerView.Read(0, out myHeader); - - if (myHeader.Magic != Magic) - { - myMapFile.Dispose(); - throw new FileLoadException( - $"File magic mismatched for {filePath}; Expected {Magic:X}, got {myHeader.Magic:X}"); - } - - if (myHeader.Version != Version) - { - myMapFile.Dispose(); - throw new FileLoadException( - $"File version mismatched for {filePath}; Expected {Version}, got {myHeader.Version}"); - } - - var offset = Marshal.SizeOf(); - using var reader = new BinaryReader(myMapFile.CreateViewStream(offset, 0, MemoryMappedFileAccess.Read), - Encoding.UTF8, false); - for (var i = 0; i < myHeader.NumAssemblies; i++) - { - var assemblyName = reader.ReadString(); - myAssemblyList.Add(LoadAssembly(assemblyName)); - } - - unsafe - { - byte* pointersPointer = null; - - myAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointersPointer); - - myPointers = (long*)(pointersPointer + myHeader.DataOffset); - myValues = (int*)(pointersPointer + myHeader.DataOffset + myHeader.NumMethods * 8); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - IEnumerator<(long, TMethod?)> IEnumerable<(long, TMethod?)>.GetEnumerator() - { - return GetEnumerator(); - } - - protected abstract TAssembly? LoadAssembly(string assemblyName); - - private void ReleaseUnmanagedResources() - { - myAccessor?.SafeMemoryMappedViewHandle.ReleasePointer(); - unsafe - { - myPointers = null; - myValues = null; - } - } - - private void Dispose(bool disposing) - { - ReleaseUnmanagedResources(); - if (disposing) - { - myAccessor?.Dispose(); - myMapFile?.Dispose(); - } - } - - ~MethodAddressToTokenMapBase() - { - Dispose(false); - } - - public unsafe TMethod? Lookup(long parsedRva) - { - var left = 0; - var right = myHeader.NumMethods; - - while (right - left > 1) - { - var mid = (left + right) / 2; - var pointerAt = myPointers[mid]; - if (pointerAt > parsedRva) - right = mid; - else - left = mid; - } - - if (myPointers[left] != parsedRva) - return default; - - var dataToken = myValues[left * 2]; - var assemblyIdx = myValues[left * 2 + 1]; - - return ResolveMethod(myAssemblyList[assemblyIdx], dataToken); - } - - protected abstract TMethod? ResolveMethod(TAssembly? assembly, int token); - - public Enumerator GetEnumerator() - { - return new(this); - } - - public class Enumerator : IEnumerator<(long, TMethod?)> - { - private readonly MethodAddressToTokenMapBase myMap; - private int myOffset = -1; - - public Enumerator(MethodAddressToTokenMapBase map) - { - myMap = map; - } - - public unsafe bool MoveNext() - { - myOffset++; - if (myOffset < myMap.myHeader.NumMethods) - { - var dataToken = myMap.myValues[myOffset * 2]; - var assemblyIdx = myMap.myValues[myOffset * 2 + 1]; - - Current = (myMap.myPointers[myOffset], - myMap.ResolveMethod(myMap.myAssemblyList[assemblyIdx], dataToken)); - - return true; - } - - return false; - } - - public void Reset() - { - myOffset = -1; - } - - object IEnumerator.Current => Current; - - public void Dispose() - { - } - - public (long, TMethod?) Current { get; private set; } - } -} - -[StructLayout(LayoutKind.Sequential)] -public struct MethodAddressToTokenMapFileHeader -{ - public int Magic; - public int Version; - public int NumAssemblies; - public int NumMethods; - public int DataOffset; - - // data is long[NumMethods] pointers, (int, int)[NumMethods] (tokens, assemblyIds) -} diff --git a/Il2CppInterop.Common/Maps/MethodXrefScanCache.cs b/Il2CppInterop.Common/Maps/MethodXrefScanCache.cs deleted file mode 100644 index 5702df89..00000000 --- a/Il2CppInterop.Common/Maps/MethodXrefScanCache.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.IO.MemoryMappedFiles; -using System.Runtime.InteropServices; -using Il2CppInterop.Common.XrefScans; - -namespace Il2CppInterop.Common.Maps; - -public class MethodXrefScanCache : IDisposable -{ - public const int Magic = 0x43584D55; // UMXC - public const int Version = 1; - public const string FileName = "MethodXrefScanCache.db"; - - public readonly FileHeader Header; - private readonly MemoryMappedViewAccessor myAccessor; - - private readonly MemoryMappedFile myMapFile; - - private unsafe MethodData* myData; - - public MethodXrefScanCache(string filePath) - { - myMapFile = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); - - var headerView = myMapFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); - myAccessor = headerView; - - headerView.Read(0, out Header); - - if (Header.Magic != Magic) - { - myMapFile.Dispose(); - throw new FileLoadException( - $"File magic mismatched for {filePath}; Expected {Magic:X}, got {Header.Magic:X}"); - } - - if (Header.Version != Version) - { - myMapFile.Dispose(); - throw new FileLoadException( - $"File version mismatched for {filePath}; Expected {Version}, got {Header.Version}"); - } - - var offset = Marshal.SizeOf(); - - unsafe - { - byte* pointersPointer = null; - - myAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointersPointer); - - myData = (MethodData*)(pointersPointer + offset); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void ReleaseUnmanagedResources() - { - myAccessor.SafeMemoryMappedViewHandle.ReleasePointer(); - unsafe - { - myData = null; - } - } - - private void Dispose(bool disposing) - { - ReleaseUnmanagedResources(); - if (disposing) - { - myAccessor?.Dispose(); - myMapFile?.Dispose(); - } - } - - ~MethodXrefScanCache() - { - Dispose(false); - } - - internal unsafe ref MethodData GetAt(int index) - { - return ref *(myData + index); - } - - [StructLayout(LayoutKind.Sequential)] - public struct FileHeader - { - public int Magic; - public int Version; - public long InitMethodMetadataRva; - - // data is MethodData[] - } - - [StructLayout(LayoutKind.Sequential)] - public struct MethodData - { - public long Address; - public long FoundAt; - public XrefType Type; - - public XrefInstance AsXrefInstance(long baseAddress) - { - return new XrefInstance(Type, (IntPtr)(baseAddress + Address), (IntPtr)(baseAddress + FoundAt)); - } - - public static MethodData FromXrefInstance(XrefInstance instance) - { - return new MethodData - { - Address = (long)instance.Pointer, - FoundAt = (long)instance.FoundAt, - Type = instance.Type - }; - } - } -} diff --git a/Il2CppInterop.Common/MiniILParser.cs b/Il2CppInterop.Common/MiniILParser.cs deleted file mode 100644 index 40a1f2dc..00000000 --- a/Il2CppInterop.Common/MiniILParser.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Reflection; -using System.Reflection.Emit; - -namespace Il2CppInterop.Common; - -internal static class MiniIlParser -{ - private static readonly Dictionary OpCodesMap = new(); - private static readonly HashSet PrefixCodes = new(); - - static MiniIlParser() - { - foreach (var fieldInfo in typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public)) - { - if (fieldInfo.FieldType != typeof(OpCode)) continue; - - var fieldValue = (OpCode)fieldInfo.GetValue(null); - OpCodesMap[fieldValue.Value] = fieldValue; - if ((ushort)fieldValue.Value > byte.MaxValue) - PrefixCodes.Add((byte)((ushort)fieldValue.Value >> 8)); - } - } - - public static IEnumerable<(OpCode, long)> Decode(byte[] ilBytes) - { - var index = 0; - while (index < ilBytes.Length) - { - short currentOp = ilBytes[index++]; - if (PrefixCodes.Contains(currentOp)) - currentOp = (short)((ushort)(currentOp << 8) | ilBytes[index++]); - - if (!OpCodesMap.TryGetValue(currentOp, out var opCode)) - throw new NotSupportedException($"Unknown opcode {currentOp} encountered"); - - var argLength = GetOperandSize(opCode); - - switch (argLength) - { - case 0: - yield return (opCode, 0); - break; - case 1: - yield return (opCode, ilBytes[index]); - break; - case 2: - yield return (opCode, BitConverter.ToInt16(ilBytes, index)); - break; - case 4: - yield return (opCode, BitConverter.ToInt32(ilBytes, index)); - break; - case 8: - yield return (opCode, BitConverter.ToInt64(ilBytes, index)); - break; - default: - throw new NotSupportedException($"Unsupported opcode argument length {argLength}"); - } - - index += argLength; - } - } - - private static int GetOperandSize(OpCode opCode) - { - switch (opCode.OperandType) - { - case OperandType.InlineField: - case OperandType.InlineBrTarget: - case OperandType.InlineI: - case OperandType.InlineMethod: - case OperandType.InlineSig: - case OperandType.InlineString: - case OperandType.InlineSwitch: - case OperandType.InlineTok: - case OperandType.InlineType: - case OperandType.ShortInlineR: - return 4; - case OperandType.InlineI8: - case OperandType.InlineR: - return 8; - case OperandType.InlineNone: - return 0; - case OperandType.InlineVar: - return 2; - case OperandType.ShortInlineBrTarget: - case OperandType.ShortInlineVar: - case OperandType.ShortInlineI: - return 1; - default: - throw new ArgumentOutOfRangeException(); - } - } -} diff --git a/Il2CppInterop.Common/ObjectPointer.cs b/Il2CppInterop.Common/ObjectPointer.cs new file mode 100644 index 00000000..35b40150 --- /dev/null +++ b/Il2CppInterop.Common/ObjectPointer.cs @@ -0,0 +1,17 @@ +namespace Il2CppInterop.Common; + +public readonly record struct ObjectPointer(IntPtr Value) +{ + public static explicit operator ObjectPointer(IntPtr value) => new(value); + public static explicit operator IntPtr(ObjectPointer value) => value.Value; + + public static unsafe explicit operator ObjectPointer(void* value) => new((IntPtr)value); + public static unsafe explicit operator void*(ObjectPointer value) => (void*)value.Value; + + public static ObjectPointer Null => new(IntPtr.Zero); + + public static ObjectPointer New() where T : IIl2CppType + { + return (ObjectPointer)IL2CPP.il2cpp_object_new(Il2CppType.GetClassPointer()); + } +} diff --git a/Il2CppInterop.Common/XrefScans/GeneratedDatabasesUtil.cs b/Il2CppInterop.Common/XrefScans/GeneratedDatabasesUtil.cs deleted file mode 100644 index f8538077..00000000 --- a/Il2CppInterop.Common/XrefScans/GeneratedDatabasesUtil.cs +++ /dev/null @@ -1,16 +0,0 @@ -#nullable enable -using System.Reflection; - -namespace Il2CppInterop.Common.XrefScans; - -internal static class GeneratedDatabasesUtil -{ - private static string? DatabasesLocationOverride => Environment.GetEnvironmentVariable("IL2CPP_INTEROP_DATABASES_LOCATION"); - - public static string GetDatabasePath(string databaseName) - { - return Path.Combine( - (DatabasesLocationOverride ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))!, - databaseName); - } -} diff --git a/Il2CppInterop.Common/XrefScans/IXrefScannerImpl.cs b/Il2CppInterop.Common/XrefScans/IXrefScannerImpl.cs deleted file mode 100644 index 4b8a5306..00000000 --- a/Il2CppInterop.Common/XrefScans/IXrefScannerImpl.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Il2CppInterop.Common.XrefScans; - -internal interface IXrefScannerImpl -{ - (XrefScanUtil.InitMetadataForMethod, IntPtr)? GetMetadataResolver(); - - bool XrefGlobalClassFilter(IntPtr movTarget); -} diff --git a/Il2CppInterop.Common/XrefScans/XrefInstance.cs b/Il2CppInterop.Common/XrefScans/XrefInstance.cs deleted file mode 100644 index e77fd9d0..00000000 --- a/Il2CppInterop.Common/XrefScans/XrefInstance.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Il2CppInterop.Common.XrefScans; - -public readonly struct XrefInstance -{ - public readonly XrefType Type; - public readonly nint Pointer; - public readonly nint FoundAt; - - public XrefInstance(XrefType type, nint pointer, nint foundAt) - { - Type = type; - Pointer = pointer; - FoundAt = foundAt; - } - - internal XrefInstance RelativeToBase(nint baseAddress) - { - return new XrefInstance(Type, Pointer - baseAddress, FoundAt - baseAddress); - } -} diff --git a/Il2CppInterop.Common/XrefScans/XrefScanMethodDb.cs b/Il2CppInterop.Common/XrefScans/XrefScanMethodDb.cs deleted file mode 100644 index 3958f4a7..00000000 --- a/Il2CppInterop.Common/XrefScans/XrefScanMethodDb.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; -using Il2CppInterop.Common.Attributes; -using Il2CppInterop.Common.Maps; - -namespace Il2CppInterop.Common.XrefScans; - -public static class XrefScanMethodDb -{ - private static readonly MethodAddressToTokenMap MethodMap; - private static readonly MethodXrefScanCache XrefScanCache; - private static readonly long GameAssemblyBase; - - private static XrefScanUtil.InitMetadataForMethod ourMetadataInitForMethodDelegate; - - static XrefScanMethodDb() - { - MethodMap = new MethodAddressToTokenMap( - GeneratedDatabasesUtil.GetDatabasePath(MethodAddressToTokenMap.FileName)); - XrefScanCache = new MethodXrefScanCache(GeneratedDatabasesUtil.GetDatabasePath(MethodXrefScanCache.FileName)); - - foreach (ProcessModule module in Process.GetCurrentProcess().Modules) - if (module.ModuleName == "GameAssembly.dll") - { - GameAssemblyBase = (long)module.BaseAddress; - break; - } - } - - public static MethodBase TryResolvePointer(IntPtr methodStart) - { - return MethodMap.Lookup((long)methodStart - GameAssemblyBase); - } - - internal static IEnumerable ListUsers(CachedScanResultsAttribute attribute) - { - for (var i = attribute.RefRangeStart; i < attribute.RefRangeEnd; i++) - yield return XrefScanCache.GetAt(i).AsXrefInstance(GameAssemblyBase); - } - - internal static IEnumerable CachedXrefScan(CachedScanResultsAttribute attribute) - { - for (var i = attribute.XrefRangeStart; i < attribute.XrefRangeEnd; i++) - yield return XrefScanCache.GetAt(i).AsXrefInstance(GameAssemblyBase); - } - - internal static void CallMetadataInitForMethod(CachedScanResultsAttribute attribute) - { - if (attribute.MetadataInitFlagRva == 0 || attribute.MetadataInitTokenRva == 0) - return; - - if (Marshal.ReadByte((IntPtr)(GameAssemblyBase + attribute.MetadataInitFlagRva)) != 0) - return; - - if (ourMetadataInitForMethodDelegate == null) - ourMetadataInitForMethodDelegate = - Marshal.GetDelegateForFunctionPointer( - (IntPtr)(GameAssemblyBase + XrefScanCache.Header.InitMethodMetadataRva)); - - var token = Marshal.ReadInt32((IntPtr)(GameAssemblyBase + attribute.MetadataInitTokenRva)); - - ourMetadataInitForMethodDelegate(token); - - Marshal.WriteByte((IntPtr)(GameAssemblyBase + attribute.MetadataInitFlagRva), 1); - } -} diff --git a/Il2CppInterop.Common/XrefScans/XrefScanUtil.cs b/Il2CppInterop.Common/XrefScans/XrefScanUtil.cs deleted file mode 100644 index 10f9b238..00000000 --- a/Il2CppInterop.Common/XrefScans/XrefScanUtil.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -namespace Il2CppInterop.Common.XrefScans; - -internal static class XrefScanUtil -{ - private static InitMetadataForMethod ourMetadataInitForMethodDelegate; - private static IntPtr ourMetadataInitForMethodPointer; - - internal static event Func<(InitMetadataForMethod, IntPtr)> InitRuntimeUtils; - - internal static unsafe bool CallMetadataInitForMethod(MethodBase method) - { - if (ourMetadataInitForMethodPointer == IntPtr.Zero) - { - var res = XrefScannerManager.Impl.GetMetadataResolver(); - if (res is ({ } m, var p)) - { - ourMetadataInitForMethodDelegate = m; - ourMetadataInitForMethodPointer = p; - } - else - { - return false; - } - } - - var nativeMethodInfoObject = - Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(method)?.GetValue(null); - if (nativeMethodInfoObject == null) return false; - var nativeMethodInfo = (IntPtr)nativeMethodInfoObject; - var codeStart = *(IntPtr*)nativeMethodInfo; - var firstCall = XrefScannerLowLevel.JumpTargets(codeStart).FirstOrDefault(); - if (firstCall != ourMetadataInitForMethodPointer || firstCall == IntPtr.Zero) return false; - - var tokenPointer = - XrefScanUtilFinder.FindLastRcxReadAddressBeforeCallTo(codeStart, ourMetadataInitForMethodPointer); - var initFlagPointer = - XrefScanUtilFinder.FindByteWriteTargetRightAfterCallTo(codeStart, ourMetadataInitForMethodPointer); - - if (tokenPointer == IntPtr.Zero || initFlagPointer == IntPtr.Zero) return false; - - if (Marshal.ReadByte(initFlagPointer) == 0) - { - ourMetadataInitForMethodDelegate(Marshal.ReadInt32(tokenPointer)); - Marshal.WriteByte(initFlagPointer, 1); - } - - return true; - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void InitMetadataForMethod(int metadataUsageToken); -} diff --git a/Il2CppInterop.Common/XrefScans/XrefScanUtilFinder.cs b/Il2CppInterop.Common/XrefScans/XrefScanUtilFinder.cs deleted file mode 100644 index 319a5a2e..00000000 --- a/Il2CppInterop.Common/XrefScans/XrefScanUtilFinder.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Iced.Intel; - -namespace Il2CppInterop.Common.XrefScans; - -internal static class XrefScanUtilFinder -{ - public static IntPtr FindLastRcxReadAddressBeforeCallTo(IntPtr codeStart, IntPtr callTarget) - { - var decoder = XrefScanner.DecoderForAddress(codeStart); - var lastRcxRead = IntPtr.Zero; - - while (true) - { - decoder.Decode(out var instruction); - if (decoder.LastError == DecoderError.NoMoreBytes) return IntPtr.Zero; - - if (instruction.FlowControl == FlowControl.Return) - return IntPtr.Zero; - - if (instruction.FlowControl == FlowControl.UnconditionalBranch) - continue; - - if (instruction.Mnemonic == Mnemonic.Int || instruction.Mnemonic == Mnemonic.Int1) - return IntPtr.Zero; - - if (instruction.Mnemonic == Mnemonic.Call) - { - var target = ExtractTargetAddress(instruction); - if ((IntPtr)target == callTarget) - return lastRcxRead; - } - - if (instruction.Mnemonic == Mnemonic.Mov) - if (instruction.Op0Kind == OpKind.Register && instruction.Op0Register == Register.ECX && - instruction.Op1Kind == OpKind.Memory && instruction.IsIPRelativeMemoryOperand) - { - var movTarget = (IntPtr)instruction.IPRelativeMemoryAddress; - if (instruction.MemorySize != MemorySize.UInt32 && instruction.MemorySize != MemorySize.Int32) - continue; - - lastRcxRead = movTarget; - } - } - } - - public static IntPtr FindByteWriteTargetRightAfterCallTo(IntPtr codeStart, IntPtr callTarget) - { - var decoder = XrefScanner.DecoderForAddress(codeStart); - var seenCall = false; - - while (true) - { - decoder.Decode(out var instruction); - if (decoder.LastError == DecoderError.NoMoreBytes) return IntPtr.Zero; - - if (instruction.FlowControl == FlowControl.Return) - return IntPtr.Zero; - - if (instruction.FlowControl == FlowControl.UnconditionalBranch) - continue; - - if (instruction.Mnemonic == Mnemonic.Int || instruction.Mnemonic == Mnemonic.Int1) - return IntPtr.Zero; - - if (instruction.Mnemonic == Mnemonic.Call) - { - var target = ExtractTargetAddress(instruction); - if ((IntPtr)target == callTarget) - seenCall = true; - } - - if (instruction.Mnemonic == Mnemonic.Mov && seenCall) - if (instruction.Op0Kind == OpKind.Memory && (instruction.MemorySize == MemorySize.Int8 || - instruction.MemorySize == MemorySize.UInt8)) - return (IntPtr)instruction.IPRelativeMemoryAddress; - } - } - - private static ulong ExtractTargetAddress(in Instruction instruction) - { - switch (instruction.Op0Kind) - { - case OpKind.NearBranch16: - return instruction.NearBranch16; - case OpKind.NearBranch32: - return instruction.NearBranch32; - case OpKind.NearBranch64: - return instruction.NearBranch64; - case OpKind.FarBranch16: - return instruction.FarBranch16; - case OpKind.FarBranch32: - return instruction.FarBranch32; - default: - return 0; - } - } -} diff --git a/Il2CppInterop.Common/XrefScans/XrefScanner.cs b/Il2CppInterop.Common/XrefScans/XrefScanner.cs deleted file mode 100644 index e16c22d7..00000000 --- a/Il2CppInterop.Common/XrefScans/XrefScanner.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Reflection; -using Iced.Intel; -using Il2CppInterop.Common.Attributes; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Common.XrefScans; - -public static class XrefScanner -{ - public static unsafe IEnumerable XrefScan(MethodBase methodBase) - { - var fieldValue = Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(methodBase) - ?.GetValue(null); - if (fieldValue == null) return Enumerable.Empty(); - - CachedScanResultsAttribute? cachedAttribute = null; - try - { - cachedAttribute = methodBase.GetCustomAttribute(false); - } - catch (Exception e) - { - Logger.Instance.LogWarning("Failed to get custom attribute for {TypeName}.{MethodName}: {Error}. Falling back to scanning", methodBase.DeclaringType!.FullName, methodBase.Name, e.Message); - } - - if (cachedAttribute == null) - { - XrefScanUtil.CallMetadataInitForMethod(methodBase); - - return XrefScanImpl(DecoderForAddress(*(IntPtr*)(IntPtr)fieldValue)); - } - - if (cachedAttribute.XrefRangeStart == cachedAttribute.XrefRangeEnd) - return Enumerable.Empty(); - - XrefScanMethodDb.CallMetadataInitForMethod(cachedAttribute); - - return XrefScanMethodDb.CachedXrefScan(cachedAttribute).Where(it => - it.Type == XrefType.Method || XrefScannerManager.Impl.XrefGlobalClassFilter(it.Pointer)); - } - - public static IEnumerable UsedBy(MethodBase methodBase) - { - var cachedAttribute = methodBase.GetCustomAttribute(false); - if (cachedAttribute == null || cachedAttribute.RefRangeStart == cachedAttribute.RefRangeEnd) - return Enumerable.Empty(); - - return XrefScanMethodDb.ListUsers(cachedAttribute); - } - - internal static unsafe Decoder DecoderForAddress(IntPtr codeStart, int lengthLimit = 1000) - { - if (codeStart == IntPtr.Zero) throw new NullReferenceException(nameof(codeStart)); - - var stream = new UnmanagedMemoryStream((byte*)codeStart, lengthLimit, lengthLimit, FileAccess.Read); - var codeReader = new StreamCodeReader(stream); - var decoder = Decoder.Create(IntPtr.Size * 8, codeReader); - decoder.IP = (ulong)codeStart; - - return decoder; - } - - internal static IEnumerable XrefScanImpl(Decoder decoder, bool skipClassCheck = false) - { - while (true) - { - decoder.Decode(out var instruction); - if (decoder.LastError == DecoderError.NoMoreBytes) yield break; - - if (instruction.FlowControl == FlowControl.Return) - yield break; - - if (instruction.Mnemonic == Mnemonic.Int || instruction.Mnemonic == Mnemonic.Int1) - yield break; - - if (instruction.Mnemonic == Mnemonic.Call || instruction.Mnemonic == Mnemonic.Jmp) - { - var targetAddress = ExtractTargetAddress(instruction); - if (targetAddress != 0) - yield return new XrefInstance(XrefType.Method, (nint)targetAddress, (nint)instruction.IP); - continue; - } - - if (instruction.FlowControl == FlowControl.UnconditionalBranch) - continue; - - if (IsMoveMnemonic(instruction.Mnemonic)) - { - XrefInstance? result = null; - try - { - if (instruction.Op1Kind == OpKind.Memory && instruction.IsIPRelativeMemoryOperand) - { - var movTarget = (IntPtr)instruction.IPRelativeMemoryAddress; - if (instruction.MemorySize != MemorySize.UInt64) - continue; - - if (skipClassCheck || XrefScannerManager.Impl.XrefGlobalClassFilter(movTarget)) - result = new XrefInstance(XrefType.Global, movTarget, (IntPtr)instruction.IP); - } - } - catch (Exception ex) - { - Logger.Instance.LogError("{Error}", ex.ToString()); - } - - if (result != null) - yield return result.Value; - } - } - } - - internal static bool IsMoveMnemonic(Mnemonic mnemonic) - { - return mnemonic is Mnemonic.Mov or >= Mnemonic.Cmova and <= Mnemonic.Cmovs; - } - - internal static ulong ExtractTargetAddress(in Instruction instruction) - { - switch (instruction.Op0Kind) - { - case OpKind.NearBranch16: - return instruction.NearBranch16; - case OpKind.NearBranch32: - return instruction.NearBranch32; - case OpKind.NearBranch64: - return instruction.NearBranch64; - case OpKind.FarBranch16: - return instruction.FarBranch16; - case OpKind.FarBranch32: - return instruction.FarBranch32; - default: - return 0; - } - } -} diff --git a/Il2CppInterop.Common/XrefScans/XrefScannerManager.cs b/Il2CppInterop.Common/XrefScans/XrefScannerManager.cs deleted file mode 100644 index 8025a517..00000000 --- a/Il2CppInterop.Common/XrefScans/XrefScannerManager.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Il2CppInterop.Common.Host; - -namespace Il2CppInterop.Common.XrefScans; - -internal static class XrefScannerManagerExtensions -{ - public static T AddXrefScanner(this T host) where T : BaseHost where TScanner : IXrefScannerImpl, new() - { - host.AddComponent(new XrefScannerManager(new TScanner())); - return host; - } -} - -internal class XrefScannerManager : IHostComponent -{ - private static IXrefScannerImpl s_xrefScanner; - - public static IXrefScannerImpl Impl - { - get - { - if (s_xrefScanner == null) - { - throw new InvalidOperationException("XrefScanner is not initialized! Initialize the host before using XrefScanner!"); - } - - return s_xrefScanner; - } - } - - public XrefScannerManager(IXrefScannerImpl impl) - { - s_xrefScanner = impl; - } - - public void Dispose() - { - s_xrefScanner = null; - } - - public void Start() { } -} diff --git a/Il2CppInterop.Common/XrefScans/XrefType.cs b/Il2CppInterop.Common/XrefScans/XrefType.cs deleted file mode 100644 index 1a1501f7..00000000 --- a/Il2CppInterop.Common/XrefScans/XrefType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Il2CppInterop.Common.XrefScans; - -public enum XrefType -{ - Global, - Method -} diff --git a/Il2CppInterop.Generator/AsmResolverDllOutputFormatBinding.cs b/Il2CppInterop.Generator/AsmResolverDllOutputFormatBinding.cs new file mode 100644 index 00000000..56d7b6f7 --- /dev/null +++ b/Il2CppInterop.Generator/AsmResolverDllOutputFormatBinding.cs @@ -0,0 +1,69 @@ +using AsmResolver.DotNet; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.OutputFormats; + +namespace Il2CppInterop.Generator; + +public class AsmResolverDllOutputFormatBinding : AsmResolverDllOutputFormatThrowNull +{ + public override string OutputFormatId => "dll_binding"; + + public override string OutputFormatName => "DLL files with method bodies containing a binding for modding."; + + protected override void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext) + { + if (methodContext.TryGetExtraData(out TranslatedMethodBody? translatedBody)) + { + translatedBody.FillMethodBody(methodDefinition); + methodContext.RemoveExtraData(); // Free up memory + } + else if (methodContext.TryGetExtraData(out NativeMethodBody? nativeBody)) + { + nativeBody.FillMethodBody(methodDefinition); + methodContext.RemoveExtraData(); // Free up memory + } + else + { + // This gets called when the body of an unstripped method could not be translated. + // We could have it throw a custom exception with a message, but throwing null is sufficient for now. + base.FillMethodBody(methodDefinition, methodContext); + } + } + + public override List BuildAssemblies(ApplicationAnalysisContext context) + { + var list = base.BuildAssemblies(context); + + var referenceAssemblies = context.Assemblies.Where(a => a.IsReferenceAssembly).Select(a => a.Name).ToHashSet(); + + // Remove injected reference assemblies from the output + for (var i = list.Count - 1; i >= 0; i--) + { + if (referenceAssemblies.Contains(list[i].Name ?? "")) + list.RemoveAt(i); + } + + // Replace mscorlib references with .NET Core references + var dotNetCorLib = KnownCorLibs.SystemRuntime_v10_0_0_0; + foreach (var assembly in list) + { + foreach (var module in assembly.Modules) + { + foreach (var reference in module.AssemblyReferences) + { + if (reference.Name == "mscorlib") + { + reference.Name = dotNetCorLib.Name; + reference.Version = dotNetCorLib.Version; + reference.Attributes = dotNetCorLib.Attributes; + reference.PublicKeyOrToken = dotNetCorLib.PublicKeyOrToken; + reference.HashValue = dotNetCorLib.HashValue; + reference.Culture = dotNetCorLib.Culture; + } + } + } + } + + return list; + } +} diff --git a/Il2CppInterop.Generator/AsmResolverDllOutputFormatUnstripped.cs b/Il2CppInterop.Generator/AsmResolverDllOutputFormatUnstripped.cs new file mode 100644 index 00000000..10341a63 --- /dev/null +++ b/Il2CppInterop.Generator/AsmResolverDllOutputFormatUnstripped.cs @@ -0,0 +1,24 @@ +using AsmResolver.DotNet; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.OutputFormats; + +namespace Il2CppInterop.Generator; + +public class AsmResolverDllOutputFormatUnstripped : AsmResolverDllOutputFormatThrowNull +{ + public override string OutputFormatId => "dll_unstripped"; + + public override string OutputFormatName => "DLL files with method bodies containing an unstripped implementation if available."; + + protected override void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext) + { + if (methodContext.TryGetExtraData(out OriginalMethodBody? originalBody)) + { + originalBody.FillMethodBody(methodDefinition); + } + else + { + base.FillMethodBody(methodDefinition, methodContext); + } + } +} diff --git a/Il2CppInterop.Generator/AttributeRemovalProcessingLayer.cs b/Il2CppInterop.Generator/AttributeRemovalProcessingLayer.cs new file mode 100644 index 00000000..1ce4806c --- /dev/null +++ b/Il2CppInterop.Generator/AttributeRemovalProcessingLayer.cs @@ -0,0 +1,93 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Model.CustomAttributes; + +namespace Il2CppInterop.Generator; + +public class AttributeRemovalProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "attribute_removal"; + public override string Name => "Attribute Removal"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var isUnmanagedAttributeType = appContext.GetAssemblyByName("mscorlib")?.GetTypeByFullName("System.Runtime.CompilerServices.IsUnmanagedAttribute"); + + if (isUnmanagedAttributeType is null) + { + Logger.WarnNewline("IsUnmanagedAttribute not found. They cannot be recovered.", nameof(AttributeRemovalProcessingLayer)); + } + + var isUnmanagedAttributeConstructor = isUnmanagedAttributeType?.Methods.First(m => m.Name == ".ctor" && m.Parameters.Count == 0); + + foreach (var assembly in appContext.Assemblies) + { + ClearAttributes(assembly); + } + + foreach (var type in appContext.AllTypes) + { + ClearAttributes(type); + + foreach (var genericParameter in type.GenericParameters) + { + ClearGenericParameterAttributes(genericParameter, isUnmanagedAttributeConstructor); + } + + foreach (var field in type.Fields) + { + ClearAttributes(field); + } + + foreach (var method in type.Methods) + { + ClearAttributes(method); + + foreach (var parameter in method.Parameters) + { + ClearAttributes(parameter); + } + + foreach (var genericParameter in method.GenericParameters) + { + ClearGenericParameterAttributes(genericParameter, isUnmanagedAttributeConstructor); + } + } + + foreach (var property in type.Properties) + { + ClearAttributes(property); + } + + foreach (var @event in type.Events) + { + ClearAttributes(@event); + } + } + } + + private static void ClearGenericParameterAttributes(GenericParameterTypeAnalysisContext genericParameter, MethodAnalysisContext? isUnmanagedAttributeConstructor) + { + if (isUnmanagedAttributeConstructor is not null && HasIsUnmanagedAttribute(genericParameter)) + { + ClearAttributes(genericParameter); + genericParameter.CustomAttributes ??= new(1); + genericParameter.CustomAttributes.Add(new AnalyzedCustomAttribute(isUnmanagedAttributeConstructor)); + } + else + { + ClearAttributes(genericParameter); + } + + static bool HasIsUnmanagedAttribute(GenericParameterTypeAnalysisContext genericParameter) + { + return genericParameter.HasCustomAttributeWithFullName("System.Runtime.CompilerServices.IsUnmanagedAttribute") + || genericParameter.HasCustomAttributeWithFullName("Il2CppSystem.Runtime.CompilerServices.IsUnmanagedAttribute"); + } + } + + private static void ClearAttributes(HasCustomAttributes context) + { + context.AttributeTypes?.Clear(); + context.CustomAttributes?.Clear(); + } +} diff --git a/Il2CppInterop.Generator/AttributesOverrideProcessingLayer.cs b/Il2CppInterop.Generator/AttributesOverrideProcessingLayer.cs new file mode 100644 index 00000000..f3aeac04 --- /dev/null +++ b/Il2CppInterop.Generator/AttributesOverrideProcessingLayer.cs @@ -0,0 +1,143 @@ +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class AttributesOverrideProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "attributes_override"; + public override string Name => "Attributes Override"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + { + continue; + } + + assembly.Culture = null; + assembly.Version = new(0, 0, 0, 0); + assembly.PublicKey = null; + assembly.PublicKeyToken = null; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + { + continue; + } + + if (type.IsStatic) + { + type.Attributes &= ~TypeAttributes.Sealed; + } + + // Remove bad flags from type + { +#pragma warning disable SYSLIB0050 // Type or member is obsolete + const TypeAttributes TypeFlagsToRemove = + TypeAttributes.AutoClass | + TypeAttributes.HasSecurity | + TypeAttributes.Import | + TypeAttributes.RTSpecialName | + TypeAttributes.Serializable | + TypeAttributes.UnicodeClass | + TypeAttributes.WindowsRuntime; +#pragma warning restore SYSLIB0050 // Type or member is obsolete + + type.Attributes &= ~TypeFlagsToRemove; + } + + if (type.IsValueType) + { + const TypeAttributes TypeFlagsToRemove = + TypeAttributes.Abstract | + TypeAttributes.ClassSemanticsMask; + type.Attributes &= ~TypeFlagsToRemove; + } + else + { + type.Attributes &= ~TypeAttributes.LayoutMask; + } + + foreach (var genericParameter in type.GenericParameters) + { + const GenericParameterAttributes GenericParamFlagsToRemove = + GenericParameterAttributes.Covariant | + GenericParameterAttributes.Contravariant; + genericParameter.Attributes &= ~GenericParamFlagsToRemove; + } + + foreach (var method in type.Methods) + { + if (method.IsInjected) + { + continue; + } + + const MethodAttributes FlagsToRemove = + MethodAttributes.HasSecurity | + MethodAttributes.RequireSecObject | + MethodAttributes.UnmanagedExport | + MethodAttributes.PinvokeImpl; + + method.Attributes &= ~FlagsToRemove; + + method.ImplAttributes = MethodImplAttributes.Managed; + + foreach (var parameter in method.Parameters) + { + const ParameterAttributes ParamFlagsToRemove = + ParameterAttributes.Optional | + ParameterAttributes.HasDefault | + ParameterAttributes.HasFieldMarshal; + parameter.Attributes &= ~ParamFlagsToRemove; + } + } + + foreach (var field in type.Fields) + { + if (field.IsInjected) + { + continue; + } + + if (field.Attributes.HasFlag(FieldAttributes.Literal)) + { + // Change constant fields to static readonly + field.Attributes = (field.Attributes & ~FieldAttributes.Literal) | FieldAttributes.InitOnly; + } + else + { + // Remove readonly from non-constant fields + field.Attributes &= ~FieldAttributes.InitOnly; + } + + const FieldAttributes FlagsToRemove = + FieldAttributes.HasFieldRVA | + FieldAttributes.HasDefault | + FieldAttributes.HasFieldMarshal; + + field.Attributes &= ~FlagsToRemove; + } + + foreach (var property in type.Properties) + { + if (property.IsInjected) + { + continue; + } + + const PropertyAttributes FlagsToRemove = + PropertyAttributes.HasDefault; + + property.Attributes &= ~FlagsToRemove; + } + + // There are no event attributes that need to be modified. + } + } + } +} diff --git a/Il2CppInterop.Generator/BoxingProcessingLayer.cs b/Il2CppInterop.Generator/BoxingProcessingLayer.cs new file mode 100644 index 00000000..88859b57 --- /dev/null +++ b/Il2CppInterop.Generator/BoxingProcessingLayer.cs @@ -0,0 +1,223 @@ +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Runtime; + +namespace Il2CppInterop.Generator; + +public sealed class BoxingProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Boxing Implementations"; + public override string Id => "boxing_implementations"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var iil2CppType = appContext.ResolveTypeOrThrow(typeof(IIl2CppType)); + var iil2CppType_BoxNative = iil2CppType.GetMethodByName(nameof(IIl2CppType.BoxNative)); + var iil2CppType_Box = iil2CppType.GetMethodByName(nameof(IIl2CppType.Box)); + + var iil2CppTypeGeneric = appContext.ResolveTypeOrThrow(typeof(IIl2CppType<>)); + var iil2CppTypeGeneric_UnboxNative = iil2CppTypeGeneric.GetMethodByName(nameof(IIl2CppType<>.UnboxNative)); + var iil2CppTypeGeneric_Unbox = iil2CppTypeGeneric.GetMethodByName(nameof(IIl2CppType<>.Unbox)); + + var nativeBoxing = appContext.ResolveTypeOrThrow(typeof(NativeBoxing)); + var nativeBoxing_BoxReferenceType = nativeBoxing.GetMethodByName(nameof(NativeBoxing.BoxReferenceType)); + var nativeBoxing_BoxValueType = nativeBoxing.GetMethodByName(nameof(NativeBoxing.BoxValueType)); + var nativeBoxing_BoxNullableValueType = nativeBoxing.GetMethodByName(nameof(NativeBoxing.BoxNullableValueType)); + var nativeBoxing_UnboxNullableValueType = nativeBoxing.GetMethodByName(nameof(NativeBoxing.UnboxNullableValueType)); + + var generationInternals = appContext.ResolveTypeOrThrow(typeof(GenerationInternals)); + var generationInternals_BoxNullableValueType = generationInternals.GetMethodByName(nameof(GenerationInternals.BoxNullableValueType)); + var generationInternals_UnboxNullableValueType = generationInternals.GetMethodByName(nameof(GenerationInternals.UnboxNullableValueType)); + + var objectPointer = appContext.ResolveTypeOrThrow(typeof(ObjectPointer)); + + var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + var il2CppSystemNullable = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Nullable`1"); + + // Il2CppSystem.Object needs to implement BoxNative + { + var method = new InjectedMethodAnalysisContext( + il2CppSystemObject, + $"{iil2CppType.FullName}.{iil2CppType_BoxNative.Name}", + iil2CppType_BoxNative.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot, + []) + { + IsInjected = true, + }; + method.Overrides.Add(iil2CppType_BoxNative); + il2CppSystemObject.Methods.Add(method); + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, nativeBoxing_BoxReferenceType), + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // Il2CppSystem.Nullable needs to implement UnboxNative + { + var interfaceMethod = iil2CppTypeGeneric_UnboxNative.MakeConcreteGeneric([il2CppSystemNullable.SelfInstantiateIfGeneric()], []); + var method = new InjectedMethodAnalysisContext( + il2CppSystemNullable, + $"{interfaceMethod.DeclaringType!.FullName}.{interfaceMethod.Name}", + interfaceMethod.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + [interfaceMethod.Parameters[0].ParameterType]) + { + IsInjected = true, + }; + method.Overrides.Add(interfaceMethod); + il2CppSystemNullable.Methods.Add(method); + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, nativeBoxing_UnboxNullableValueType.MakeConcreteGeneric([], il2CppSystemNullable.GenericParameters)), + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // Il2CppSystem.Nullable needs to implement Box + { + var method = new InjectedMethodAnalysisContext( + il2CppSystemNullable, + $"{iil2CppType.FullName}.{iil2CppType_Box.Name}", + iil2CppType_Box.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot, + []) + { + IsInjected = true, + }; + method.Overrides.Add(iil2CppType_Box); + il2CppSystemNullable.Methods.Add(method); + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, generationInternals_BoxNullableValueType.MakeGenericInstanceMethod(il2CppSystemNullable.GenericParameters)), + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // Il2CppSystem.Nullable needs to implement Unbox + { + var interfaceMethod = iil2CppTypeGeneric_Unbox.MakeConcreteGeneric([il2CppSystemNullable.SelfInstantiateIfGeneric()], []); + var method = new InjectedMethodAnalysisContext( + il2CppSystemNullable, + $"{interfaceMethod.DeclaringType!.FullName}.{interfaceMethod.Name}", + interfaceMethod.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + [interfaceMethod.Parameters[0].ParameterType]) + { + IsInjected = true, + }; + method.Overrides.Add(interfaceMethod); + il2CppSystemNullable.Methods.Add(method); + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, generationInternals_UnboxNullableValueType.MakeGenericInstanceMethod(il2CppSystemNullable.GenericParameters)), + new Instruction(CilOpCodes.Ret), + ] + }); + } + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + if (type.IsValueType) + { + // Value types need to implement BoxNative + var method = new InjectedMethodAnalysisContext( + type, + $"{iil2CppType.FullName}.{iil2CppType_BoxNative.Name}", + iil2CppType_BoxNative.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot, + []) + { + IsInjected = true, + }; + method.Overrides.Add(iil2CppType_BoxNative); + type.Methods.Add(method); + + var boxMethod = type == il2CppSystemNullable + ? nativeBoxing_BoxNullableValueType.MakeConcreteGeneric([], type.GenericParameters) + : nativeBoxing_BoxValueType.MaybeMakeConcreteGeneric([], [type.SelfInstantiateIfGeneric()]); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, boxMethod), + new Instruction(CilOpCodes.Ret), + ] + }); + } + else if (!type.IsAbstract && !type.IsInterface) + { + // Concrete reference types need to implement UnboxNative + var interfaceMethod = iil2CppTypeGeneric_UnboxNative.MakeConcreteGeneric([type.SelfInstantiateIfGeneric()], []); + var method = new InjectedMethodAnalysisContext( + type, + $"{interfaceMethod.DeclaringType!.FullName}.{interfaceMethod.Name}", + interfaceMethod.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + [interfaceMethod.Parameters[0].ParameterType]) + { + IsInjected = true, + }; + method.Overrides.Add(interfaceMethod); + type.Methods.Add(method); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Newobj, GetObjectPointerConstructor(type, objectPointer)), + new Instruction(CilOpCodes.Ret), + ] + }); + } + } + } + } + + private static MethodAnalysisContext GetObjectPointerConstructor(TypeAnalysisContext declaringType, TypeAnalysisContext objectPointer) + { + MethodAnalysisContext? constructor = null; + foreach (var method in declaringType.Methods) + { + if (method.IsInstanceConstructor && method.Parameters.Count is 1 && method.Parameters[0].ParameterType == objectPointer) + { + constructor = method; + break; + } + } + if (constructor == null) + { + throw new InvalidOperationException($"No suitable constructor found on {declaringType.FullName} that takes an ObjectPointer"); + } + return constructor.MaybeMakeConcreteGeneric(declaringType.GenericParameters, []); + } +} diff --git a/Il2CppInterop.Generator/ByRefParameterOverloadProcessingLayer.cs b/Il2CppInterop.Generator/ByRefParameterOverloadProcessingLayer.cs new file mode 100644 index 00000000..92f15bda --- /dev/null +++ b/Il2CppInterop.Generator/ByRefParameterOverloadProcessingLayer.cs @@ -0,0 +1,189 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime.InteropTypes; + +namespace Il2CppInterop.Generator; + +public sealed class ByRefParameterOverloadProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "ByRef Parameter Overloads"; + public override string Id => "byref_parameter_overloads"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var byReference = appContext.ResolveTypeOrThrow(typeof(ByReference<>)); + var byReference_Constructor = byReference.GetMethodByName(".ctor"); + var byReference_CopyFrom = byReference.GetMethodByName(nameof(ByReference<>.CopyFrom)); + var byReference_CopyTo = byReference.GetMethodByName(nameof(ByReference<>.CopyTo)); + var byReference_Clear = byReference.GetMethodByName(nameof(ByReference<>.Clear)); + + var il2CppTypeHelper = appContext.ResolveTypeOrThrow(typeof(Il2CppType)); + var il2CppTypeHelper_SizeOf = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.SizeOf)); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + // for instead of foreach because we might be modifying the collection + for (var methodIndex = 0; methodIndex < type.Methods.Count; methodIndex++) + { + var method = type.Methods[methodIndex]; + if (method.IsInjected) + continue; + if (!method.IsPublic || method.IsSpecialName) + continue; // Don't generate overloads for non-public or special name methods + if (method.IsVirtual && !method.IsNewSlot) + continue; // Don't generate overloads for overrides/implementations + + if (!method.Parameters.Any(p => p.DefaultParameterType is ByRefTypeAnalysisContext)) + continue; + + const MethodAttributes AttributesToRemove = MethodAttributes.Virtual | MethodAttributes.Abstract | MethodAttributes.Final | MethodAttributes.NewSlot; + var newMethod = new InjectedMethodAnalysisContext(type, method.Name, appContext.SystemTypes.SystemVoidType, method.Attributes & ~AttributesToRemove, []) + { + IsInjected = true, + }; + type.Methods.Add(newMethod); + + Debug.Assert(method.MostUserFriendlyOverload == method); + method.MostUserFriendlyOverload = newMethod; + + newMethod.CopyGenericParameters(method, true); + + var visitor = TypeReplacementVisitor.CreateForMethodCopying(method, newMethod); + + newMethod.SetDefaultReturnType(visitor.Replace(method.ReturnType)); + + foreach (var parameter in method.Parameters) + { + TypeAnalysisContext parameterType; + if (parameter.DefaultParameterType is ByRefTypeAnalysisContext) + { + Debug.Assert(parameter.ParameterType is GenericInstanceTypeAnalysisContext { GenericArguments.Count: 1 }); + var underlyingType = ((GenericInstanceTypeAnalysisContext)parameter.ParameterType).GenericArguments[0]; + parameterType = visitor.Replace(underlyingType).MakeByReferenceType(); + } + else + { + parameterType = visitor.Replace(parameter.ParameterType); + } + + var newParameter = new InjectedParameterAnalysisContext(parameter.Name, parameterType, parameter.Attributes, parameter.ParameterIndex, newMethod); + newMethod.Parameters.Add(newParameter); + } + + List instructions = new(); + List variables = new(); + + LocalVariable?[] variableMap = new LocalVariable?[newMethod.Parameters.Count]; + + for (var i = 0; i < newMethod.Parameters.Count; i++) + { + var parameter = newMethod.Parameters[i]; + if (parameter.ParameterType is ByRefTypeAnalysisContext { ElementType: { } underlyingType }) + { + LocalVariable local = new(byReference.MakeGenericInstanceType([underlyingType])); + variables.Add(local); + + instructions.Add(new Instruction(CilOpCodes.Call, il2CppTypeHelper_SizeOf.MakeGenericInstanceMethod(underlyingType))); + instructions.Add(new Instruction(CilOpCodes.Conv_U)); + instructions.Add(new Instruction(CilOpCodes.Localloc)); + instructions.Add(new Instruction(CilOpCodes.Newobj, new ConcreteGenericMethodAnalysisContext(byReference_Constructor, [underlyingType], []))); + instructions.Add(new Instruction(CilOpCodes.Stloc, local)); + + if (parameter.Attributes.HasFlag(ParameterAttributes.Out)) + { + instructions.Add(new Instruction(CilOpCodes.Ldloca, local)); + instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(byReference_Clear, [underlyingType], []))); + } + else + { + instructions.Add(new Instruction(CilOpCodes.Ldloca, local)); + instructions.Add(new Instruction(CilOpCodes.Ldarg, parameter)); + instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(byReference_CopyFrom, [underlyingType], []))); + } + + variableMap[i] = local; + } + } + + if (!newMethod.IsStatic) + { + instructions.Add(new Instruction(CilOpCodes.Ldarg, This.Instance)); + } + + for (var i = 0; i < newMethod.Parameters.Count; i++) + { + var local = variableMap[i]; + if (local is not null) + { + instructions.Add(new Instruction(CilOpCodes.Ldloc, local)); + } + else + { + instructions.Add(new Instruction(CilOpCodes.Ldarg, newMethod.Parameters[i])); + } + } + + instructions.Add(new Instruction(newMethod.IsStatic || type.IsValueType ? CilOpCodes.Call : CilOpCodes.Callvirt, method.MaybeMakeConcreteGeneric(type.GenericParameters, newMethod.GenericParameters))); + + LocalVariable? resultLocal; + if (newMethod.IsVoid) + { + resultLocal = null; + } + else + { + resultLocal = new(newMethod.ReturnType); + variables.Add(resultLocal); + instructions.Add(new Instruction(CilOpCodes.Stloc, resultLocal)); + } + + for (var i = 0; i < newMethod.Parameters.Count; i++) + { + var local = variableMap[i]; + if (local is null) + { + continue; + } + + var parameter = newMethod.Parameters[i]; + if (parameter.Attributes.HasFlag(ParameterAttributes.In)) + { + continue; + } + + var underlyingType = ((ByRefTypeAnalysisContext)parameter.ParameterType).ElementType; + + instructions.Add(new Instruction(CilOpCodes.Ldloca, local)); + instructions.Add(new Instruction(CilOpCodes.Ldarg, parameter)); + instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(byReference_CopyTo, [underlyingType], []))); + } + + if (resultLocal is not null) + { + instructions.Add(new Instruction(CilOpCodes.Ldloc, resultLocal)); + } + + instructions.Add(new Instruction(CilOpCodes.Ret)); + + newMethod.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = variables, + }); + } + } + } + } +} diff --git a/Il2CppInterop.Generator/CleanRenamingProcessingLayer.cs b/Il2CppInterop.Generator/CleanRenamingProcessingLayer.cs new file mode 100644 index 00000000..cd9ec205 --- /dev/null +++ b/Il2CppInterop.Generator/CleanRenamingProcessingLayer.cs @@ -0,0 +1,278 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Text.RegularExpressions; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public partial class CleanRenamingProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Clean Name Changes"; + + public override string Id => "cleanrenamer"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + for (var i = 0; i < appContext.Assemblies.Count; i++) + { + var assembly = appContext.Assemblies[i]; + + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + RenameTypes(assembly.TopLevelTypes); + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + RenameMembers(type); + } + + progressCallback?.Invoke(i, appContext.Assemblies.Count); + } + } + + private static void RenameTypes(IEnumerable types, string? declaringTypeName = null) + { + HashSet<(string, string)> reservedFullNames = []; + foreach (var type in types) + { + if (type.IsInjected) + { + reservedFullNames.Add((type.Namespace, type.Name)); + continue; + } + + if (GenericTypeName.TryMatch(type.Name, out var typeName, out var genericCount)) + { + type.OverrideName = $"{typeName.MakeValidCSharpName()}`{genericCount}"; + } + else if (type.IsModuleType) + { + type.OverrideName = $"Module_{type.DeclaringAssembly.DefaultName.MakeValidCSharpName()}"; + } + else if (type.IsPrivateImplementationDetailsType) + { + type.OverrideName = $"PrivateImplementationDetails_{type.DeclaringAssembly.DefaultName.MakeValidCSharpName()}"; + } + else + { + type.OverrideName = type.Name.MakeValidCSharpName(); + } + } + + // Resolve any name conflicts + foreach (var type in types) + { + if (type.IsInjected) + continue; + + var @namespace = type.Namespace; + var name = type.Name; + while (name == declaringTypeName || !reservedFullNames.Add((@namespace, name))) + { + name = $"_{name}"; + } + + type.OverrideName = name; + } + + foreach (var type in types) + { + RenameTypes(type.NestedTypes, type.Name); + } + } + + private static void RenameMembers(TypeAnalysisContext type) + { + // Virtual method lookup depends on the name being consistent with the base type. + // Special names also have special meaning and should not be changed. + const MethodAttributes FlagsWhichRequireNameConsistency = + MethodAttributes.SpecialName | + MethodAttributes.Abstract | + MethodAttributes.Virtual | + MethodAttributes.RTSpecialName; + + var typeName = GetCSharpName(type); + + // Events and properties do not get renamed + + // Collect all reserved names + HashSet reservedNames = + [ + "", + "_", + typeName, + .. GetConflictingNames(type.Properties), + .. GetConflictingNames(type.Events), + ]; + if (StartsWithGetSet(typeName)) + { + reservedNames.Add(typeName.Substring(4)); + } + + HashSet<(string Name, int SignatureHash)> existingMethods = []; + + // Injected and special methods + foreach (var method in type.Methods) + { + if (method.IsInjected) + { + existingMethods.Add((method.Name, GetMethodSignatureHash(method))); + } + else if ((method.Attributes & FlagsWhichRequireNameConsistency) != 0) + { + if (method.IsStaticConstructor) + { + // Rename static constructor, but allow it to be renamed again if needed. + // Also remove special name flags, since it's no longer a special name. + method.OverrideName = "StaticConstructor"; + method.OverrideAttributes = method.Attributes & ~(MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); + } + else + { + existingMethods.Add((method.Name, GetMethodSignatureHash(method))); + } + } + } + + // Normal methods + foreach (var method in type.Methods) + { + if (method.IsInjected) + continue; + + if ((method.Attributes & FlagsWhichRequireNameConsistency) != 0) + continue; + + var methodName = method.Name.MakeValidCSharpName(); + var signatureHash = GetMethodSignatureHash(method); + + while (reservedNames.Contains(methodName) || !existingMethods.Add((methodName, signatureHash))) + { + methodName = $"_{methodName}"; + } + method.OverrideName = methodName; + } + + reservedNames.AddRange(GetConflictingNames(type.Methods)); + + // Fields + foreach (var field in type.Fields) + { + if (field.IsInjected) + continue; + + string fieldName; + if (TryMatchPropertyBackingField(field.Name, out var propertyName)) + { + fieldName = $"{propertyName}_BackingField"; + } + else if (type.Events.Any(e => e.Name == field.Name)) + { + fieldName = $"{field.Name}_BackingField"; + } + else + { + fieldName = field.Name.MakeValidCSharpName(); + } + + while (reservedNames.Contains(fieldName)) + { + fieldName = $"_{fieldName}"; + } + field.OverrideName = fieldName; + reservedNames.Add(field.Name); + } + + // Parameters + var parameterNames = new HashSet(); + foreach (var method in type.Methods) + { + if (method.IsInjected) + continue; + + if (method.Parameters.Count == 0) + continue; + + parameterNames.Clear(); + + for (var i = 0; i < method.Parameters.Count; i++) + { + var parameter = method.Parameters[i]; + var parameterName = string.IsNullOrEmpty(parameter.Name) + ? $"parameter{i}" + : parameter.Name.MakeValidCSharpName(); + + while (reservedNames.Contains(parameterName) || !parameterNames.Add(parameterName)) + { + parameterName = $"_{parameterName}"; + } + parameter.OverrideName = parameterName; + } + } + } + + private static IEnumerable GetConflictingNames(IEnumerable members) + { + foreach (var member in members) + { + yield return member.Name; + if (StartsWithGetSet(member.Name)) + { + yield return member.Name.Substring(4); + } + } + } + + private static bool StartsWithGetSet(string name) + { + return name.StartsWith("get_", StringComparison.Ordinal) + || name.StartsWith("set_", StringComparison.Ordinal); + } + + private static string GetCSharpName(TypeAnalysisContext type) + { + if (GenericTypeName.TryMatch(type.Name, out var typeName, out _)) + { + return typeName; + } + else + { + return type.Name; + } + } + + private static int GetMethodSignatureHash(MethodAnalysisContext method) + { + HashCode hash = new(); + hash.Add(method.GenericParameters.Count); + hash.Add(TypeAnalysisContextEqualityComparer.Instance.GetHashCode(method.ReturnType)); + foreach (var param in method.Parameters) + { + hash.Add(TypeAnalysisContextEqualityComparer.Instance.GetHashCode(param.ParameterType)); + } + return hash.ToHashCode(); + } + + private static bool TryMatchPropertyBackingField(string fieldName, [NotNullWhen(true)] out string? propertyName) + { + var match = PropertyBackingFieldRegex.Match(fieldName); + if (match.Success) + { + propertyName = match.Groups[1].Value; + return true; + } + else + { + propertyName = null; + return false; + } + } + + [GeneratedRegex(@"^<(\w+)>k__BackingField$")] + private static partial Regex PropertyBackingFieldRegex { get; } +} diff --git a/Il2CppInterop.Generator/ConflictRenamingProcessingLayer.cs b/Il2CppInterop.Generator/ConflictRenamingProcessingLayer.cs new file mode 100644 index 00000000..ace52f64 --- /dev/null +++ b/Il2CppInterop.Generator/ConflictRenamingProcessingLayer.cs @@ -0,0 +1,116 @@ +using System.Diagnostics; +using System.Text.RegularExpressions; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +/// +/// 3 virtual methods in Il2CppSystem.Object conflict with their System.Object counterparts. +/// +public partial class ConflictRenamingProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Conflict Renaming"; + public override string Id => "conflictrenamer"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + // ToString => ToIl2CppString + // GetHashCode => GetIl2CppHashCode + // Finalize => Il2CppFinalize + + var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + + for (var i = 0; i < appContext.Assemblies.Count; i++) + { + var assembly = appContext.Assemblies[i]; + + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + MaybeAppendUnderscore(type.Name, type); + + foreach (var field in type.Fields) + { + MaybeAppendUnderscore(field.Name, field); + } + + foreach (var property in type.Properties) + { + MaybeAppendUnderscore(property.Name, property); + } + + foreach (var @event in type.Events) + { + MaybeAppendUnderscore(@event.Name, @event); + } + + foreach (var method in type.Methods) + { + var name = method.Name; + switch (name) + { + case "ToString": + if (method.Parameters.Count == 0 && method.GenericParameters.Count == 0) + { + Debug.Assert(!method.IsInjected); + method.Name = "ToIl2CppString"; + } + break; + case "GetHashCode": + if (method.Parameters.Count == 0 && method.GenericParameters.Count == 0) + { + Debug.Assert(!method.IsInjected); + method.Name = "GetIl2CppHashCode"; + } + break; + case "Finalize": + if (method.Parameters.Count == 0 && method.GenericParameters.Count == 0) + { + Debug.Assert(!method.IsInjected); + method.Name = "Il2CppFinalize"; + method.Overrides.Clear(); // Since this is no longer the Finalize method, it shouldn't have an explicit override. + } + break; + default: + MaybeAppendUnderscore(name, method); + break; + } + } + } + + progressCallback?.Invoke(i, appContext.Assemblies.Count); + } + } + + private static void MaybeAppendUnderscore(string name, HasCustomAttributesAndName context) + { + // If the name matches any of the patterns, append an underscore to avoid conflicts. + if (ToStringRegex.IsMatch(name)) + { + context.Name = $"{name}_"; + } + else if (GetHashCodeRegex.IsMatch(name)) + { + context.Name = $"{name}_"; + } + else if (FinalizeRegex.IsMatch(name)) + { + context.Name = $"{name}_"; + } + } + + [GeneratedRegex(@"^ToIl2CppString_*$")] + private static partial Regex ToStringRegex { get; } + + [GeneratedRegex(@"^GetIl2CppHashCode_*$")] + private static partial Regex GetHashCodeRegex { get; } + + [GeneratedRegex(@"^Il2CppFinalize_*$")] + private static partial Regex FinalizeRegex { get; } +} diff --git a/Il2CppInterop.Generator/ConstantInitializationProcessingLayer.cs b/Il2CppInterop.Generator/ConstantInitializationProcessingLayer.cs new file mode 100644 index 00000000..5840c40a --- /dev/null +++ b/Il2CppInterop.Generator/ConstantInitializationProcessingLayer.cs @@ -0,0 +1,72 @@ +using System.Diagnostics; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class ConstantInitializationProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Constant Initialization Processor"; + public override string Id => "constant_initialization_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + foreach (var field in type.Fields) + { + if (field.ConstantValue is null || field.IsInjected) + continue; + + Debug.Assert(field.IsStatic); + + var instructions = type.GetOrCreateStaticConstructorInstructions(); + + object operandCast; + unchecked + { + operandCast = field.ConstantValue switch + { + bool value => value ? 1 : 0, + char value => (int)value, + byte value => (int)value, + sbyte value => (int)value, + ushort value => (int)value, + short value => (int)value, + uint value => (int)value, + ulong value => (long)value, + _ => field.ConstantValue, + }; + } + + var opCode = operandCast switch + { + long => CilOpCodes.Ldc_I8, + float => CilOpCodes.Ldc_R4, + double => CilOpCodes.Ldc_R8, + string => CilOpCodes.Ldstr, + _ => CilOpCodes.Ldc_I4 + }; + + instructions.Add(new Instruction(opCode, operandCast)); + if (opCode == CilOpCodes.Ldstr) + { + MonoIl2CppConversion.AddMonoToIl2CppStringConversion(instructions, appContext); + } + else + { + MonoIl2CppConversion.AddMonoToIl2CppConversion(instructions, field.FieldType); + } + instructions.Add(new Instruction(CilOpCodes.Stsfld, field.MaybeMakeConcreteGeneric(type.GenericParameters))); + } + } + } + } +} diff --git a/Il2CppInterop.Generator/ContextResolver.cs b/Il2CppInterop.Generator/ContextResolver.cs new file mode 100644 index 00000000..9d72f8e9 --- /dev/null +++ b/Il2CppInterop.Generator/ContextResolver.cs @@ -0,0 +1,325 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public readonly struct ContextResolver +{ + private readonly AssemblyAnalysisContext referencedFrom; + private readonly TypeAnalysisContext? referencingType; + private readonly MethodAnalysisContext? referencingMethod; + private readonly RuntimeContext runtimeContext; + + public ContextResolver(AssemblyAnalysisContext referencedFrom, RuntimeContext runtimeContext) + { + this.referencedFrom = referencedFrom; + this.runtimeContext = runtimeContext; + } + + public ContextResolver(TypeAnalysisContext referencingType, RuntimeContext runtimeContext) + { + if (referencingType is ReferencedTypeAnalysisContext) + throw new ArgumentException("Must be a simple type", nameof(referencingType)); + referencedFrom = referencingType.DeclaringAssembly; + this.referencingType = referencingType; + this.runtimeContext = runtimeContext; + } + + public ContextResolver(MethodAnalysisContext referencingMethod, RuntimeContext runtimeContext) + { + if (referencingMethod is ConcreteGenericMethodAnalysisContext) + throw new ArgumentException("Must be a simple method", nameof(referencingMethod)); + referencedFrom = referencingMethod.CustomAttributeAssembly; + referencingType = referencingMethod.DeclaringType; + this.referencingMethod = referencingMethod; + this.runtimeContext = runtimeContext; + } + + public TypeAnalysisContext? Resolve(TypeSignature? type) => type switch + { + // Ordered roughly by frequency + TypeDefOrRefSignature typeDefOrRef => Resolve(typeDefOrRef), + CorLibTypeSignature primitive => Resolve(primitive), + GenericInstanceTypeSignature genericInstance => Resolve(genericInstance), + SzArrayTypeSignature szArray => Resolve(szArray.BaseType)?.MakeSzArrayType(), + GenericParameterSignature genericParameter => genericParameter.ParameterType switch + { + GenericParameterType.Type => TryGetGenericParameter(referencingType?.GenericParameters, genericParameter.Index), + _ => TryGetGenericParameter(referencingMethod?.GenericParameters, genericParameter.Index), + }, + ByReferenceTypeSignature byRef => Resolve(byRef.BaseType)?.MakeByReferenceType(), + PointerTypeSignature pointer => Resolve(pointer.BaseType)?.MakePointerType(), + ArrayTypeSignature array => Resolve(array.BaseType)?.MakeArrayType(array.Rank), + PinnedTypeSignature pinned => Resolve(pinned.BaseType)?.MakePinnedType(), + CustomModifierTypeSignature customModifier => Resolve(customModifier), + BoxedTypeSignature boxed => Resolve(boxed.BaseType)?.MakeBoxedType(), + SentinelTypeSignature => new SentinelTypeAnalysisContext(referencedFrom), + _ => null + }; + + private static GenericParameterTypeAnalysisContext? TryGetGenericParameter(List? genericParameters, int index) + { + if (genericParameters is null || index < 0 || index >= genericParameters.Count) + return null; + return genericParameters[index]; + } + + public bool TryResolve(TypeSignature? type, [NotNullWhen(true)] out TypeAnalysisContext? result) + { + result = Resolve(type); + return result is not null; + } + + private GenericInstanceTypeAnalysisContext? Resolve(GenericInstanceTypeSignature genericInstance) + { + return TryResolve(genericInstance.GenericType, out var genericType) && TryResolve(genericInstance.TypeArguments, out var genericArguments) + ? new GenericInstanceTypeAnalysisContext(genericType, genericArguments, referencedFrom) + : null; + } + + private CustomModifierTypeAnalysisContext? Resolve(CustomModifierTypeSignature customModifier) + { + return TryResolve(customModifier.BaseType, out var baseType) && TryResolve(customModifier.ModifierType, out var modifier) + ? new CustomModifierTypeAnalysisContext(baseType, modifier, customModifier.IsRequired, referencedFrom) + : null; + } + + private TypeAnalysisContext? Resolve(TypeDefOrRefSignature typeDefOrRef) + { + return Resolve(typeDefOrRef.Type); + } + + private TypeAnalysisContext? Resolve(ITypeDefOrRef typeDefOrRef) + { + if (typeDefOrRef is TypeSpecification typeSpecification) + { + return Resolve(typeSpecification.Signature); + } + + if (typeDefOrRef.DeclaringType is not null) + { + if (!TryResolve(typeDefOrRef.DeclaringType, out var declaringType)) + return null; + + foreach (var nestedType in declaringType.NestedTypes) + { + if (nestedType.Name == typeDefOrRef.Name) + { + return nestedType; + } + } + + return null; + } + + if (typeDefOrRef is not TypeDefinition) + { + typeDefOrRef = typeDefOrRef.TryResolve(runtimeContext) ?? typeDefOrRef; + } + + var assemblyName = GetName(typeDefOrRef.Scope); + if (assemblyName == null) + return null; + + if (!referencedFrom.AppContext.AssembliesByName.TryGetValue(assemblyName, out var targetAssembly)) + return null; + + return targetAssembly.GetTypeByFullName(typeDefOrRef.FullName); + + static string? GetName(IResolutionScope? scope) => scope switch + { + ModuleDefinition module => module.Assembly!.Name, + AssemblyReference assembly => assembly.Name, + _ => throw new NotImplementedException(), + }; + } + + private TypeAnalysisContext? Resolve(CorLibTypeSignature corLibType) + { + return referencedFrom.AppContext.Mscorlib.GetTypeByFullName(corLibType.FullName); + } + + public TypeAnalysisContext? Resolve(ITypeDescriptor? type) + { + return type switch + { + TypeSignature signature => Resolve(signature), + ITypeDefOrRef typeDefOrRef => Resolve(typeDefOrRef), + _ => null, + }; + } + + public bool TryResolve(ITypeDescriptor? type, [NotNullWhen(true)] out TypeAnalysisContext? result) + { + result = Resolve(type); + return result is not null; + } + + public IEnumerable Resolve(IEnumerable types) + { + foreach (var type in types) + { + yield return Resolve(type); + } + } + + public bool TryResolve(IEnumerable types, [NotNullWhen(true)] out List? result) + { + result = []; + foreach (var type in types) + { + if (TryResolve(type, out var resolvedType)) + { + result.Add(resolvedType); + } + else + { + result = null; + return false; + } + } + return true; + } + + public FieldAnalysisContext? Resolve(IFieldDescriptor fieldDescriptor) + { + var declaringType = Resolve(fieldDescriptor.DeclaringType); + if (declaringType is null) + return null; + + if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType) + { + var baseField = genericInstanceType.GenericType.TryGetFieldByName(fieldDescriptor.Name); + if (baseField is null) + return null; + + return new ConcreteGenericFieldAnalysisContext(baseField, genericInstanceType); + } + else + { + return declaringType.TryGetFieldByName(fieldDescriptor.Name); + } + } + + public bool TryResolve(IFieldDescriptor fieldDescriptor, [NotNullWhen(true)] out FieldAnalysisContext? result) + { + result = Resolve(fieldDescriptor); + return result is not null; + } + + public object? Resolve(IMethodDescriptor methodDescriptor) + { + if (methodDescriptor is MethodSpecification specification) + { + return Resolve(specification); + } + + var methodDefOrRef = (IMethodDefOrRef)methodDescriptor; + + if (!TryResolve(methodDefOrRef.DeclaringType, out var declaringType)) + return null; + + var nonGenericDeclaringType = (declaringType as GenericInstanceTypeAnalysisContext)?.GenericType ?? declaringType; + + if (nonGenericDeclaringType is ArrayTypeAnalysisContext arrayDeclaringType) + { + Debug.Assert(nonGenericDeclaringType == declaringType, "Array types should not be generic instances"); + return methodDefOrRef.Name?.Value switch + { + "Get" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Get), + "Set" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Set), + "Address" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Address), + ".ctor" => new MultiDimensionalArrayMethod(arrayDeclaringType, MultiDimensionalArrayMethodType.Constructor), + _ => null, + }; + } + + Debug.Assert(nonGenericDeclaringType is not ReferencedTypeAnalysisContext); + + var targetMethod = new ContextResolver(nonGenericDeclaringType, runtimeContext).ResolveInType(methodDefOrRef); + if (targetMethod is null) + return null; + + if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType) + { + return new ConcreteGenericMethodAnalysisContext(targetMethod, genericInstanceType.GenericArguments, []); + } + else + { + return targetMethod; + } + } + + public bool TryResolve(IMethodDescriptor methodDescriptor, [NotNullWhen(true)] out object? result) + { + result = Resolve(methodDescriptor); + return result is not null; + } + + public MethodAnalysisContext? Resolve(MethodDefinition methodDefinition) + { + // The declaring type can be resolved with nothing, but resolution for the method itself requires a context. + return TryResolve(methodDefinition.DeclaringType, out var declaringType) ? new ContextResolver(declaringType, runtimeContext).ResolveInType(methodDefinition) : null; + } + + public MethodAnalysisContext? ResolveInType(IMethodDefOrRef methodDefOrRef) + { + if (referencingType is null) + throw new InvalidOperationException("Cannot resolve method in type without a referencing type"); + + if (methodDefOrRef.Signature is null || methodDefOrRef.Signature.SentinelParameterTypes.Count > 0) + return null; + + foreach (var methodContext in referencingType.Methods) + { + if (methodContext.Parameters.Count != methodDefOrRef.Signature.ParameterTypes.Count) + continue; + + if (methodContext.GenericParameters.Count != methodDefOrRef.Signature.GenericParameterCount) + continue; + + if (methodContext.IsStatic == methodDefOrRef.Signature.HasThis) + continue; + + if (methodContext.IsVoid == methodDefOrRef.Signature.ReturnsValue) + continue; + + if (methodContext.Name != (string?)methodDefOrRef.Name) + continue; + + // We need to use a resolver for the method context to resolve potential method generic parameters correctly. + var methodResolver = new ContextResolver(methodContext, runtimeContext); + + if (!methodResolver.TryResolve(methodDefOrRef.Signature.ReturnType, out var returnType) || + !TypeAnalysisContextEqualityComparer.Instance.Equals(methodContext.ReturnType, returnType)) + continue; + + if (!methodResolver.TryResolve(methodDefOrRef.Signature.ParameterTypes, out var parameterTypes) || + !methodContext.Parameters.Select(p => p.ParameterType).SequenceEqual(parameterTypes, TypeAnalysisContextEqualityComparer.Instance)) + continue; + + return methodContext; + } + + return null; + } + + public ConcreteGenericMethodAnalysisContext? Resolve(MethodSpecification specification) + { + if (specification.Method is null || specification.Signature is null) + return null; + + var baseMethod = (MethodAnalysisContext?)Resolve(specification.Method); + if (baseMethod is null or { DeclaringType: null }) + return null; + + if (!TryResolve(specification.Signature.TypeArguments, out var methodTypeArguments)) + return null; + + return baseMethod.MakeGenericInstanceMethod(methodTypeArguments); + } +} diff --git a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs b/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs deleted file mode 100644 index a5e8f2fd..00000000 --- a/Il2CppInterop.Generator/Contexts/AssemblyRewriteContext.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System.Diagnostics; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Contexts; - -[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] -public class AssemblyRewriteContext -{ - public readonly RewriteGlobalContext GlobalContext; - - public readonly RuntimeAssemblyReferences Imports; - private readonly Dictionary myNameTypeMap = new(); - private readonly Dictionary myNewTypeMap = new(); - - private readonly Dictionary myOldTypeMap = new(); - public readonly AssemblyDefinition NewAssembly; - - public readonly AssemblyDefinition OriginalAssembly; - - public AssemblyRewriteContext(RewriteGlobalContext globalContext, AssemblyDefinition originalAssembly, - AssemblyDefinition newAssembly) - { - OriginalAssembly = originalAssembly; - NewAssembly = newAssembly; - GlobalContext = globalContext; - - Imports = globalContext.ImportsMap.GetOrCreate(newAssembly.ManifestModule!, - mod => new RuntimeAssemblyReferences(mod, globalContext)); - } - - public IEnumerable Types => myOldTypeMap.Values; - - public TypeRewriteContext GetContextForOriginalType(TypeDefinition type) - { - return myOldTypeMap[type]; - } - - public TypeRewriteContext? TryGetContextForOriginalType(TypeDefinition type) - { - return myOldTypeMap.TryGetValue(type, out var result) ? result : null; - } - - public TypeRewriteContext GetContextForNewType(TypeDefinition type) - { - return myNewTypeMap[type]; - } - - public void RegisterTypeRewrite(TypeRewriteContext context) - { - if (context.OriginalType != null) - myOldTypeMap[context.OriginalType] = context; - myNewTypeMap[context.NewType] = context; - myNameTypeMap[(context.OriginalType ?? context.NewType).FullName] = context; - } - - public IMethodDefOrRef RewriteMethodRef(IMethodDefOrRef methodRef) - { - var newType = GlobalContext.GetNewTypeForOriginal(methodRef.DeclaringType!.Resolve()!); - var newMethod = newType.GetMethodByOldMethod(methodRef.Resolve()!).NewMethod; - return NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(newMethod); - } - - public ITypeDefOrRef RewriteTypeRef(ITypeDescriptor typeRef) - { - return RewriteTypeRef(typeRef?.ToTypeSignature()).ToTypeDefOrRef(); - } - - public TypeSignature RewriteTypeRef(TypeSignature? typeRef) - { - if (typeRef == null) - return Imports.Il2CppObjectBase; - - var sourceModule = NewAssembly.ManifestModule!; - - if (typeRef is ArrayBaseTypeSignature arrayType) - { - if (arrayType.Rank != 1) - return Imports.Il2CppObjectBase; - - var elementType = arrayType.BaseType; - if (elementType.FullName == "System.String") - return Imports.Il2CppStringArray; - - var convertedElementType = RewriteTypeRef(elementType); - if (elementType is GenericParameterSignature) - return new GenericInstanceTypeSignature(Imports.Il2CppArrayBase.ToTypeDefOrRef(), false, convertedElementType); - - return new GenericInstanceTypeSignature(convertedElementType.IsValueType() - ? Imports.Il2CppStructArray.ToTypeDefOrRef() - : Imports.Il2CppReferenceArray.ToTypeDefOrRef(), false, convertedElementType); - } - - if (typeRef is GenericParameterSignature genericParameter) - { - return new GenericParameterSignature(sourceModule, genericParameter.ParameterType, genericParameter.Index); - } - - if (typeRef is ByReferenceTypeSignature byRef) - return new ByReferenceTypeSignature(RewriteTypeRef(byRef.BaseType)); - - if (typeRef is PointerTypeSignature pointerType) - return new PointerTypeSignature(RewriteTypeRef(pointerType.BaseType)); - - if (typeRef is GenericInstanceTypeSignature genericInstance) - { - var genericType = RewriteTypeRef(genericInstance.GenericType.ToTypeSignature()).ToTypeDefOrRef(); - var newRef = new GenericInstanceTypeSignature(genericType, genericType.IsValueType()); - foreach (var originalParameter in genericInstance.TypeArguments) - newRef.TypeArguments.Add(RewriteTypeRef(originalParameter)); - - return newRef; - } - - if (typeRef.IsPrimitive() || typeRef.FullName == "System.TypedReference") - return sourceModule.ImportCorlibReference(typeRef.FullName); - - if (typeRef.FullName == "System.Void") - return Imports.Module.Void(); - - if (typeRef.FullName == "System.String") - return Imports.Module.String(); - - if (typeRef.FullName == "System.Object") - return sourceModule.DefaultImporter.ImportType(GlobalContext.GetAssemblyByName("mscorlib") - .GetTypeByName("System.Object").NewType).ToTypeSignature(); - - if (typeRef.FullName == "System.Attribute") - return sourceModule.DefaultImporter.ImportType(GlobalContext.GetAssemblyByName("mscorlib") - .GetTypeByName("System.Attribute").NewType).ToTypeSignature(); - - var originalTypeDef = typeRef.Resolve()!; - var targetAssembly = GlobalContext.GetNewAssemblyForOriginal(originalTypeDef.DeclaringModule!.Assembly!); - var target = targetAssembly.GetContextForOriginalType(originalTypeDef).NewType; - - return sourceModule.DefaultImporter.ImportType(target).ToTypeSignature(); - } - - public TypeRewriteContext GetTypeByName(string name) - { - return myNameTypeMap[name]; - } - - public TypeRewriteContext? TryGetTypeByName(string name) - { - return myNameTypeMap.TryGetValue(name, out var result) ? result : null; - } - - private string GetDebuggerDisplay() - { - return NewAssembly.FullName; - } -} diff --git a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs b/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs deleted file mode 100644 index a2420e68..00000000 --- a/Il2CppInterop.Generator/Contexts/FieldRewriteContext.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Diagnostics; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Contexts; - -[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] -public class FieldRewriteContext -{ - private static readonly string[] MethodAccessTypeLabels = - {"CompilerControlled", "Private", "FamAndAssem", "Internal", "Protected", "FamOrAssem", "Public"}; - - public readonly TypeRewriteContext DeclaringType; - public readonly FieldDefinition OriginalField; - - public readonly MemberReference PointerField; - public readonly string UnmangledName; - - public FieldRewriteContext(TypeRewriteContext declaringType, FieldDefinition originalField, - Dictionary? renamedFieldCounts = null) - { - DeclaringType = declaringType; - OriginalField = originalField; - - UnmangledName = UnmangleFieldName(originalField, declaringType.AssemblyContext.GlobalContext.Options, - renamedFieldCounts); - var pointerField = new FieldDefinition("NativeFieldInfoPtr_" + UnmangledName, - FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly, - declaringType.AssemblyContext.Imports.Module.IntPtr()); - - declaringType.NewType.Fields.Add(pointerField); - - Debug.Assert(pointerField.Signature is not null); - PointerField = new MemberReference(DeclaringType.SelfSubstitutedRef, pointerField.Name, new FieldSignature(pointerField.Signature!.FieldType)); - } - - private string UnmangleFieldNameBase(FieldDefinition field, GeneratorOptions options) - { - if (options.PassthroughNames) - return field.Name!; - - if (!field.Name.IsObfuscated(options)) - { - return field.Name.MakeValidInSource(); - } - - Debug.Assert(field.Signature is not null); - var accessModString = MethodAccessTypeLabels[(int)(field.Attributes & FieldAttributes.FieldAccessMask)]; - var staticString = field.IsStatic ? "_Static" : ""; - return "field_" + accessModString + staticString + "_" + - DeclaringType.AssemblyContext.RewriteTypeRef(field.Signature!.FieldType).GetUnmangledName(field.DeclaringType); - } - - private string UnmangleFieldName(FieldDefinition field, GeneratorOptions options, - Dictionary? renamedFieldCounts) - { - if (options.PassthroughNames) - return field.Name!; - - if (!field.Name.IsObfuscated(options)) - { - return field.Name.MakeValidInSource(); - } - - if (renamedFieldCounts == null) throw new ArgumentNullException(nameof(renamedFieldCounts)); - - var unmangleFieldNameBase = UnmangleFieldNameBase(field, options); - - renamedFieldCounts.TryGetValue(unmangleFieldNameBase, out var count); - renamedFieldCounts[unmangleFieldNameBase] = count + 1; - - unmangleFieldNameBase += "_" + count; - - if (DeclaringType.AssemblyContext.GlobalContext.Options.RenameMap.TryGetValue( - DeclaringType.NewType.GetNamespacePrefix() + "." + DeclaringType.NewType.Name + "::" + - unmangleFieldNameBase, out var newName)) - unmangleFieldNameBase = newName; - - return unmangleFieldNameBase; - } - - private string GetDebuggerDisplay() - { - return DeclaringType.NewType.FullName + "::" + UnmangledName; - } -} diff --git a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs b/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs deleted file mode 100644 index efdcd85c..00000000 --- a/Il2CppInterop.Generator/Contexts/MethodRewriteContext.cs +++ /dev/null @@ -1,316 +0,0 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text; -using AsmResolver; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Passes; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Contexts; - -[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] -public class MethodRewriteContext -{ - private static readonly string[] MethodAccessTypeLabels = - {"CompilerControlled", "Private", "FamAndAssem", "Internal", "Protected", "FamOrAssem", "Public"}; - - private static readonly (MethodSemanticsAttributes, string)[] SemanticsToCheck = - { - (MethodSemanticsAttributes.Setter, "_set"), - (MethodSemanticsAttributes.Getter, "_get"), - (MethodSemanticsAttributes.Other, "_oth"), - (MethodSemanticsAttributes.AddOn, "_add"), - (MethodSemanticsAttributes.RemoveOn, "_rem"), - (MethodSemanticsAttributes.Fire, "_fire") - }; - - public readonly TypeRewriteContext DeclaringType; - - public readonly long FileOffset; - public readonly MethodDefinition NewMethod; - public readonly MethodDefinition OriginalMethod; - - public readonly bool OriginalNameObfuscated; - public readonly long Rva; - - public readonly List XrefScanResults = new(); - - public long MetadataInitFlagRva; - public long MetadataInitTokenRva; - - public MethodRewriteContext(TypeRewriteContext declaringType, MethodDefinition originalMethod) - { - DeclaringType = declaringType; - OriginalMethod = originalMethod; - - var passthroughNames = declaringType.AssemblyContext.GlobalContext.Options.PassthroughNames; - - OriginalNameObfuscated = !passthroughNames && - (OriginalMethod.Name?.IsObfuscated(declaringType.AssemblyContext.GlobalContext - .Options) ?? false); - - var newAttributes = AdjustAttributes(originalMethod.Attributes, originalMethod.Name == "Finalize"); - var newSignature = (newAttributes & MethodAttributes.Static) != 0 - ? MethodSignature.CreateStatic(declaringType.AssemblyContext.Imports.Module.Void(), originalMethod.GenericParameters.Count) - : MethodSignature.CreateInstance(declaringType.AssemblyContext.Imports.Module.Void(), originalMethod.GenericParameters.Count); - var newMethod = new MethodDefinition("", newAttributes, newSignature); - newMethod.CilMethodBody = new(); - NewMethod = newMethod; - - HasExtensionAttribute = - originalMethod.CustomAttributes.Any(x => x.AttributeType()?.FullName == typeof(ExtensionAttribute).FullName); - - if (HasExtensionAttribute) - newMethod.CustomAttributes.Add( - new CustomAttribute(declaringType.AssemblyContext.Imports.Module.ExtensionAttributeCtor())); - - if (originalMethod.HasGenericParameters()) - { - var genericParams = originalMethod.GenericParameters; - - foreach (var oldParameter in genericParams) - { - newMethod.GenericParameters.Add(new GenericParameter( - oldParameter.Name.MakeValidInSource(), - oldParameter.Attributes.StripValueTypeConstraint())); - } - } - - if (!Pass15GenerateMemberContexts.HasObfuscatedMethods && !passthroughNames && - originalMethod.Name.IsObfuscated(declaringType.AssemblyContext.GlobalContext.Options)) - Pass15GenerateMemberContexts.HasObfuscatedMethods = true; - - FileOffset = originalMethod.ExtractOffset(); - // Workaround for garbage file offsets passed by Cpp2IL - if (FileOffset < 0) FileOffset = 0; - Rva = originalMethod.ExtractRva(); - if (FileOffset != 0) - declaringType.AssemblyContext.GlobalContext.MethodStartAddresses.Add(FileOffset); - } - - public Utf8String? UnmangledName { get; private set; } - public string? UnmangledNameWithSignature { get; private set; } - - public TypeDefinition? GenericInstantiationsStore { get; private set; } - public ITypeDefOrRef? GenericInstantiationsStoreSelfSubstRef { get; private set; } - public ITypeDefOrRef? GenericInstantiationsStoreSelfSubstMethodRef { get; private set; } - public MemberReference NonGenericMethodInfoPointerField { get; private set; } = null!; // Initialized in CtorPhase2 - - public bool HasExtensionAttribute { get; } - - public void CtorPhase2() - { - UnmangledName = UnmangleMethodName(); - UnmangledNameWithSignature = UnmangleMethodNameWithSignature(); - - NewMethod.Name = UnmangledName; - NewMethod.Signature!.ReturnType = DeclaringType.AssemblyContext.RewriteTypeRef(OriginalMethod.Signature?.ReturnType); - - var nonGenericMethodInfoPointerField = new FieldDefinition( - "NativeMethodInfoPtr_" + UnmangledNameWithSignature, - FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly, - DeclaringType.AssemblyContext.Imports.Module.IntPtr()); - DeclaringType.NewType.Fields.Add(nonGenericMethodInfoPointerField); - - NonGenericMethodInfoPointerField = new MemberReference(DeclaringType.SelfSubstitutedRef, nonGenericMethodInfoPointerField.Name, - new FieldSignature(nonGenericMethodInfoPointerField.Signature!.FieldType)); - - if (OriginalMethod.HasGenericParameters()) - { - var genericParams = OriginalMethod.GenericParameters; - var genericMethodInfoStoreType = new TypeDefinition("", - "MethodInfoStoreGeneric_" + UnmangledNameWithSignature + "`" + genericParams.Count, - TypeAttributes.NestedPrivate | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit, - DeclaringType.AssemblyContext.Imports.Module.Object().ToTypeDefOrRef()); - DeclaringType.NewType.NestedTypes.Add(genericMethodInfoStoreType); - GenericInstantiationsStore = genericMethodInfoStoreType; - - var selfSubstRef = new GenericInstanceTypeSignature(genericMethodInfoStoreType, false); - var selfSubstMethodRef = new GenericInstanceTypeSignature(genericMethodInfoStoreType, false); - - for (var index = 0; index < genericParams.Count; index++) - { - var oldParameter = genericParams[index]; - var genericParameter = new GenericParameter(oldParameter.Name.MakeValidInSource()); - genericMethodInfoStoreType.GenericParameters.Add(genericParameter); - selfSubstRef.TypeArguments.Add(genericParameter.ToTypeSignature()); - var newParameter = NewMethod.GenericParameters[index]; - selfSubstMethodRef.TypeArguments.Add(newParameter.ToTypeSignature()); - - foreach (var oldConstraint in oldParameter.Constraints) - { - if (oldConstraint.IsSystemValueType() || oldConstraint.IsInterface()) - continue; - - if (oldConstraint.IsSystemEnum()) - { - newParameter.Constraints.Add(new GenericParameterConstraint( - DeclaringType.AssemblyContext.Imports.Module.Enum().ToTypeDefOrRef())); - continue; - } - - newParameter.Constraints.Add(new GenericParameterConstraint( - DeclaringType.AssemblyContext.RewriteTypeRef(oldConstraint.Constraint?.ToTypeSignature()).ToTypeDefOrRef())); - } - } - - var pointerField = new FieldDefinition("Pointer", FieldAttributes.Assembly | FieldAttributes.Static, - DeclaringType.AssemblyContext.Imports.Module.IntPtr()); - genericMethodInfoStoreType.Fields.Add(pointerField); - - GenericInstantiationsStoreSelfSubstRef = DeclaringType.NewType.DeclaringModule!.DefaultImporter.ImportType(selfSubstRef.ToTypeDefOrRef()); - GenericInstantiationsStoreSelfSubstMethodRef = - DeclaringType.NewType.DeclaringModule.DefaultImporter.ImportType(selfSubstMethodRef.ToTypeDefOrRef()); - } - - DeclaringType.NewType.Methods.Add(NewMethod); - } - - private MethodAttributes AdjustAttributes(MethodAttributes original, bool stripVirtual) - { - original &= ~MethodAttributes.MemberAccessMask; // todo: handle Object overload correctly - original &= ~MethodAttributes.PInvokeImpl; - original &= ~MethodAttributes.Abstract; - if (stripVirtual) original &= ~MethodAttributes.Virtual; - original &= ~MethodAttributes.Final; - if (stripVirtual) original &= ~MethodAttributes.NewSlot; - original &= ~MethodAttributes.ReuseSlot; - original &= ~MethodAttributes.CheckAccessOnOverride; - original |= MethodAttributes.Public; - return original; - } - - private string UnmangleMethodName() - { - var method = OriginalMethod; - - if (method.Name == "GetType" && method.Parameters.Count == 0) - return "GetIl2CppType"; - - if (DeclaringType.AssemblyContext.GlobalContext.Options.PassthroughNames) - return method.Name!; - - if (method.Name == ".ctor") - return ".ctor"; - - if (method.Name.IsObfuscated(DeclaringType.AssemblyContext.GlobalContext.Options)) - return UnmangleMethodNameWithSignature(); - - return method.Name.MakeValidInSource(); - } - - private string ProduceMethodSignatureBase() - { - var method = OriginalMethod; - - string name; - if (method.Name.IsObfuscated(DeclaringType.AssemblyContext.GlobalContext.Options)) - name = "Method"; - else - name = method.Name.MakeValidInSource(); - - if (method.Name == "GetType" && method.Parameters.Count == 0) - name = "GetIl2CppType"; - - var builder = new StringBuilder(); - builder.Append(name); - builder.Append('_'); - builder.Append(MethodAccessTypeLabels[(int)(method.Attributes & MethodAttributes.MemberAccessMask)]); - if (method.IsAbstract) builder.Append("_Abstract"); - if (method.IsVirtual) builder.Append("_Virtual"); - if (method.IsStatic) builder.Append("_Static"); - if (method.IsFinal) builder.Append("_Final"); - if (method.IsNewSlot) builder.Append("_New"); - if (method.Semantics is not null) - foreach (var (semantic, str) in SemanticsToCheck) - if ((semantic & method.Semantics.Attributes) != 0) - builder.Append(str); - - builder.Append('_'); - builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(method.Signature?.ReturnType).GetUnmangledName(method.DeclaringType, method)); - - foreach (var param in method.Parameters) - { - builder.Append('_'); - builder.Append(DeclaringType.AssemblyContext.RewriteTypeRef(param.ParameterType).GetUnmangledName(method.DeclaringType, method)); - } - - var address = Rva; - if (address != 0 && Pass15GenerateMemberContexts.HasObfuscatedMethods && - !Pass16ScanMethodRefs.NonDeadMethods.Contains(address)) builder.Append("_PDM"); - - return builder.ToString(); - } - - - private string UnmangleMethodNameWithSignature() - { - var unmangleMethodNameWithSignature = ProduceMethodSignatureBase() + "_" + DeclaringType.Methods - .Where(ParameterSignatureMatchesThis).TakeWhile(it => it != this).Count(); - - if (DeclaringType.AssemblyContext.GlobalContext.Options.RenameMap.TryGetValue( - DeclaringType.NewType.GetNamespacePrefix() + "." + DeclaringType.NewType.Name + "::" + unmangleMethodNameWithSignature, out var newNameByType)) - { - unmangleMethodNameWithSignature = newNameByType; - } - else if (DeclaringType.AssemblyContext.GlobalContext.Options.RenameMap.TryGetValue( - DeclaringType.NewType.GetNamespacePrefix() + "::" + unmangleMethodNameWithSignature, out var newName)) - { - unmangleMethodNameWithSignature = newName; - } - - return unmangleMethodNameWithSignature; - } - - private bool ParameterSignatureMatchesThis(MethodRewriteContext otherRewriteContext) - { - var aM = otherRewriteContext.OriginalMethod; - var bM = OriginalMethod; - - if (!otherRewriteContext.OriginalNameObfuscated) - return false; - - var comparisonMask = MethodAttributes.MemberAccessMask | MethodAttributes.Static | MethodAttributes.Final | - MethodAttributes.Abstract | MethodAttributes.Virtual | MethodAttributes.NewSlot; - if ((aM.Attributes & comparisonMask) != - (bM.Attributes & comparisonMask)) - return false; - - if (aM.Semantics?.Attributes != bM.Semantics?.Attributes) - return false; - - if (aM.Signature?.ReturnType.FullName != bM.Signature?.ReturnType.FullName) - return false; - - var a = aM.Parameters; - var b = bM.Parameters; - - if (a.Count != b.Count) - return false; - - for (var i = 0; i < a.Count; i++) - if (a[i].ParameterType.FullName != b[i].ParameterType.FullName) - return false; - - if (Pass15GenerateMemberContexts.HasObfuscatedMethods) - { - var addressA = otherRewriteContext.Rva; - var addressB = Rva; - if (addressA != 0 && addressB != 0) - if (Pass16ScanMethodRefs.NonDeadMethods.Contains(addressA) != - Pass16ScanMethodRefs.NonDeadMethods.Contains(addressB)) - return false; - } - - return true; - } - - private string GetDebuggerDisplay() - { - return NewMethod.FullName; - } -} diff --git a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs b/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs deleted file mode 100644 index 76c1ea2b..00000000 --- a/Il2CppInterop.Generator/Contexts/RewriteGlobalContext.cs +++ /dev/null @@ -1,243 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.MetadataAccess; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Contexts; - -public class RewriteGlobalContext : IDisposable -{ - internal readonly List MethodStartAddresses = new(); - - private readonly Dictionary myAssemblies = new(); - private readonly Dictionary myAssembliesByOld = new(); - private readonly Dictionary myAssembliesByNew = new(); - internal readonly Dictionary PreviousRenamedTypes = new(); - internal readonly Dictionary RenamedTypes = new(); - - internal readonly Dictionary<(object?, string, int), List> RenameGroups = new(); - - internal readonly Dictionary ImportsMap = new(); - - public RewriteGlobalContext(GeneratorOptions options, IIl2CppMetadataAccess gameAssemblies, - IMetadataAccess unityAssemblies) - { - Options = options; - GameAssemblies = gameAssemblies; - UnityAssemblies = unityAssemblies; - - Il2CppAssemblyResolver assemblyResolver = new(); - - foreach (var sourceAssembly in gameAssemblies.Assemblies) - { - var assemblyName = sourceAssembly.Name!; - if (assemblyName == "Il2CppDummyDll") - { - continue; - } - - var newAssembly = new AssemblyDefinition(sourceAssembly.Name.UnSystemify(options), sourceAssembly.Version); - var newModule = new ModuleDefinition(sourceAssembly.ManifestModule?.Name.UnSystemify(options), CorlibReferences.TargetCorlib); - newAssembly.Modules.Add(newModule); - - newModule.MetadataResolver = new DefaultMetadataResolver(assemblyResolver); - assemblyResolver.AddToCache(newAssembly); - - var assemblyRewriteContext = new AssemblyRewriteContext(this, sourceAssembly, newAssembly); - AddAssemblyContext(assemblyName, assemblyRewriteContext); - } - } - - public GeneratorOptions Options { get; } - public IIl2CppMetadataAccess GameAssemblies { get; } - public IMetadataAccess UnityAssemblies { get; } - - public IEnumerable Assemblies => myAssemblies.Values; - public AssemblyRewriteContext CorLib => myAssemblies["mscorlib"]; - - internal bool HasGcWbarrierFieldWrite { get; set; } - - public void Dispose() - { - UnityAssemblies.Dispose(); - } - - internal void AddAssemblyContext(string assemblyName, AssemblyRewriteContext context) - { - myAssemblies[assemblyName] = context; - if (context.OriginalAssembly != null) - myAssembliesByOld[context.OriginalAssembly] = context; - myAssembliesByNew[context.NewAssembly] = context; - } - - public AssemblyRewriteContext GetNewAssemblyForOriginal(AssemblyDefinition oldAssembly) - { - return myAssembliesByOld[oldAssembly]; - } - - public TypeRewriteContext GetNewTypeForOriginal(TypeDefinition originalType) - { - return GetNewAssemblyForOriginal(originalType.DeclaringModule!.Assembly!) - .GetContextForOriginalType(originalType); - } - - public TypeRewriteContext? TryGetNewTypeForOriginal(TypeDefinition originalType) - { - if (!myAssembliesByOld.TryGetValue(originalType.DeclaringModule!.Assembly!, out var assembly)) - return null; - return assembly.TryGetContextForOriginalType(originalType); - } - - public TypeRewriteContext.TypeSpecifics JudgeSpecificsByOriginalType(TypeSignature typeRef) - { - if (typeRef.IsPrimitive() || typeRef is PointerTypeSignature || typeRef.FullName == "System.TypedReference") - return TypeRewriteContext.TypeSpecifics.BlittableStruct; - if (typeRef - is CorLibTypeSignature { ElementType: ElementType.String or ElementType.Object } - or ArrayBaseTypeSignature - or ByReferenceTypeSignature - or GenericParameterSignature - or GenericInstanceTypeSignature) - return TypeRewriteContext.TypeSpecifics.ReferenceType; - - var fieldTypeContext = GetNewTypeForOriginal(typeRef.Resolve() ?? throw new($"Could not resolve {typeRef.FullName}")); - return fieldTypeContext.ComputedTypeSpecifics; - } - - public AssemblyRewriteContext GetAssemblyByName(string name) - { - return myAssemblies[name]; - } - - public AssemblyRewriteContext? TryGetAssemblyByName(string? name) - { - if (name is null) - return null; - - if (myAssemblies.TryGetValue(name, out var result)) - return result; - - if (name == "netstandard") - return myAssemblies.TryGetValue("mscorlib", out var result2) ? result2 : null; - - return null; - } - - public AssemblyRewriteContext GetContextForNewAssembly(AssemblyDefinition assembly) - { - return myAssembliesByNew[assembly]; - } - - public TypeRewriteContext GetContextForNewType(TypeDefinition type) - { - return GetContextForNewAssembly(type.DeclaringModule!.Assembly!).GetContextForNewType(type); - } - - public MethodDefinition? CreateParamsMethod(MethodDefinition originalMethod, MethodDefinition newMethod, - RuntimeAssemblyReferences imports, Func resolve) - { - if (newMethod.Name == "Invoke") - return null; - - var paramsParameters = originalMethod.Parameters.Where(parameter => - parameter.IsParamsArray() && resolve(((ArrayBaseTypeSignature)parameter.ParameterType).BaseType) is not null and not GenericParameterSignature - ).ToArray(); - - if (paramsParameters.Any()) - { - var paramsMethod = new MethodDefinition(newMethod.Name, newMethod.Attributes, MethodSignatureCreator.CreateMethodSignature(newMethod.Attributes, newMethod.Signature!.ReturnType, newMethod.Signature.GenericParameterCount)); - foreach (var genericParameter in originalMethod.GenericParameters) - { - var newGenericParameter = new GenericParameter(genericParameter.Name.MakeValidInSource(), genericParameter.Attributes); - - foreach (var constraint in genericParameter.Constraints) - { - var newConstraintType = constraint.Constraint != null ? resolve(constraint.Constraint.ToTypeSignature())?.ToTypeDefOrRef() : null; - var newConstraint = new GenericParameterConstraint(newConstraintType); - - // We don't need to copy custom attributes on constraints for generic parameters because Il2Cpp doesn't support them. - - newGenericParameter.Constraints.Add(newConstraint); - } - - // Similarly, custom attributes on generic parameters are also stripped by Il2Cpp, so we don't need to copy them. - - paramsMethod.GenericParameters.Add(newGenericParameter); - } - - foreach (var originalParameter in originalMethod.Parameters) - { - var isParams = paramsParameters.Contains(originalParameter); - - TypeSignature? convertedType; - if (isParams && originalParameter.ParameterType is ArrayBaseTypeSignature arrayType) - { - var resolvedElementType = resolve(arrayType.GetElementType()); - convertedType = arrayType is SzArrayTypeSignature - ? resolvedElementType?.MakeSzArrayType() - : resolvedElementType?.MakeArrayType(arrayType.Rank); - } - else - { - convertedType = resolve(originalParameter.ParameterType); - } - - if (convertedType == null) - { - throw new($"Could not resolve parameter type {originalParameter.ParameterType.FullName}"); - } - - var parameter = paramsMethod.AddParameter(convertedType, originalParameter.Name, originalParameter.Definition?.Attributes ?? default); - - if (isParams) - parameter.Definition!.CustomAttributes.Add(new CustomAttribute(newMethod.DeclaringModule!.ParamArrayAttributeCtor())); - } - - paramsMethod.CilMethodBody = new(); - var body = paramsMethod.CilMethodBody.Instructions; - - if (!newMethod.IsStatic) - { - body.Add(OpCodes.Ldarg_0); - } - - for (var i = 0; i < newMethod.Parameters.Count; i++) - { - body.Add(OpCodes.Ldarg, newMethod.Parameters[i]); - - var parameter = originalMethod.Parameters[i]; - if (paramsParameters.Contains(parameter)) - { - var parameterType = (ArrayBaseTypeSignature)parameter.ParameterType; - - IMethodDescriptor constructorReference; - - var elementType = parameterType.GetElementType(); - if (elementType.FullName == "System.String") - { - constructorReference = imports.Il2CppStringArrayctor.Value; - } - else - { - var convertedElementType = resolve(elementType)!; - - constructorReference = imports.Module.DefaultImporter.ImportMethod(convertedElementType.IsValueType() - ? imports.Il2CppStructArrayctor.Get(convertedElementType) - : imports.Il2CppRefrenceArrayctor.Get(convertedElementType)); - } - - body.Add(OpCodes.Newobj, constructorReference); - } - } - - body.Add(OpCodes.Call, newMethod); - body.Add(OpCodes.Ret); - - return paramsMethod; - } - - return null; - } -} diff --git a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs b/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs deleted file mode 100644 index 23b9e8b9..00000000 --- a/Il2CppInterop.Generator/Contexts/TypeRewriteContext.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Diagnostics; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Contexts; - -[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] -public class TypeRewriteContext -{ - public enum TypeSpecifics - { - NotComputed, - Computing, - ReferenceType, - BlittableStruct, - NonBlittableStruct - } - - public readonly AssemblyRewriteContext AssemblyContext; - - private readonly Dictionary myFieldContexts = new(); - private readonly Dictionary myMethodContexts = new(); - private readonly Dictionary myMethodContextsByName = new(); - public readonly TypeDefinition NewType; - - public readonly bool OriginalNameWasObfuscated; -#nullable disable - // OriginalType is null for unstripped types, but we don't want to warn anywhere, - // including in the constructor, so we disable all null tracking for this field. - public readonly TypeDefinition OriginalType; -#nullable enable - - public TypeSpecifics ComputedTypeSpecifics; - - public TypeRewriteContext(AssemblyRewriteContext assemblyContext, TypeDefinition? originalType, - TypeDefinition newType) - { - AssemblyContext = assemblyContext ?? throw new ArgumentNullException(nameof(assemblyContext)); - OriginalType = originalType; - NewType = newType ?? throw new ArgumentNullException(nameof(newType)); - - if (OriginalType == null) return; - - OriginalNameWasObfuscated = OriginalType.Name != NewType.Name; - if (OriginalNameWasObfuscated) - NewType.CustomAttributes.Add(new CustomAttribute( - (ICustomAttributeType)assemblyContext.Imports.ObfuscatedNameAttributector.Value, - new CustomAttributeSignature(new CustomAttributeArgument(assemblyContext.Imports.Module.String(), OriginalType.FullName)))); - - if (!OriginalType.IsValueType()) - ComputedTypeSpecifics = TypeSpecifics.ReferenceType; - else if (OriginalType.IsEnum) - ComputedTypeSpecifics = TypeSpecifics.BlittableStruct; - else if (OriginalType.HasGenericParameters()) - ComputedTypeSpecifics = TypeSpecifics.NonBlittableStruct; // not reference type, covered by first if - } - - // These are initialized in AddMembers, which is called from an early rewrite pass. - public IFieldDescriptor ClassPointerFieldRef { get; private set; } = null!; - public ITypeDefOrRef SelfSubstitutedRef { get; private set; } = null!; - - public IEnumerable Fields => myFieldContexts.Values; - public IEnumerable Methods => myMethodContexts.Values; - - public void AddMembers() - { - if (NewType.HasGenericParameters()) - { - var genericInstanceType = new GenericInstanceTypeSignature(NewType, NewType.IsValueType()); - foreach (var newTypeGenericParameter in NewType.GenericParameters) - genericInstanceType.TypeArguments.Add(newTypeGenericParameter.ToTypeSignature()); - SelfSubstitutedRef = NewType.DeclaringModule!.DefaultImporter.ImportTypeSignature(genericInstanceType).ToTypeDefOrRef(); - var genericTypeRef = new GenericInstanceTypeSignature( - AssemblyContext.Imports.Il2CppClassPointerStore.ToTypeDefOrRef(), - AssemblyContext.Imports.Il2CppClassPointerStore.IsValueType(), - SelfSubstitutedRef.ToTypeSignature()); - ClassPointerFieldRef = ReferenceCreator.CreateFieldReference("NativeClassPtr", AssemblyContext.Imports.Module.IntPtr(), - NewType.DeclaringModule.DefaultImporter.ImportType(genericTypeRef.ToTypeDefOrRef())); - } - else - { - SelfSubstitutedRef = NewType; - var genericTypeRef = new GenericInstanceTypeSignature( - AssemblyContext.Imports.Il2CppClassPointerStore.ToTypeDefOrRef(), - AssemblyContext.Imports.Il2CppClassPointerStore.IsValueType()); - if (OriginalType.ToTypeSignature().IsPrimitive() || OriginalType.FullName == "System.String") - genericTypeRef.TypeArguments.Add( - NewType.DeclaringModule!.ImportCorlibReference(OriginalType.FullName)); - else - genericTypeRef.TypeArguments.Add(SelfSubstitutedRef.ToTypeSignature()); - ClassPointerFieldRef = ReferenceCreator.CreateFieldReference("NativeClassPtr", AssemblyContext.Imports.Module.IntPtr(), - NewType.DeclaringModule!.DefaultImporter.ImportType(genericTypeRef.ToTypeDefOrRef())); - } - - if (OriginalType.IsEnum) return; - - var renamedFieldCounts = new Dictionary(); - - foreach (var originalTypeField in OriginalType.Fields) - myFieldContexts[originalTypeField] = new FieldRewriteContext(this, originalTypeField, renamedFieldCounts); - - var hasExtensionMethods = false; - - foreach (var originalTypeMethod in OriginalType.Methods) - { - if (originalTypeMethod.IsStatic && originalTypeMethod.IsConstructor) - continue; - if (originalTypeMethod.IsConstructor - && originalTypeMethod.Parameters is [{ ParameterType: CorLibTypeSignature { ElementType: ElementType.I } }]) - continue; - var modules = this.AssemblyContext.GlobalContext.Assemblies.Select(a => a.OriginalAssembly.ManifestModule!); - - var methodRewriteContext = new MethodRewriteContext(this, originalTypeMethod); - myMethodContexts[originalTypeMethod] = methodRewriteContext; - myMethodContextsByName[originalTypeMethod.Name!] = methodRewriteContext; - - if (methodRewriteContext.HasExtensionAttribute) hasExtensionMethods = true; - } - - if (hasExtensionMethods) - NewType.CustomAttributes.Add(new CustomAttribute(AssemblyContext.Imports.Module.ExtensionAttributeCtor())); - } - - public FieldRewriteContext GetFieldByOldField(FieldDefinition field) - { - return myFieldContexts[field]; - } - - public MethodRewriteContext GetMethodByOldMethod(MethodDefinition method) - { - return myMethodContexts[method]; - } - - public MethodRewriteContext? TryGetMethodByOldMethod(MethodDefinition method) - { - return myMethodContexts.TryGetValue(method, out var result) ? result : null; - } - - public MethodRewriteContext? TryGetMethodByName(string name) - { - return myMethodContextsByName.TryGetValue(name, out var result) ? result : null; - } - - public MethodRewriteContext? TryGetMethodByUnityAssemblyMethod(MethodDefinition method) - { - foreach (var methodRewriteContext in myMethodContexts) - { - var originalMethod = methodRewriteContext.Value.OriginalMethod; - if (originalMethod.Name != method.Name) continue; - if (originalMethod.Parameters.Count != method.Parameters.Count) continue; - var badMethod = false; - for (var i = 0; i < originalMethod.Parameters.Count; i++) - if (originalMethod.Parameters[i].ParameterType.FullName != method.Parameters[i].ParameterType.FullName) - { - badMethod = true; - break; - } - - if (badMethod) continue; - - return methodRewriteContext.Value; - } - - return null; - } - - public FieldRewriteContext? TryGetFieldByUnityAssemblyField(FieldDefinition field) - { - foreach (var fieldRewriteContext in myFieldContexts) - { - var originalField = fieldRewriteContext.Value.OriginalField; - if (originalField.Name != field.Name) continue; - - if (originalField.Signature?.FieldType.FullName != field.Signature?.FieldType.FullName) - continue; - - return fieldRewriteContext.Value; - } - - return null; - } - - private string GetDebuggerDisplay() - { - return NewType.FullName; - } -} diff --git a/Il2CppInterop.Generator/Conversions/CastClassConversion.cs b/Il2CppInterop.Generator/Conversions/CastClassConversion.cs new file mode 100644 index 00000000..34b129cc --- /dev/null +++ b/Il2CppInterop.Generator/Conversions/CastClassConversion.cs @@ -0,0 +1,12 @@ +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Conversions; + +internal sealed class CastClassConversion(TypeAnalysisContext targetType) : Conversion +{ + public override void Add(List instructions) + { + instructions.Add(CilOpCodes.Castclass, targetType); + } +} diff --git a/Il2CppInterop.Generator/Conversions/Conversion.cs b/Il2CppInterop.Generator/Conversions/Conversion.cs new file mode 100644 index 00000000..8dc5dc59 --- /dev/null +++ b/Il2CppInterop.Generator/Conversions/Conversion.cs @@ -0,0 +1,8 @@ +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Conversions; + +internal abstract class Conversion +{ + public abstract void Add(List instructions); +} diff --git a/Il2CppInterop.Generator/Conversions/IsInstanceConversion.cs b/Il2CppInterop.Generator/Conversions/IsInstanceConversion.cs new file mode 100644 index 00000000..ba6be280 --- /dev/null +++ b/Il2CppInterop.Generator/Conversions/IsInstanceConversion.cs @@ -0,0 +1,12 @@ +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Conversions; + +internal sealed class IsInstanceConversion(TypeAnalysisContext targetType) : Conversion +{ + public override void Add(List instructions) + { + instructions.Add(CilOpCodes.Isinst, targetType); + } +} diff --git a/Il2CppInterop.Generator/Conversions/MethodCallConversion.cs b/Il2CppInterop.Generator/Conversions/MethodCallConversion.cs new file mode 100644 index 00000000..982413ad --- /dev/null +++ b/Il2CppInterop.Generator/Conversions/MethodCallConversion.cs @@ -0,0 +1,12 @@ +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Conversions; + +internal sealed class MethodCallConversion(MethodAnalysisContext method) : Conversion +{ + public override void Add(List instructions) + { + instructions.Add(CilOpCodes.Call, method); + } +} diff --git a/Il2CppInterop.Generator/Conversions/NullConversion.cs b/Il2CppInterop.Generator/Conversions/NullConversion.cs new file mode 100644 index 00000000..e7839ee2 --- /dev/null +++ b/Il2CppInterop.Generator/Conversions/NullConversion.cs @@ -0,0 +1,16 @@ +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Conversions; + +internal sealed class NullConversion : Conversion +{ + public static NullConversion Instance = new(); + + private NullConversion() + { + } + + public override void Add(List instructions) + { + } +} diff --git a/Il2CppInterop.Generator/DelegateConversionProcessingLayer.cs b/Il2CppInterop.Generator/DelegateConversionProcessingLayer.cs new file mode 100644 index 00000000..251c83d9 --- /dev/null +++ b/Il2CppInterop.Generator/DelegateConversionProcessingLayer.cs @@ -0,0 +1,306 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime; + +namespace Il2CppInterop.Generator; + +public class DelegateConversionProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Delegate Conversion"; + + public override string Id => "delegate_conversion"; + + /// + /// The maximum number of parameters that a System.Action or System.Func delegate can have. + /// + private const int MaxSystemDelegateParameters = 16; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + GetActionFuncDelegates(appContext, out var actionTypes, out var funcTypes); + + var multicastDelegateType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.MulticastDelegate"); + var asyncCallbackType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.AsyncCallback"); + var iasyncResultType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.IAsyncResult"); + + var delegateSupportType = appContext.ResolveTypeOrThrow(typeof(DelegateSupport)); + var delegateSupportMethod = delegateSupportType.GetMethodByName(nameof(DelegateSupport.ConvertDelegate)); + + var il2CppSystemDelegate = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Delegate"); + var il2CppSystemDelegateCombine = il2CppSystemDelegate.Methods.Single(m => m.Name == "Combine" && m.Parameters.Count == 2); + var il2CppSystemDelegateRemove = il2CppSystemDelegate.GetMethodByName("Remove"); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + // for rather than foreach, as we will be adding items to the collection + for (var typeIndex = 0; typeIndex < assembly.Types.Count; typeIndex++) + { + var type = assembly.Types[typeIndex]; + if (!type.IsIl2CppDelegate) + continue; + + // Remove variance on generic parameters because only interfaces and (real) delegates can have variance. + // https://github.com/dotnet/csharplang/discussions/2498 + // https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance + { + // Before: Func + // After: Func + // + // This inherently makes some code invalid, both normal and unstripped. + // There's probably no (good) way to fix that because the runtime doesn't actually change the type. + // https://lab.razor.fyi/#hZDBSsRADIaRetCcxCfIcXrpA7giqIsFURFcEDw5zoYamM6UmbSwLD36BB68-wC-nyels3VVFM0hhPzk-38CzxnAZfBV0HVh4u5j1kZ2FV4tolA9AWjaO8sGjdUx4pGOhEvov2-nFLijOe596ith5MISEBGjaGGDnec5nmt2Kk_rlTjUsXfRWyquAwudsSNVkoxwlRclyWzRkMqLk9baC11TPkm3fepN4E4LfdgcGmHv9odEB1iSDMMPx0DSBodTP7tnV_1PG8Mk4DrY78y15R_U9IrRXKXfiQ4VyVdkDz2cZqF1N9tbLw9vT6-0s3m78Q4 + // One way the problem might be solved is to: + // * Generate a nested interface for every Il2Cpp delegate type, eg IFunc. + // * Replace all occurances of the class type with the interface type, everywhere. + // * During unstripping, redirect references to the class methods to the interface methods. + + foreach (var genericParameter in type.GenericParameters) + { + genericParameter.OverrideAttributes = genericParameter.Attributes & ~GenericParameterAttributes.VarianceMask; + } + } + + var concreteType = type.SelfInstantiateIfGeneric(); + + var addition = new InjectedMethodAnalysisContext( + type, + "op_Addition", + concreteType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [concreteType, concreteType]) + { + IsInjected = true, + }; + type.Methods.Add(addition); + addition.PutExtraData(new() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, il2CppSystemDelegateCombine), + new Instruction(CilOpCodes.Castclass, concreteType), + new Instruction(CilOpCodes.Ret), + ] + }); + + var subtraction = new InjectedMethodAnalysisContext( + type, + "op_Subtraction", + concreteType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [concreteType, concreteType]) + { + IsInjected = true, + }; + type.Methods.Add(subtraction); + subtraction.PutExtraData(new() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, il2CppSystemDelegateRemove), + new Instruction(CilOpCodes.Castclass, concreteType), + new Instruction(CilOpCodes.Ret), + ] + }); + + var invokeMethod = type.TryGetMethodByName("Invoke"); + if (invokeMethod is null) + { + // The delegate does not have an Invoke method. + // This can happen if the delegate is unstripped, but a parameter type could not unstripped. + continue; + } + + Debug.Assert(invokeMethod.ReturnType is not PointerTypeAnalysisContext and not ByRefTypeAnalysisContext, "All pointers and by reference types should be converted to generics"); + Debug.Assert(invokeMethod.Parameters.All(p => p.ParameterType is not PointerTypeAnalysisContext and not ByRefTypeAnalysisContext), "All pointers and by reference types should be converted to generics"); + + TypeAnalysisContext managedDelegateType; + + if (invokeMethod.Parameters.Count <= MaxSystemDelegateParameters) + { + // We can use a System delegate + + if (!invokeMethod.IsVoid) + { + managedDelegateType = funcTypes[invokeMethod.Parameters.Count] + .MakeGenericInstanceType(invokeMethod.Parameters.Select(p => p.ParameterType).Append(invokeMethod.ReturnType)); + } + else if (invokeMethod.Parameters.Count > 0) + { + managedDelegateType = actionTypes[invokeMethod.Parameters.Count] + .MakeGenericInstanceType(invokeMethod.Parameters.Select(p => p.ParameterType)); + } + else + { + managedDelegateType = actionTypes[0]; + } + } + else + { + // We need to create a new delegate type + + var name = type.Name is "Delegate" ? "Converted" : "Delegate"; // Name can't be the same as the declaring type + var injectedType = type.InjectNestedType( + name, + multicastDelegateType); + + injectedType.IsInjected = true; + injectedType.CopyGenericParameters(type, true); + + TypeAnalysisContext returnType; + List parameterTypes = invokeMethod.Parameters.Select(p => p.ParameterType).ToList(); + { + var genericParameterDictionary = Enumerable.Range(0, type.GenericParameters.Count) + .ToDictionary(i => type.GenericParameters[i], i => injectedType.GenericParameters[i]); + var replacementVisitor = new TypeReplacementVisitor(genericParameterDictionary); + replacementVisitor.Modify(parameterTypes); + returnType = replacementVisitor.Replace(invokeMethod.ReturnType); + } + + // Constructor + { + injectedType.Methods.Add(new InjectedMethodAnalysisContext( + injectedType, + ".ctor", + appContext.SystemTypes.SystemVoidType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + [appContext.SystemTypes.SystemObjectType, appContext.SystemTypes.SystemIntPtrType], + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + + // Invoke + { + injectedType.Methods.Add(new InjectedMethodAnalysisContext( + injectedType, + "Invoke", + returnType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + parameterTypes.ToArray(), + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + + // BeginInvoke + { + injectedType.Methods.Add(new InjectedMethodAnalysisContext( + injectedType, + "BeginInvoke", + iasyncResultType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + parameterTypes.Append(asyncCallbackType).Append(appContext.SystemTypes.SystemObjectType).ToArray(), + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + + // EndInvoke + { + injectedType.Methods.Add(new InjectedMethodAnalysisContext( + injectedType, + "EndInvoke", + returnType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + [iasyncResultType], + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + + if (injectedType.GenericParameters.Count > 0) + { + managedDelegateType = injectedType.MakeGenericInstanceType(type.GenericParameters); + } + else + { + managedDelegateType = injectedType; + } + } + + type.ManagedDelegateType = managedDelegateType; + + // Explicit conversion operator from the managed delegate type to the Il2Cpp delegate type. + { + var explicitConversion = new InjectedMethodAnalysisContext( + type, + "op_Explicit", + concreteType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [managedDelegateType]) + { + IsInjected = true, + }; + type.Methods.Add(explicitConversion); + explicitConversion.PutExtraData(new() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, delegateSupportMethod.MakeGenericInstanceMethod(concreteType)), + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // Explicit conversion operator from the Il2Cpp delegate type to the managed delegate type. + { + var explicitConversion = new InjectedMethodAnalysisContext( + type, + "op_Explicit", + managedDelegateType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [concreteType]) + { + IsInjected = true, + }; + type.Methods.Add(explicitConversion); + + MethodAnalysisContext managedDelegateConstructor; + if (managedDelegateType is GenericInstanceTypeAnalysisContext genericInstance) + { + managedDelegateConstructor = genericInstance.GenericType.Methods.Single(m => m.IsInstanceConstructor).MakeConcreteGenericMethod(genericInstance.GenericArguments, []); + } + else + { + managedDelegateConstructor = managedDelegateType.Methods.Single(m => m.IsInstanceConstructor); + } + + explicitConversion.PutExtraData(new() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldftn, invokeMethod.MaybeMakeConcreteGeneric(type.GenericParameters, [])), + new Instruction(CilOpCodes.Newobj, managedDelegateConstructor), + new Instruction(CilOpCodes.Ret), + ] + }); + } + } + } + } + + private static void GetActionFuncDelegates(ApplicationAnalysisContext appContext, out TypeAnalysisContext[] actionTypes, out TypeAnalysisContext[] funcTypes) + { + var mscorlib = appContext.Mscorlib; + + actionTypes = Enumerable.Range(0, MaxSystemDelegateParameters + 1) + .Select(i => GetActionType(mscorlib, i)) + .ToArray(); + + funcTypes = Enumerable.Range(0, MaxSystemDelegateParameters + 1) + .Select(i => GetFuncType(mscorlib, i)) + .ToArray(); + + static TypeAnalysisContext GetActionType(AssemblyAnalysisContext mscorlib, int parameterCount) => parameterCount switch + { + 0 => mscorlib.GetTypeByFullNameOrThrow("System.Action"), + _ => mscorlib.GetTypeByFullNameOrThrow($"System.Action`{parameterCount}"), + }; + static TypeAnalysisContext GetFuncType(AssemblyAnalysisContext mscorlib, int parameterCount) => mscorlib.GetTypeByFullNameOrThrow($"System.Func`{parameterCount + 1}"); + } +} diff --git a/Il2CppInterop.Generator/EnumProcessingLayer.cs b/Il2CppInterop.Generator/EnumProcessingLayer.cs new file mode 100644 index 00000000..c0195472 --- /dev/null +++ b/Il2CppInterop.Generator/EnumProcessingLayer.cs @@ -0,0 +1,287 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class EnumProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Enum Processor"; + public override string Id => "enum_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + var il2CppSystemValueType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.ValueType"); + var il2CppSystemEnum = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Enum"); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.DefaultBaseType != il2CppSystemEnum) + continue; + + // Sequential layout is required to ensure that this struct has the same memory layout as a C# enum. + type.OverrideAttributes = (type.Attributes & ~TypeAttributes.LayoutMask) | TypeAttributes.SequentialLayout; + + var valueField = type.Fields.First(f => f.Name == "value__"); + + valueField.OverrideAttributes = FieldAttributes.Private; + + Debug.Assert(valueField.FieldType == valueField.DefaultFieldType, "Field type should not be overriden."); + + var il2CppType = valueField.FieldType; + var monoType = appContext.Mscorlib.GetTypeByFullNameOrThrow($"System.{il2CppType.Name}"); + + Debug.Assert(monoType != il2CppType); + + type.EnumIl2CppUnderlyingType = il2CppType; + type.EnumMonoUnderlyingType = monoType; + + // Constructor + var constructor = new InjectedMethodAnalysisContext( + type, + ".ctor", + appContext.SystemTypes.SystemVoidType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + [il2CppType], + ["value"]) + { + IsInjected = true, + }; + { + type.Methods.Add(constructor); + + var methodBody = new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Stfld, valueField), + new Instruction(CilOpCodes.Ret) + ] + }; + + constructor.PutExtraData(methodBody); + } + + #region Conversions + // Conversion: enum -> il2cpp + var conversionEnumIl2Cpp = new InjectedMethodAnalysisContext( + type, + "op_Explicit", + il2CppType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [type], + ["value"]) + { + IsInjected = true, + }; + { + type.Methods.Add(conversionEnumIl2Cpp); + var methodBody = new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarga, conversionEnumIl2Cpp.Parameters[0]), + new Instruction(CilOpCodes.Ldfld, valueField), + new Instruction(CilOpCodes.Ret) + ] + }; + conversionEnumIl2Cpp.PutExtraData(methodBody); + } + + // Conversion: il2cpp -> enum + var conversionIl2CppEnum = new InjectedMethodAnalysisContext( + type, + "op_Explicit", + type, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [il2CppType], + ["value"]) + { + IsInjected = true, + }; + { + type.Methods.Add(conversionIl2CppEnum); + var methodBody = new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Newobj, constructor), + new Instruction(CilOpCodes.Ret) + ] + }; + conversionIl2CppEnum.PutExtraData(methodBody); + } + + // Conversion: enum -> mono + var conversionEnumMono = new InjectedMethodAnalysisContext( + type, + "op_Explicit", + monoType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [type], + ["value"]) + { + IsInjected = true, + }; + { + type.Methods.Add(conversionEnumMono); + var methodBody = new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, conversionEnumIl2Cpp), + new Instruction(CilOpCodes.Call, il2CppType.GetImplicitConversionTo(monoType)), + new Instruction(CilOpCodes.Ret) + ] + }; + conversionEnumMono.PutExtraData(methodBody); + } + + // Conversion: mono -> enum + var conversionMonoEnum = new InjectedMethodAnalysisContext( + type, + "op_Explicit", + type, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + [monoType], + ["value"]) + { + IsInjected = true, + }; + { + type.Methods.Add(conversionMonoEnum); + var methodBody = new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, il2CppType.GetImplicitConversionFrom(monoType)), + new Instruction(CilOpCodes.Call, conversionIl2CppEnum), + new Instruction(CilOpCodes.Ret) + ] + }; + conversionMonoEnum.PutExtraData(methodBody); + } + #endregion + + #region Bitwise Operators + // & + { + var method = new InjectedMethodAnalysisContext( + type, + "op_BitwiseAnd", + type, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + [type, type]) + { + IsInjected = true, + }; + + type.Methods.Add(method); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, conversionEnumMono), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, conversionEnumMono), + new Instruction(CilOpCodes.And), + new Instruction(CilOpCodes.Call, conversionMonoEnum), + new Instruction(CilOpCodes.Ret) + ] + }); + } + + // | + { + var method = new InjectedMethodAnalysisContext( + type, + "op_BitwiseOr", + type, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + [type, type]) + { + IsInjected = true, + }; + + type.Methods.Add(method); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, conversionEnumMono), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, conversionEnumMono), + new Instruction(CilOpCodes.Or), + new Instruction(CilOpCodes.Call, conversionMonoEnum), + new Instruction(CilOpCodes.Ret) + ] + }); + } + + // ^ + { + var method = new InjectedMethodAnalysisContext( + type, + "op_ExclusiveOr", + type, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + [type, type]) + { + IsInjected = true, + }; + + type.Methods.Add(method); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, conversionEnumMono), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, conversionEnumMono), + new Instruction(CilOpCodes.Xor), + new Instruction(CilOpCodes.Call, conversionMonoEnum), + new Instruction(CilOpCodes.Ret) + ] + }); + } + + // ~ + { + var method = new InjectedMethodAnalysisContext( + type, + "op_OnesComplement", + type, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + [type]) + { + IsInjected = true, + }; + + type.Methods.Add(method); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, conversionEnumMono), + new Instruction(CilOpCodes.Not), + new Instruction(CilOpCodes.Call, conversionMonoEnum), + new Instruction(CilOpCodes.Ret) + ] + }); + } + #endregion + } + } + } +} diff --git a/Il2CppInterop.Generator/EventProcessingLayer.cs b/Il2CppInterop.Generator/EventProcessingLayer.cs new file mode 100644 index 00000000..9d04b70a --- /dev/null +++ b/Il2CppInterop.Generator/EventProcessingLayer.cs @@ -0,0 +1,66 @@ +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Model.CustomAttributes; +using Il2CppInterop.Common.Attributes; + +namespace Il2CppInterop.Generator; + +public class EventProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Event Processor"; + + public override string Id => "event_processor"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + // C# requires events to have a delegate type and will not allow event syntax without it. + // https://github.com/ds5678/Il2CppEventTest + // We remove the event definitions and add attributes to the add/remove/invoke methods instead. + + var il2CppEventAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppEventAttribute)); + var il2CppEventAttributeConstructor = il2CppEventAttribute.GetMethodByName(".ctor"); + + var il2CppMemberAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppMemberAttribute)); + var il2CppMemberAttributeName = il2CppMemberAttribute.GetPropertyByName(nameof(Il2CppMemberAttribute.Name)); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + for (var i = 0; i < type.Events.Count; i++) + { + var @event = type.Events[i]; + if (@event.IsInjected) + continue; + + AddAttribute(@event.Adder, @event.DefaultName, il2CppEventAttributeConstructor, il2CppMemberAttributeName); + AddAttribute(@event.Remover, @event.DefaultName, il2CppEventAttributeConstructor, il2CppMemberAttributeName); + AddAttribute(@event.Invoker, @event.DefaultName, il2CppEventAttributeConstructor, il2CppMemberAttributeName); + + type.Events.RemoveAt(i); + } + } + } + } + + private static void AddAttribute(MethodAnalysisContext? method, string name, MethodAnalysisContext il2CppEventAttributeConstructor, PropertyAnalysisContext il2CppMemberAttributeName) + { + if (method is not null) + { + var attribute = new AnalyzedCustomAttribute(il2CppEventAttributeConstructor); + var parameter = new CustomAttributePrimitiveParameter(name, attribute, CustomAttributeParameterKind.Property, 0); + attribute.Properties.Add(new CustomAttributeProperty(il2CppMemberAttributeName, parameter)); + method.CustomAttributes ??= new(1); + method.CustomAttributes.Add(attribute); + + method.OverrideAttributes = method.Attributes & ~MethodAttributes.SpecialName; + } + } +} diff --git a/Il2CppInterop.Generator/ExceptionHierarchyProcessingLayer.cs b/Il2CppInterop.Generator/ExceptionHierarchyProcessingLayer.cs new file mode 100644 index 00000000..da424e28 --- /dev/null +++ b/Il2CppInterop.Generator/ExceptionHierarchyProcessingLayer.cs @@ -0,0 +1,190 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime.Exceptions; + +namespace Il2CppInterop.Generator; + +public class ExceptionHierarchyProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Exception Hierarchy"; + public override string Id => "exception_hierarchy"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var il2CppSystemExceptionType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Exception"); + + var il2CppExceptionInterface = appContext.ResolveTypeOrThrow(typeof(IIl2CppException)); + var il2CppExceptionInterfaceMethod = il2CppExceptionInterface.GetMethodByName(nameof(IIl2CppException.CreateSystemException)); + var il2CppExceptionInterfaceMethodName = $"{il2CppExceptionInterface.FullName}.{il2CppExceptionInterfaceMethod.Name}"; + + var exceptionTypes = GetExceptionTypes(appContext); + + foreach (var exceptionType in exceptionTypes) + { + var nestedExceptionType = exceptionType.InjectNestedType(GetUniqueNameForNestedClass(exceptionType), null, TypeAttributes.NestedPublic); + nestedExceptionType.IsInjected = true; + exceptionType.SystemExceptionType = nestedExceptionType; + + // Copy generic parameters + nestedExceptionType.CopyGenericParameters(exceptionType, true); + + // Inject constructor + nestedExceptionType.InjectMethodContext( + ".ctor", + appContext.SystemTypes.SystemVoidType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + il2CppSystemExceptionType); + } + + foreach (var exceptionType in exceptionTypes) + { + var nestedExceptionType = exceptionType.SystemExceptionType!; + + // Base type + Debug.Assert(exceptionType.BaseType is not null); + if (exceptionType == il2CppSystemExceptionType) + { + nestedExceptionType.SetDefaultBaseType(appContext.ResolveTypeOrThrow(typeof(Il2CppException))); + } + else if (exceptionType.BaseType is GenericInstanceTypeAnalysisContext exceptionBaseTypeGenericInstance) + { + // Build replacements dictionary + Dictionary replacements = new(); + for (var i = 0; i < exceptionType.GenericParameters.Count; i++) + { + replacements.Add(exceptionType.GenericParameters[i], nestedExceptionType.GenericParameters[i]); + } + replacements.Add(exceptionBaseTypeGenericInstance.GenericType, exceptionBaseTypeGenericInstance.GenericType.SystemExceptionType!); + + nestedExceptionType.SetDefaultBaseType(new TypeReplacementVisitor(replacements).Replace(exceptionBaseTypeGenericInstance)); + } + else + { + nestedExceptionType.SetDefaultBaseType(exceptionType.BaseType.SystemExceptionType); + } + + // Constructor + Debug.Assert(nestedExceptionType.Methods.Count is 1); + var constructor = nestedExceptionType.Methods[0]; + Debug.Assert(nestedExceptionType.BaseType is not null); + MethodAnalysisContext baseConstructor; + if (nestedExceptionType.BaseType is GenericInstanceTypeAnalysisContext nestedExceptionBaseTypeGenericInstance) + { + baseConstructor = new ConcreteGenericMethodAnalysisContext( + nestedExceptionBaseTypeGenericInstance.GenericType.GetMethodByName(".ctor"), + nestedExceptionBaseTypeGenericInstance.GenericArguments, + []); + } + else + { + baseConstructor = nestedExceptionType.BaseType.GetMethodByName(".ctor"); + } + constructor.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new(CilOpCodes.Ldarg_0), // this + new(CilOpCodes.Ldarg_1), // object + new(CilOpCodes.Call, baseConstructor), + new(CilOpCodes.Ret), + ], + }); + + // Interface implementation + exceptionType.InterfaceContexts.Add(il2CppExceptionInterface); + { + var interfaceMethod = new InjectedMethodAnalysisContext( + exceptionType, + il2CppExceptionInterfaceMethodName, + il2CppExceptionInterfaceMethod.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot, + []) + { + IsInjected = true + }; + exceptionType.Methods.Add(interfaceMethod); + interfaceMethod.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new(CilOpCodes.Ldarg_0), // this + new(CilOpCodes.Newobj, exceptionType.HasGenericParameters ? new ConcreteGenericMethodAnalysisContext(constructor, exceptionType.GenericParameters, []) : constructor), + new(CilOpCodes.Ret), + ], + }); + interfaceMethod.Overrides.Add(il2CppExceptionInterfaceMethod); + } + } + } + + private static HashSet GetExceptionTypes(ApplicationAnalysisContext appContext) + { + var exceptionTypes = new HashSet(); + var nonExceptionTypes = new HashSet(); + + // Add Il2CppSystem.Exception + { + exceptionTypes.Add(appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Exception")); + } + + // Add all derived types of Il2CppSystem.Exception + foreach (var type in appContext.AllTypes) + { + AddTypeToSet(type, exceptionTypes, nonExceptionTypes); + } + + return exceptionTypes; + + static bool AddTypeToSet(TypeAnalysisContext? type, HashSet exceptionTypes, HashSet nonExceptionTypes) + { + if (type is GenericInstanceTypeAnalysisContext genericInstance) + { + return AddTypeToSet(genericInstance.GenericType, exceptionTypes, nonExceptionTypes); + } + Debug.Assert(type is not ReferencedTypeAnalysisContext); + if (type == null) + { + return false; + } + if (exceptionTypes.Contains(type)) + { + return true; + } + if (nonExceptionTypes.Contains(type)) + { + return false; + } + var isException = AddTypeToSet(type.BaseType, exceptionTypes, nonExceptionTypes); + if (isException) + { + exceptionTypes.Add(type); + } + else + { + nonExceptionTypes.Add(type); + } + return isException; + } + } + + private static string GetUniqueNameForNestedClass(TypeAnalysisContext declaringType) + { + var genericParameterCount = declaringType.GenericParameters.Count; + var nonGenericName = "Exception"; + while (true) + { + var name = genericParameterCount > 0 ? $"{nonGenericName}`{genericParameterCount}" : nonGenericName; + if (declaringType.Name == name || declaringType.NestedTypes.Any(t => t.Name == name)) + { + nonGenericName += "_"; + } + else + { + return name; + } + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/ApplicationAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/ApplicationAnalysisContextExtensions.cs new file mode 100644 index 00000000..7f69a0a2 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/ApplicationAnalysisContextExtensions.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Cpp2IL.Core.Model.Contexts; +using LibCpp2IL; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class ApplicationAnalysisContextExtensions +{ + extension(ApplicationAnalysisContext appContext) + { + public InjectedAssemblyAnalysisContext InjectAssembly(Assembly assembly) + { + return appContext.InjectAssembly(assembly.GetName()); + } + + public InjectedAssemblyAnalysisContext InjectAssembly(AssemblyName assemblyName) + { +#pragma warning disable SYSLIB0037 // Type or member is obsolete + return appContext.InjectAssembly( + assemblyName.Name!, + assemblyName.Version, + (uint)assemblyName.HashAlgorithm, + (uint)assemblyName.Flags, + assemblyName.CultureName, + assemblyName.GetPublicKeyToken(), + assemblyName.GetPublicKey()); +#pragma warning restore SYSLIB0037 // Type or member is obsolete + } + + public TypeAnalysisContext ResolveTypeOrThrow(Type? type) + { + return appContext.ResolveType(type) ?? throw new($"Unable to resolve type {type?.FullName}"); + } + + public TypeAnalysisContext? ResolveType(Type? type) + { + if (type is null) + return null; + + var assemblyName = type.Assembly.GetName().Name!; + if (assemblyName == "System.Private.CoreLib") + assemblyName = "mscorlib"; + var assembly = appContext.GetAssemblyByName(assemblyName); + return assembly?.GetTypeByFullName(type.FullName!); + } + + public AssemblyAnalysisContext Mscorlib => appContext.AssembliesByName["mscorlib"]; + public AssemblyAnalysisContext Il2CppMscorlib => appContext.AssembliesByName["Il2Cppmscorlib"]; + + [return: NotNullIfNotNull(nameof(methodReference))] + public ConcreteGenericMethodAnalysisContext? ResolveContextForMethod(Cpp2IlMethodRef? methodReference) + { + return methodReference is not null + ? appContext.ConcreteGenericMethodsByRef.TryGetValue(methodReference, out var context) ? context : new(methodReference, appContext) + : null; + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs b/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs index 7fc9f869..15b221b8 100644 --- a/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs +++ b/Il2CppInterop.Generator/Extensions/AsmResolverExtensions.cs @@ -1,107 +1,19 @@ using AsmResolver.DotNet; -using AsmResolver.DotNet.Collections; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; namespace Il2CppInterop.Generator.Extensions; internal static class AsmResolverExtensions { - /// - /// Boolean, Char, or numeric type - /// - public static bool IsPrimitive(this TypeSignature type) + extension(CilOpCode opCode) { - return (type as CorLibTypeSignature)?.ElementType.IsPrimitive() ?? false; + public bool IsLoadConstantI4 + { + get => opCode.Code is (>= CilCode.Ldc_I4_M1 and <= CilCode.Ldc_I4_8) or CilCode.Ldc_I4_S or CilCode.Ldc_I4; + } } - /// - /// Boolean, Char, or numeric type - /// - public static bool IsPrimitive(this ElementType elementType) + public static TypeDefinition? TryResolve(this ITypeDescriptor typeDescriptor, RuntimeContext? runtimeContext) { - return elementType is >= ElementType.Boolean and <= ElementType.R8 or ElementType.I or ElementType.U; - } - - public static TypeSignature GetElementType(this TypeSignature type) => type switch - { - TypeSpecificationSignature specification => specification.BaseType, - _ => type, - }; - - public static void AddLoadArgument(this ILProcessor instructions, int argumentIndex) - { - instructions.Add(OpCodes.Ldarg, instructions.GetArgument(argumentIndex)); - } - - public static void AddLoadArgumentAddress(this ILProcessor instructions, int argumentIndex) - { - instructions.Add(OpCodes.Ldarga, instructions.GetArgument(argumentIndex)); - } - - private static Parameter GetArgument(this ILProcessor instructions, int argumentIndex) - { - var method = instructions.Owner.Owner; - return method.IsStatic - ? method.Parameters[argumentIndex] - : argumentIndex == 0 - ? method.Parameters.ThisParameter! - : method.Parameters[argumentIndex - 1]; - } - - public static bool IsReferenceType(this TypeDefinition type) => !type.IsValueType(); - - public static bool HasGenericParameters(this TypeDefinition type) => type.GenericParameters.Count > 0; - - public static bool HasGenericParameters(this MethodDefinition method) => method.GenericParameters.Count > 0; - - public static bool HasConstant(this FieldDefinition field) => field.Constant is not null; - - public static bool IsInstance(this FieldDefinition field) => !field.IsStatic; - - public static bool HasMethods(this TypeDefinition type) => type.Methods.Count > 0; - - public static bool HasFields(this TypeDefinition type) => type.Fields.Count > 0; - - public static bool IsNested(this ITypeDefOrRef type) => type.DeclaringType is not null; - - public static bool IsNested(this TypeSignature type) => type.DeclaringType is not null; - - public static bool IsPointerLike(this TypeSignature type) => type is PointerTypeSignature or ByReferenceTypeSignature; - - public static bool IsValueTypeLike(this TypeSignature type) => type.IsValueType() || type.IsPointerLike(); - - public static bool IsSystemEnum(this GenericParameterConstraint constraint) => constraint.Constraint?.FullName is "System.Enum"; - - public static bool IsSystemValueType(this GenericParameterConstraint constraint) => constraint.Constraint?.FullName is "System.ValueType"; - - public static bool IsInterface(this GenericParameterConstraint constraint) => constraint.Constraint?.Resolve()?.IsInterface == true; - - public static ITypeDefOrRef? AttributeType(this CustomAttribute attribute) => attribute.Constructor?.DeclaringType; - - public static Parameter AddParameter(this MethodDefinition method, TypeSignature parameterSignature, string parameterName, ParameterAttributes parameterAttributes = default) - { - var parameter = method.AddParameter(parameterSignature); - var parameterDefinition = parameter.GetOrCreateDefinition(); - parameterDefinition.Name = parameterName; - parameterDefinition.Attributes = parameterAttributes; - return parameter; - } - - public static Parameter AddParameter(this MethodDefinition method, TypeSignature parameterSignature) - { - method.Signature!.ParameterTypes.Add(parameterSignature); - method.Parameters.PullUpdatesFromMethodSignature(); - return method.Parameters[method.Parameters.Count - 1]; - } - - public static TypeDefinition GetType(this ModuleDefinition module, string fullName) - { - return module.TopLevelTypes.First(t => t.FullName == fullName); - } - - public static GenericParameterSignature ToTypeSignature(this GenericParameter genericParameter) - { - return new GenericParameterSignature(genericParameter.Owner is ITypeDescriptor ? GenericParameterType.Type : GenericParameterType.Method, genericParameter.Number); + return typeDescriptor.TryResolve(runtimeContext, out var resolvedType) ? resolvedType : null; } } diff --git a/Il2CppInterop.Generator/Extensions/AssemblyAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/AssemblyAnalysisContextExtensions.cs new file mode 100644 index 00000000..22a1591d --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/AssemblyAnalysisContextExtensions.cs @@ -0,0 +1,63 @@ +using Cpp2IL.Core.Model.Contexts; +using LibCpp2IL.BinaryStructures; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class AssemblyAnalysisContextExtensions +{ + extension(AssemblyAnalysisContext assembly) + { + public bool IsReferenceAssembly + { + get => assembly.GetExtraData("ReferenceAssembly") is true; + set => assembly.PutExtraData("ReferenceAssembly", value); + } + + public InjectedTypeAnalysisContext InjectType(Type type) + { + var result = assembly.InjectType(type.Namespace ?? "", type.Name, null, type.Attributes); + if (type.ContainsGenericParameters) + { + foreach (var genericParameter in type.GetGenericArguments()) + { + var genericParameterContext = new GenericParameterTypeAnalysisContext(genericParameter.Name, genericParameter.GenericParameterPosition, Il2CppTypeEnum.IL2CPP_TYPE_VAR, genericParameter.GenericParameterAttributes, result); + result.GenericParameters.Add(genericParameterContext); + } + } + return result; + } + + public TypeAnalysisContext GetTypeByFullNameOrThrow(string fullName) + { + return assembly.GetTypeByFullName(fullName) ?? throw new($"Unable to find type by full name {fullName}"); + } + + public TypeAnalysisContext? GetTypeByFullName(Type type) + { + var fullName = type.FullName; + return string.IsNullOrEmpty(fullName) ? null : assembly.GetTypeByFullName(fullName); + } + + public TypeAnalysisContext GetTypeByFullNameOrThrow(Type type) + { + return assembly.GetTypeByFullName(type) ?? throw new($"Unable to find type by full name {type.FullName}"); + } + + public string ImageName + { + get + { + var result = assembly.Definition?.Image.Name; + if (string.IsNullOrEmpty(result)) + { + result = assembly.DefaultName; + if (!result.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + { + result = $"{result}.dll"; + } + } + return result; + } + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/CollectionEx.cs b/Il2CppInterop.Generator/Extensions/CollectionEx.cs deleted file mode 100644 index 818cc58b..00000000 --- a/Il2CppInterop.Generator/Extensions/CollectionEx.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Il2CppInterop.Generator.Extensions; - -public static class CollectionEx -{ - public static TV GetOrCreate(this IDictionary dict, TK key, Func valueFactory) - where TK : notnull - { - if (!dict.TryGetValue(key, out var result)) - { - result = valueFactory(key); - dict[key] = result; - } - - return result; - } - - public static void AddLocked(this List list, T value) - { - lock (list) - { - list.Add(value); - } - } -} diff --git a/Il2CppInterop.Generator/Extensions/ContextWithDataStorageExtensions.cs b/Il2CppInterop.Generator/Extensions/ContextWithDataStorageExtensions.cs new file mode 100644 index 00000000..c6cf3d70 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/ContextWithDataStorageExtensions.cs @@ -0,0 +1,94 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class ContextWithDataStorageExtensions +{ + // Prevent boxing a large number of booleans. + private static readonly object True = true; + private static readonly object False = false; + + extension(ContextWithDataStorage context) + { + public void PutExtraData(T data) where T : class + { + context.PutExtraData(typeof(T).Name, data); + } + + public T? GetExtraData() where T : class + { + return context.GetExtraData(typeof(T).Name); + } + + public void RemoveExtraData() where T : class + { + context.RemoveExtraData(typeof(T).Name); + } + + public void RemoveExtraData(string key) + { + context.GetDataStorage().Remove(key); + } + + public bool TryGetExtraData([NotNullWhen(true)] out T? data) where T : class + { + data = context.GetExtraData(); + return data is not null; + } + + public bool HasExtraData() where T : class + { + return context.TryGetExtraData(out _); + } + + /// + /// This type or member was stripped by the compiler, but we restored it using external assemblies. + /// + public bool IsUnstripped + { + get => context.GetExtraBoolean("IsUnstripped"); + set => context.PutExtraBoolean("IsUnstripped", value); + } + + /// + /// This type or member is injected by the generator as a helper method. For example, static constructors to trigger Il2Cpp initialization. + /// + /// + /// For memory efficiency, this is only set at the top-level. If a type is injected, its members are not marked as injected, even though they obviously are. + /// For the purpose of this property, nested types are considered "members". + /// + public bool IsInjected + { + get => context.GetExtraBoolean("IsInjected") || (context.Parent?.IsInjected ?? false); + set => context.PutExtraBoolean("IsInjected", value); + } + + public ContextWithDataStorage? Parent => context switch + { + MethodAnalysisContext method => method.DeclaringType, + FieldAnalysisContext @field => @field.DeclaringType, + PropertyAnalysisContext property => property.DeclaringType, + EventAnalysisContext @event => @event.DeclaringType, + TypeAnalysisContext type => (ContextWithDataStorage?)type.DeclaringType ?? type.DeclaringAssembly, + _ => null, + }; + + public bool GetExtraBoolean(string key) => context.GetExtraData(key) is true; + public void PutExtraBoolean(string key, bool value) => context.PutExtraData(key, value ? True : False); + + public T GetExtraStruct(string key, T defaultValue = default) where T : struct + { + var data = context.GetExtraData(key); + return data is T t ? t : defaultValue; + } + public void PutExtraStruct(string key, T value) where T : struct + { + context.PutExtraData(key, (object)value); + } + } + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_dataStorage")] + private static extern ref Dictionary GetDataStorage(this ContextWithDataStorage context); +} diff --git a/Il2CppInterop.Generator/Extensions/CustomAttributeEx.cs b/Il2CppInterop.Generator/Extensions/CustomAttributeEx.cs deleted file mode 100644 index fba48706..00000000 --- a/Il2CppInterop.Generator/Extensions/CustomAttributeEx.cs +++ /dev/null @@ -1,52 +0,0 @@ -using AsmResolver; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.Extensions; - -public static class CustomAttributeEx -{ - public static long ExtractOffset(this IHasCustomAttribute originalMethod) - { - return ExtractLong(originalMethod, "AddressAttribute", "Offset"); - } - - public static long ExtractRva(this IHasCustomAttribute originalMethod) - { - return ExtractLong(originalMethod, "AddressAttribute", "RVA"); - } - - public static long ExtractToken(this IHasCustomAttribute originalMethod) - { - return ExtractLong(originalMethod, "TokenAttribute", "Token"); - } - - public static int ExtractFieldOffset(this IHasCustomAttribute originalField) - { - return ExtractInt(originalField, "FieldOffsetAttribute", "Offset"); - } - - public static string? GetElementAsString(this CustomAttributeArgument argument) - { - return argument.Element as Utf8String ?? argument.Element as string; - } - - private static string? Extract(this IHasCustomAttribute originalMethod, string attributeName, - string parameterName) - { - var attribute = originalMethod.CustomAttributes.SingleOrDefault(it => it.Constructor?.DeclaringType?.Name == attributeName); - var field = attribute?.Signature?.NamedArguments.SingleOrDefault(it => it.MemberName == parameterName); - - return field?.Argument.GetElementAsString(); - } - - private static long ExtractLong(this IHasCustomAttribute originalMethod, string attributeName, string parameterName) - { - return Convert.ToInt64(Extract(originalMethod, attributeName, parameterName), 16); - } - - private static int ExtractInt(this IHasCustomAttribute originalMethod, string attributeName, string parameterName) - { - return Convert.ToInt32(Extract(originalMethod, attributeName, parameterName), 16); - } -} diff --git a/Il2CppInterop.Generator/Extensions/DictionaryExtensions.cs b/Il2CppInterop.Generator/Extensions/DictionaryExtensions.cs new file mode 100644 index 00000000..8f81f6b5 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/DictionaryExtensions.cs @@ -0,0 +1,18 @@ +namespace Il2CppInterop.Generator.Extensions; + +internal static class DictionaryExtensions +{ + public static TValue? TryGetValue(this Dictionary dictionary, TKey? key) + where TKey : class + where TValue : class + { + return key is not null && dictionary.TryGetValue(key, out var value) ? value : null; + } + + public static TValue? GetValue(this Dictionary dictionary, TKey? key) + where TKey : class + where TValue : class + { + return key is not null ? dictionary[key] : null; + } +} diff --git a/Il2CppInterop.Generator/Extensions/EnumEx.cs b/Il2CppInterop.Generator/Extensions/EnumEx.cs deleted file mode 100644 index b305c61d..00000000 --- a/Il2CppInterop.Generator/Extensions/EnumEx.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AsmResolver.PE.DotNet.Metadata.Tables; - -namespace Il2CppInterop.Generator.Extensions; - -public static class EnumEx -{ - public static FieldAttributes ForcePublic(this FieldAttributes fieldAttributes) - { - return (fieldAttributes & ~FieldAttributes.FieldAccessMask & ~FieldAttributes.HasFieldMarshal) | - FieldAttributes.Public; - } - - public static GenericParameterAttributes StripValueTypeConstraint( - this GenericParameterAttributes parameterAttributes) - { - return parameterAttributes & ~(GenericParameterAttributes.NotNullableValueTypeConstraint | - GenericParameterAttributes.VarianceMask); - } -} diff --git a/Il2CppInterop.Generator/Extensions/FieldAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/FieldAnalysisContextExtensions.cs new file mode 100644 index 00000000..4bdc89a6 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/FieldAnalysisContextExtensions.cs @@ -0,0 +1,96 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class FieldAnalysisContextExtensions +{ + extension(FieldAnalysisContext field) + { + [MaybeNull] + public PropertyAnalysisContext PropertyAccessor + { + get => @field.GetExtraData("PropertyAccessor"); + set => @field.PutExtraData("PropertyAccessor", value); + } + + [MaybeNull] + public FieldAnalysisContext FieldInfoAddressStorage + { + get => @field.GetExtraData("FieldInfoAddressStorage"); + set => @field.PutExtraData("FieldInfoAddressStorage", value); + } + + [MaybeNull] + public FieldAnalysisContext OffsetStorage + { + get => @field.GetExtraData("OffsetStorage"); + set => @field.PutExtraData("OffsetStorage", value); + } + + [MaybeNull] + public MethodAnalysisContext FieldAddressAccessor + { + get => @field.GetExtraData("FieldAddressAccessor"); + set => @field.PutExtraData("FieldAddressAccessor", value); + } + + [MaybeNull] + public int InitializationClassIndex + { + get => @field.GetExtraStruct("InitializationClassIndex", -1); + set => @field.PutExtraStruct("InitializationClassIndex", value); + } + + /// + /// Gets the field info address storage for this field, and instantiate for use in this field's declaring type. + /// + /// The instantiated storage field + public FieldAnalysisContext GetInstantiatedFieldInfoAddressStorage() + { + return field.GetInstantiatedStorageField(field.FieldInfoAddressStorage); + } + + public FieldAnalysisContext GetInstantiatedOffsetStorage() + { + return field.GetInstantiatedStorageField(field.OffsetStorage); + } + + private FieldAnalysisContext GetInstantiatedStorageField(FieldAnalysisContext? storageField) + { + Debug.Assert(storageField is not null); + Debug.Assert(storageField.DeclaringType.GenericParameters.Count == field.DeclaringType.GenericParameters.Count); + + if (storageField.DeclaringType.GenericParameters.Count == 0) + { + return storageField; + } + else + { + return storageField.MakeConcreteGeneric(field.DeclaringType.GenericParameters); + } + } + + public ConcreteGenericFieldAnalysisContext MakeConcreteGeneric(IEnumerable declaringTypeGenericArguments) + { + return new ConcreteGenericFieldAnalysisContext( + field, + field.DeclaringType.MakeGenericInstanceType(declaringTypeGenericArguments) + ); + } + + public FieldAnalysisContext MaybeMakeConcreteGeneric(IEnumerable declaringTypeGenericArguments) + { + if (field.DeclaringType.GenericParameters.Count == 0) + return field; + else + return field.MakeConcreteGeneric(declaringTypeGenericArguments); + } + + public FieldAnalysisContext SelfInstantiateIfNecessary() + { + return field.MaybeMakeConcreteGeneric(field.DeclaringType.GenericParameters); + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/GenericParameterTypeAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/GenericParameterTypeAnalysisContextExtensions.cs new file mode 100644 index 00000000..d88fddcc --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/GenericParameterTypeAnalysisContextExtensions.cs @@ -0,0 +1,36 @@ +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Visitors; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class GenericParameterTypeAnalysisContextExtensions +{ + public static void CopyConstraintsFrom(this GenericParameterTypeAnalysisContext destination, GenericParameterTypeAnalysisContext source, TypeReplacementVisitor visitor) + { + foreach (var constraint in source.ConstraintTypes) + { + destination.ConstraintTypes.Add(visitor.Replace(constraint)); + } + } + + public static void CopyConstraintsFrom(this IReadOnlyList destination, IReadOnlyList source) + { + if (destination.Count != source.Count) + throw new ArgumentException("Source and destination lists must have the same number of elements."); + + if (source.Count == 0) + return; + + var replacements = new Dictionary(source.Count); + for (var i = source.Count - 1; i >= 0; i--) + { + replacements.Add(source[i], destination[i]); + } + + var visitor = new TypeReplacementVisitor(replacements); + for (var i = source.Count - 1; i >= 0; i--) + { + destination[i].CopyConstraintsFrom(source[i], visitor); + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/HasGenericParametersExtensions.cs b/Il2CppInterop.Generator/Extensions/HasGenericParametersExtensions.cs new file mode 100644 index 00000000..1c11843f --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/HasGenericParametersExtensions.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using Cpp2IL.Core.Model.Contexts; +using LibCpp2IL.BinaryStructures; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class HasGenericParametersExtensions +{ + public static void CopyGenericParameters(this HasGenericParameters destination, HasGenericParameters source, bool copyConstraints = false, bool clearVarianceAttributes = false) + { + var type = destination is TypeAnalysisContext ? Il2CppTypeEnum.IL2CPP_TYPE_VAR : Il2CppTypeEnum.IL2CPP_TYPE_MVAR; + foreach (var genericParameter in source.GenericParameters) + { + var attributes = clearVarianceAttributes ? genericParameter.Attributes & ~GenericParameterAttributes.VarianceMask : genericParameter.Attributes; + destination.GenericParameters.Add(new GenericParameterTypeAnalysisContext(genericParameter.Name, destination.GenericParameters.Count, type, attributes, destination)); + } + if (copyConstraints) + { + destination.GenericParameters.CopyConstraintsFrom(source.GenericParameters); + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/HashSetExtensions.cs b/Il2CppInterop.Generator/Extensions/HashSetExtensions.cs new file mode 100644 index 00000000..18682620 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/HashSetExtensions.cs @@ -0,0 +1,12 @@ +namespace Il2CppInterop.Generator.Extensions; + +internal static class HashSetExtensions +{ + public static void AddRange(this HashSet set, IEnumerable values) + { + foreach (var value in values) + { + set.Add(value); + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs b/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs deleted file mode 100644 index 36a4a16c..00000000 --- a/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs +++ /dev/null @@ -1,447 +0,0 @@ -using System.Diagnostics; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Collections; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Extensions; - -public static class ILGeneratorEx -{ - public static void EmitObjectStore(this ILProcessor body, TypeSignature originalType, TypeSignature newType, - TypeRewriteContext enclosingType, int argumentIndex) - { - // input stack: object address, target address - // output: nothing - if (originalType is GenericParameterSignature) - { - EmitObjectStoreGeneric(body, originalType, newType, enclosingType, argumentIndex); - return; - } - - var imports = enclosingType.AssemblyContext.Imports; - - if (originalType.FullName == "System.String") - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Call, imports.IL2CPP_ManagedStringToIl2Cpp.Value); - body.Add(OpCodes.Call, imports.WriteFieldWBarrier); - } - else if (originalType.IsPointerLike()) - { - Debug.Assert(newType.IsPointerLike()); - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Stobj, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Pop); - } - else if (originalType.IsValueType()) - { - var typeSpecifics = enclosingType.AssemblyContext.GlobalContext.JudgeSpecificsByOriginalType(originalType); - if (typeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct) - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Stobj, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Pop); - } - else - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtr.Value); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_unbox.Value); - var classPointerTypeRef = new GenericInstanceTypeSignature(imports.Il2CppClassPointerStore.ToTypeDefOrRef(), imports.Il2CppClassPointerStore.IsValueType(), newType); - var classPointerFieldRef = - ReferenceCreator.CreateFieldReference("NativeClassPtr", imports.Module.IntPtr(), classPointerTypeRef.ToTypeDefOrRef()); - body.Add(OpCodes.Ldsfld, enclosingType.NewType.DeclaringModule!.DefaultImporter.ImportField(classPointerFieldRef)); - body.Add(OpCodes.Ldc_I4_0); - body.Add(OpCodes.Conv_U); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_class_value_size.Value); - body.Add(OpCodes.Cpblk); - body.Add(OpCodes.Pop); - } - } - else - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtr.Value); - body.Add(OpCodes.Call, imports.WriteFieldWBarrier); - } - } - - private static void EmitObjectStoreGeneric(ILProcessor body, TypeSignature originalType, TypeSignature newType, - TypeRewriteContext enclosingType, int argumentIndex) - { - // input stack: object address, target address - // output: nothing - - var imports = enclosingType.AssemblyContext.Imports; - - Debug.Assert(enclosingType.NewType.DeclaringModule is not null); - body.Add(OpCodes.Ldtoken, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Call, enclosingType.NewType.DeclaringModule!.TypeGetTypeFromHandle()); - body.Add(OpCodes.Dup); - body.Add(OpCodes.Callvirt, enclosingType.NewType.DeclaringModule!.TypeGetIsValueType()); - - var finalNop = new CilInstructionLabel(); - var stringNop = new CilInstructionLabel(); - var valueTypeNop = new CilInstructionLabel(); - var storePointerNop = new CilInstructionLabel(); - - body.Add(OpCodes.Brtrue, valueTypeNop); - - body.Add(OpCodes.Callvirt, enclosingType.NewType.DeclaringModule!.TypeGetFullName()); - body.Add(OpCodes.Ldstr, "System.String"); - body.Add(OpCodes.Call, enclosingType.NewType.DeclaringModule!.StringEquals()); - body.Add(OpCodes.Brtrue_S, stringNop); - - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Box, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Isinst, imports.Il2CppObjectBase.ToTypeDefOrRef()); - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtr.Value); - body.Add(OpCodes.Dup); - body.Add(OpCodes.Brfalse_S, storePointerNop); - - body.Add(OpCodes.Dup); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_get_class.Value); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_class_is_valuetype.Value); - body.Add(OpCodes.Brfalse_S, storePointerNop); - - body.Add(OpCodes.Dup); - var tempLocal = new CilLocalVariable(imports.Module.IntPtr()); - body.Owner.LocalVariables.Add(tempLocal); - body.Add(OpCodes.Stloc, tempLocal); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_unbox.Value); - body.Add(OpCodes.Ldloc, tempLocal); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_get_class.Value); - body.Add(OpCodes.Ldc_I4_0); - body.Add(OpCodes.Conv_U); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_class_value_size.Value); - body.Add(OpCodes.Cpblk); - body.Add(OpCodes.Pop); - body.Add(OpCodes.Br_S, finalNop); - - storePointerNop.Instruction = body.Add(OpCodes.Nop); - body.Add(OpCodes.Call, imports.WriteFieldWBarrier); - body.Add(OpCodes.Br_S, finalNop); - - stringNop.Instruction = body.Add(OpCodes.Nop); - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Box, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Isinst, imports.Module.String().ToTypeDefOrRef()); - body.Add(OpCodes.Call, imports.IL2CPP_ManagedStringToIl2Cpp.Value); - body.Add(OpCodes.Call, imports.WriteFieldWBarrier); - body.Add(OpCodes.Br_S, finalNop); - - valueTypeNop.Instruction = body.Add(OpCodes.Nop); - body.Add(OpCodes.Pop); // pop extra typeof(T) - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Stobj, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Pop); - - finalNop.Instruction = body.Add(OpCodes.Nop); - } - - public static void EmitObjectToPointer(this ILProcessor body, TypeSignature originalType, TypeSignature newType, - TypeRewriteContext enclosingType, int argumentIndex, bool valueTypeArgument0IsAPointer, bool allowNullable, - bool unboxNonBlittableType, bool unboxNonBlittableGeneric, out CilLocalVariable? refVariable) - { - // input stack: not used - // output stack: IntPtr to either Il2CppObject or IL2CPP value type - refVariable = null; - - if (originalType is GenericParameterSignature) - { - EmitObjectToPointerGeneric(body, originalType, newType, enclosingType, argumentIndex, - valueTypeArgument0IsAPointer, allowNullable, unboxNonBlittableGeneric); - return; - } - - var imports = enclosingType.AssemblyContext.Imports; - if (originalType is ByReferenceTypeSignature) - { - if (newType.GetElementType().IsValueType()) - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Conv_I); - } - else if (originalType.GetElementType().IsValueType()) - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Ldind_Ref); - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtrNotNull.Value); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_unbox.Value); - } - else - { - var pointerVar = new CilLocalVariable(imports.Module.IntPtr()); - refVariable = pointerVar; - body.Owner.LocalVariables.Add(pointerVar); - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Ldind_Ref); - if (originalType.GetElementType().FullName == "System.String") - body.Add(OpCodes.Call, imports.IL2CPP_ManagedStringToIl2Cpp.Value); - else - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtr.Value); - body.Add(OpCodes.Stloc, pointerVar); - body.Add(OpCodes.Ldloca, pointerVar); - body.Add(OpCodes.Conv_I); - } - } - else if (originalType.IsPointerLike()) - { - Debug.Assert(newType.IsPointerLike()); - body.AddLoadArgument(argumentIndex); - } - else if (originalType.IsValueType()) - { - if (newType.IsValueType()) - { - if (argumentIndex == 0 && valueTypeArgument0IsAPointer) - body.Add(OpCodes.Ldarg_0); - else - body.AddLoadArgumentAddress(argumentIndex); - } - else - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtrNotNull.Value); - if (unboxNonBlittableType) - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_unbox.Value); - } - } - else if (originalType.FullName == "System.String") - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Call, imports.IL2CPP_ManagedStringToIl2Cpp.Value); - } - else - { - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Call, - allowNullable - ? imports.IL2CPP_Il2CppObjectBaseToPtr.Value - : imports.IL2CPP_Il2CppObjectBaseToPtrNotNull.Value); - } - } - - private static void EmitObjectToPointerGeneric(ILProcessor body, TypeSignature originalType, - TypeSignature newType, TypeRewriteContext enclosingType, int argumentIndex, - bool valueTypeArgument0IsAPointer, bool allowNullable, bool unboxNonBlittableType) - { - var imports = enclosingType.AssemblyContext.Imports; - - Debug.Assert(enclosingType.NewType.DeclaringModule is not null); - body.Add(OpCodes.Ldtoken, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Call, enclosingType.NewType.DeclaringModule!.TypeGetTypeFromHandle()); - body.Add(OpCodes.Callvirt, enclosingType.NewType.DeclaringModule!.TypeGetIsValueType()); - - var finalNop = new CilInstructionLabel(); - var valueTypeNop = new CilInstructionLabel(); - var stringNop = new CilInstructionLabel(); - - body.Add(OpCodes.Brtrue, valueTypeNop); - - body.AddLoadArgument(argumentIndex); - body.Add(OpCodes.Box, newType.ToTypeDefOrRef()); - body.Add(OpCodes.Dup); - body.Add(OpCodes.Isinst, imports.Module.String().ToTypeDefOrRef()); - body.Add(OpCodes.Brtrue_S, stringNop); - - body.Add(OpCodes.Isinst, imports.Il2CppObjectBase.ToTypeDefOrRef()); - body.Add(OpCodes.Call, - allowNullable - ? imports.IL2CPP_Il2CppObjectBaseToPtr.Value - : imports.IL2CPP_Il2CppObjectBaseToPtrNotNull.Value); - if (unboxNonBlittableType) - { - body.Add(OpCodes.Dup); - body.Add(OpCodes.Brfalse_S, finalNop); // return null immediately - body.Add(OpCodes.Dup); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_get_class.Value); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_class_is_valuetype.Value); - body.Add(OpCodes.Brfalse_S, finalNop); // return reference types immediately - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_unbox.Value); - } - - body.Add(OpCodes.Br, finalNop); - - stringNop.Instruction = body.Add(OpCodes.Nop); - body.Add(OpCodes.Isinst, imports.Module.String().ToTypeDefOrRef()); - body.Add(OpCodes.Call, imports.IL2CPP_ManagedStringToIl2Cpp.Value); - body.Add(OpCodes.Br_S, finalNop); - - valueTypeNop.Instruction = body.Add(OpCodes.Nop); - body.AddLoadArgumentAddress(argumentIndex); - - finalNop.Instruction = body.Add(OpCodes.Nop); - } - - public static void EmitPointerToObject(this ILProcessor body, TypeSignature originalReturnType, - TypeSignature convertedReturnType, TypeRewriteContext enclosingType, CilLocalVariable pointerVariable, - bool extraDerefForNonValueTypes, bool unboxValueType) - { - // input stack: not used - // output stack: converted result - - if (originalReturnType is GenericParameterSignature) - { - EmitPointerToObjectGeneric(body, originalReturnType, convertedReturnType, enclosingType, pointerVariable, - extraDerefForNonValueTypes, unboxValueType); - return; - } - - var imports = enclosingType.AssemblyContext.Imports; - if (originalReturnType.FullName == "System.Void") - { - // do nothing - } - else if (originalReturnType.IsPointerLike()) - { - Debug.Assert(convertedReturnType.IsPointerLike()); - body.Add(OpCodes.Ldloc, pointerVariable); - } - else if (originalReturnType.IsValueType()) - { - if (convertedReturnType.IsValueType()) - { - body.Add(OpCodes.Ldloc, pointerVariable); - if (unboxValueType) body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_unbox.Value); - body.Add(OpCodes.Ldobj, convertedReturnType.ToTypeDefOrRef()); - } - else - { - if (unboxValueType) - { - body.Add(OpCodes.Ldloc, pointerVariable); - } - else - { - Debug.Assert(enclosingType.NewType.DeclaringModule is not null); - var classPointerTypeRef = new GenericInstanceTypeSignature(imports.Il2CppClassPointerStore.ToTypeDefOrRef(), imports.Il2CppClassPointerStore.IsValueType(), convertedReturnType); - var classPointerFieldRef = - ReferenceCreator.CreateFieldReference("NativeClassPtr", imports.Module.IntPtr(), - classPointerTypeRef.ToTypeDefOrRef()); - body.Add(OpCodes.Ldsfld, enclosingType.NewType.DeclaringModule!.DefaultImporter.ImportField(classPointerFieldRef)); - body.Add(OpCodes.Ldloc, pointerVariable); - body.Add(OpCodes.Call, imports.IL2CPP_il2cpp_value_box.Value); - } - - body.Add(OpCodes.Newobj, - ReferenceCreator.CreateInstanceMethodReference(".ctor", imports.Module.Void(), convertedReturnType.ToTypeDefOrRef(), imports.Module.IntPtr())); - } - } - else if (originalReturnType.FullName == "System.String") - { - body.Add(OpCodes.Ldloc, pointerVariable); - if (extraDerefForNonValueTypes) body.Add(OpCodes.Ldind_I); - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppStringToManaged.Value); - } - else if (originalReturnType is ArrayBaseTypeSignature && originalReturnType.GetElementType() is GenericParameterSignature genericParameterSignature) - { - // Note: - // The method reference parent is constructed relative to the calling method. - // The return type and parameter types are constructed relative to the called method. - body.Add(OpCodes.Ldloc, pointerVariable); - if (extraDerefForNonValueTypes) body.Add(OpCodes.Ldind_I); - var actualReturnType = imports.Module.DefaultImporter.ImportTypeSignature(imports.Il2CppArrayBase.MakeGenericInstanceType(new GenericParameterSignature(GenericParameterType.Type, 0))); - var methodRef = ReferenceCreator.CreateStaticMethodReference("WrapNativeGenericArrayPointer", - actualReturnType, - convertedReturnType.ToTypeDefOrRef(), - imports.Module.IntPtr()); - body.Add(OpCodes.Call, methodRef); - } - else - { - var createPoolObject = new CilInstructionLabel(); - var endNop = new CilInstructionLabel(); - - body.Add(OpCodes.Ldloc, pointerVariable); - if (extraDerefForNonValueTypes) body.Add(OpCodes.Ldind_I); - body.Add(OpCodes.Dup); - body.Add(OpCodes.Brtrue_S, createPoolObject); - body.Add(OpCodes.Pop); - body.Add(OpCodes.Ldnull); - body.Add(OpCodes.Br, endNop); - - createPoolObject.Instruction = body.Add(OpCodes.Call, - imports.Module.DefaultImporter.ImportMethod(imports.Il2CppObjectPool_Get.Value.MakeGenericInstanceMethod(convertedReturnType))); - endNop.Instruction = body.Add(OpCodes.Nop); - } - } - - private static void EmitPointerToObjectGeneric(ILProcessor body, TypeSignature originalReturnType, - TypeSignature newReturnType, - TypeRewriteContext enclosingType, CilLocalVariable pointerVariable, bool extraDerefForNonValueTypes, - bool unboxValueType) - { - var imports = enclosingType.AssemblyContext.Imports; - - body.Add(OpCodes.Ldloc, pointerVariable); - - body.Add(extraDerefForNonValueTypes ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - body.Add(unboxValueType ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - body.Add(OpCodes.Call, - imports.Module.DefaultImporter.ImportMethod(imports.IL2CPP_PointerToValueGeneric.Value.MakeGenericInstanceMethod(newReturnType))); - } - - public static void GenerateBoxMethod(RuntimeAssemblyReferences imports, TypeDefinition targetType, - IFieldDescriptor classHandle, TypeSignature il2CppObjectTypeDef) - { - Debug.Assert(targetType.DeclaringModule is not null); - var method = new MethodDefinition("BoxIl2CppObject", MethodAttributes.Public | MethodAttributes.HideBySig, - MethodSignature.CreateInstance(targetType.DeclaringModule!.DefaultImporter.ImportTypeSignature(il2CppObjectTypeDef))); - targetType.Methods.Add(method); - - method.CilMethodBody = new CilMethodBody(); - var methodBody = method.CilMethodBody.Instructions; - methodBody.Add(OpCodes.Ldsfld, classHandle); - methodBody.Add(OpCodes.Ldarg_0); - methodBody.Add(OpCodes.Call, targetType.DeclaringModule.DefaultImporter.ImportMethod(imports.IL2CPP_il2cpp_value_box.Value)); - - methodBody.Add(OpCodes.Newobj, - new MemberReference(il2CppObjectTypeDef.ToTypeDefOrRef(), ".ctor", MethodSignature.CreateInstance(targetType.DeclaringModule.Void(), targetType.DeclaringModule.IntPtr()))); - - methodBody.Add(OpCodes.Ret); - } - - public static void EmitUpdateRef(this ILProcessor body, Parameter newMethodParameter, int argIndex, - CilLocalVariable paramVariable, RuntimeAssemblyReferences imports) - { - body.AddLoadArgument(argIndex); - body.Add(OpCodes.Ldloc, paramVariable); - if (newMethodParameter.ParameterType.GetElementType().FullName == "System.String") - { - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppStringToManaged.Value); - } - else - { - body.Add(OpCodes.Dup); - var nullbr = new CilInstructionLabel(); - var stnop = new CilInstructionLabel(); - body.Add(OpCodes.Brfalse_S, nullbr); - - if (newMethodParameter.ParameterType.GetElementType() is GenericParameterSignature) - { - body.Add(OpCodes.Ldc_I4_0); - body.Add(OpCodes.Ldc_I4_0); - body.Add(OpCodes.Call, - imports.Module.DefaultImporter.ImportMethod(imports.IL2CPP_PointerToValueGeneric.Value.MakeGenericInstanceMethod(newMethodParameter.ParameterType.GetElementType()))); - } - else - { - body.Add(OpCodes.Newobj, - ReferenceCreator.CreateInstanceMethodReference(".ctor", imports.Module.Void(), newMethodParameter.ParameterType.GetElementType().ToTypeDefOrRef(), imports.Module.IntPtr())); - } - body.Add(OpCodes.Br_S, stnop); - - nullbr.Instruction = body.Add(OpCodes.Pop); - body.Add(OpCodes.Ldnull); - stnop.Instruction = body.Add(OpCodes.Nop); - } - - body.Add(OpCodes.Stind_Ref); - } -} diff --git a/Il2CppInterop.Generator/Extensions/InjectedMethodAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/InjectedMethodAnalysisContextExtensions.cs new file mode 100644 index 00000000..6f536ae0 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/InjectedMethodAnalysisContextExtensions.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class InjectedMethodAnalysisContextExtensions +{ + public static void SetDefaultReturnType(this InjectedMethodAnalysisContext context, TypeAnalysisContext type) + { + GetDefaultReturnType(context) = type; + } + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = $"<{nameof(InjectedMethodAnalysisContext.DefaultReturnType)}>k__BackingField")] + private static extern ref TypeAnalysisContext GetDefaultReturnType(InjectedMethodAnalysisContext context); +} diff --git a/Il2CppInterop.Generator/Extensions/InjectedParameterAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/InjectedParameterAnalysisContextExtensions.cs new file mode 100644 index 00000000..fafe48ba --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/InjectedParameterAnalysisContextExtensions.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class InjectedParameterAnalysisContextExtensions +{ + public static void SetDefaultParameterType(this InjectedParameterAnalysisContext context, TypeAnalysisContext type) + { + GetDefaultParameterType(context) = type; + } + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = $"<{nameof(InjectedParameterAnalysisContext.DefaultParameterType)}>k__BackingField")] + private static extern ref TypeAnalysisContext GetDefaultParameterType(InjectedParameterAnalysisContext context); +} diff --git a/Il2CppInterop.Generator/Extensions/InjectedTypeAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/InjectedTypeAnalysisContextExtensions.cs new file mode 100644 index 00000000..2bad53d4 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/InjectedTypeAnalysisContextExtensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; +using Cpp2IL.Core.Model.Contexts; +using LibCpp2IL.BinaryStructures; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class InjectedTypeAnalysisContextExtensions +{ + public const DynamicallyAccessedMemberTypes AccessedMemberTypes = DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.PublicEvents + | DynamicallyAccessedMemberTypes.PublicMethods + | DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.NonPublicEvents + | DynamicallyAccessedMemberTypes.NonPublicMethods + | DynamicallyAccessedMemberTypes.NonPublicConstructors; + + extension(InjectedTypeAnalysisContext type) + { + public void InjectContentFromSourceType([DynamicallyAccessedMembers(AccessedMemberTypes)] Type sourceType) + { + var appContext = type.AppContext; + + type.SetDefaultBaseType(sourceType.BaseType != null + ? new SystemTypeResolver(type).ResolveOrThrow(sourceType.BaseType) + : null); + + foreach (var fieldInfo in sourceType.GetFields()) + { + if (fieldInfo.DeclaringType != sourceType) + continue; + + var resolver = new SystemTypeResolver(type); + + type.InjectFieldContext( + fieldInfo.Name, + resolver.ResolveOrThrow(fieldInfo.FieldType), + fieldInfo.Attributes); + } + + Dictionary methodMap = new(); + foreach (var method in GetPublicAndProtectedMethods(sourceType)) + { + if (method.DeclaringType != sourceType) + continue; + + var parameterInfoArray = method.GetParameters(); + var parameterNames = parameterInfoArray + .Select(x => x.Name!) + .ToArray(); + var parameterAttributes = parameterInfoArray + .Select(x => x.Attributes) + .ToArray(); + var methodContext = new InjectedMethodAnalysisContext( + type, + method.Name, + appContext.SystemTypes.SystemObjectType, + method.Attributes, + Enumerable.Repeat(appContext.SystemTypes.SystemObjectType, parameterInfoArray.Length).ToArray(), + parameterNames, + parameterAttributes); + if (method is not ConstructorInfo) + { + foreach (var genericParameter in method.GetGenericArguments()) + { + var genericParameterContext = new GenericParameterTypeAnalysisContext(genericParameter.Name, genericParameter.GenericParameterPosition, Il2CppTypeEnum.IL2CPP_TYPE_MVAR, genericParameter.GenericParameterAttributes, methodContext); + methodContext.GenericParameters.Add(genericParameterContext); + } + } + type.Methods.Add(methodContext); + + methodMap.Add(method, methodContext); + + var resolver = new SystemTypeResolver(methodContext); + + var returnType = method switch + { + MethodInfo methodInfo => resolver.ResolveOrThrow(methodInfo.ReturnType), + ConstructorInfo => appContext.SystemTypes.SystemVoidType, + _ => throw new NotSupportedException($"Unsupported method type: {method.GetType()}") + }; + methodContext.SetDefaultReturnType(returnType); + for (var i = 0; i < parameterInfoArray.Length; i++) + { + var parameterType = resolver.ResolveOrThrow(parameterInfoArray[i].ParameterType); + var parameterContext = (InjectedParameterAnalysisContext)methodContext.Parameters[i]; + parameterContext.SetDefaultParameterType(parameterType); + } + } + + foreach (var property in sourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + { + if (property.DeclaringType != sourceType) + continue; + + var getMethod = methodMap.TryGetValue(property.GetMethod); + var setMethod = methodMap.TryGetValue(property.SetMethod); + if (getMethod == null && setMethod == null) + continue; + + var resolver = new SystemTypeResolver(type); + + var propertyType = resolver.ResolveOrThrow(property.PropertyType); + type.InjectPropertyContext( + property.Name, + propertyType, + getMethod, + setMethod, + property.Attributes); + } + + foreach (var eventInfo in sourceType.GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + { + if (eventInfo.DeclaringType != sourceType) + continue; + + var addMethod = methodMap.TryGetValue(eventInfo.AddMethod); + var removeMethod = methodMap.TryGetValue(eventInfo.RemoveMethod); + var raiseMethod = methodMap.TryGetValue(eventInfo.RaiseMethod); + if (addMethod == null && removeMethod == null && raiseMethod == null) + continue; + + var resolver = new SystemTypeResolver(type); + var eventType = resolver.ResolveOrThrow(eventInfo.EventHandlerType!); + type.InjectEventContext( + eventInfo.Name, + eventType, + addMethod, + removeMethod, + raiseMethod, + eventInfo.Attributes); + } + } + + public void SetDefaultBaseType(TypeAnalysisContext? baseType) + { + GetDefaultBaseType(type) = baseType; + } + } + + static IEnumerable GetPublicAndProtectedMethods([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) + { + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + foreach (var constructor in constructors) + { + if (constructor.IsPublic || constructor.IsFamily || constructor.IsFamilyOrAssembly) + { + yield return constructor; + } + } + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + foreach (var method in methods) + { + if (method.IsPublic || method.IsFamily || method.IsFamilyOrAssembly) + { + yield return method; + } + } + } + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = $"<{nameof(InjectedTypeAnalysisContext.DefaultBaseType)}>k__BackingField")] + private static extern ref TypeAnalysisContext? GetDefaultBaseType(InjectedTypeAnalysisContext context); +} diff --git a/Il2CppInterop.Generator/Extensions/InstructionListExtensions.cs b/Il2CppInterop.Generator/Extensions/InstructionListExtensions.cs new file mode 100644 index 00000000..de6f668b --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/InstructionListExtensions.cs @@ -0,0 +1,18 @@ +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class InstructionListExtensions +{ + public static Instruction Add(this List instructions, CilOpCode opCode) + { + return instructions.Add(opCode, null); + } + + public static Instruction Add(this List instructions, CilOpCode opCode, object? operand) + { + var instruction = new Instruction(opCode, operand); + instructions.Add(instruction); + return instruction; + } +} diff --git a/Il2CppInterop.Generator/Extensions/LocalVariableListExtensions.cs b/Il2CppInterop.Generator/Extensions/LocalVariableListExtensions.cs new file mode 100644 index 00000000..68be8ada --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/LocalVariableListExtensions.cs @@ -0,0 +1,14 @@ +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class LocalVariableListExtensions +{ + public static LocalVariable AddNew(this List localVariables, TypeAnalysisContext variableType) + { + var localVariable = new LocalVariable(variableType); + localVariables.Add(localVariable); + return localVariable; + } +} diff --git a/Il2CppInterop.Generator/Extensions/MethodAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/MethodAnalysisContextExtensions.cs new file mode 100644 index 00000000..810b1454 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/MethodAnalysisContextExtensions.cs @@ -0,0 +1,213 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils; +using LibCpp2IL; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class MethodAnalysisContextExtensions +{ + extension(MethodAnalysisContext method) + { + [MaybeNull] + public FieldAnalysisContext MethodInfoField + { + get => method.GetExtraData("MethodInfoField"); + set => method.PutExtraData("MethodInfoField", value); + } + + [MaybeNull] + public FieldAnalysisContext ICallDelegateField + { + get => method.GetExtraData("ICallDelegateField"); + set => method.PutExtraData("ICallDelegateField", value); + } + + public MethodAnalysisContext MostUserFriendlyOverload + { + get => method.GetExtraData("MostUserFriendlyOverload") ?? method; + set => method.PutExtraData("MostUserFriendlyOverload", value); + } + + /// + /// The unsafe invoke method for this method. + /// + [MaybeNull] + public MethodAnalysisContext UnsafeInvokeMethod + { + get => method.GetExtraData("UnsafeInvokeMethod"); + set => method.PutExtraData("UnsafeInvokeMethod", value); + } + + /// + /// The unsafe implementation method for this method. + /// + [MaybeNull] + public MethodAnalysisContext UnsafeImplementationMethod + { + get => method.GetExtraData("UnsafeImplementationMethod"); + set => method.PutExtraData("UnsafeImplementationMethod", value); + } + + /// + /// The interface method that should be used instead when emitting calls to this method during unstripping. + /// + [MaybeNull] + public MethodAnalysisContext InterfaceRedirectMethod + { + get => method.GetExtraData("InterfaceRedirectMethod"); + set => method.PutExtraData("InterfaceRedirectMethod", value); + } + + [MaybeNull] + public int InitializationClassIndex + { + get => method.GetExtraStruct("InitializationClassIndex", -1); + set => method.PutExtraStruct("InitializationClassIndex", value); + } + + public MethodAnalysisContext? UltimateBaseMethod + { + get + { + var currentBaseMethod = method.BaseMethodFixed; + if (currentBaseMethod is null) + return null; + MethodAnalysisContext? nextBaseMethod; + while (true) + { + nextBaseMethod = currentBaseMethod.BaseMethodFixed; + if (nextBaseMethod is not null) + { + currentBaseMethod = nextBaseMethod; + } + else + { + break; + } + } + return currentBaseMethod; + } + } + + public MethodAnalysisContext? BaseMethodFixed + { + get + { + if (method.Definition == null) + return null; + + var vtable = method.DeclaringType?.Definition?.VTable; + if (vtable == null) + return null; + + for (var i = 0; i < vtable.Length; ++i) + { + var vtableEntry = vtable[i]; + if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef } || vtableEntry.AsMethod() != method.Definition) + continue; + + if (IsInterfaceSlot(method, i)) + { + continue; + } + + var baseType = method.DeclaringType?.DefaultBaseType; + while (baseType is not null) + { + if (TryGetMethodForSlot(baseType, i, out var method2)) + { + return method2; + } + baseType = baseType.DefaultBaseType; + } + } + return null; + } + } + + public bool IsInstanceConstructor => method.Name == ".ctor"; + public bool IsStaticConstructor => method.Name == ".cctor"; + public bool IsConstructor => method.IsInstanceConstructor || method.IsStaticConstructor; + public bool IsPublic => (method.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public; + public bool IsSpecialName => (method.Attributes & MethodAttributes.SpecialName) != default; + public bool IsFinal => (method.Attributes & MethodAttributes.Final) != default; + + public bool ImplementsAnInterfaceMethod => method.Overrides.Count > 0; + + public ushort Slot => method.Definition?.slot ?? ushort.MaxValue; + + public FieldAnalysisContext GetInstantiatedMethodInfoField() + { + var methodInfoField = method.MethodInfoField; + Debug.Assert(methodInfoField is not null); + Debug.Assert(method.DeclaringType is not null); + + IReadOnlyList methodInfoGenericArguments = [.. method.DeclaringType.GenericParameters, .. method.GenericParameters]; + if (methodInfoGenericArguments.Count == 0) + { + return methodInfoField; + } + else + { + return methodInfoField.MakeConcreteGeneric(methodInfoGenericArguments); + } + } + + public ConcreteGenericMethodAnalysisContext MakeConcreteGeneric(IEnumerable typeArguments, IEnumerable methodArguments) + { + return new ConcreteGenericMethodAnalysisContext(method, typeArguments, methodArguments); + } + + public MethodAnalysisContext MaybeMakeConcreteGeneric(IReadOnlyCollection typeArguments, IReadOnlyCollection methodArguments) + { + if (typeArguments.Count == 0 && methodArguments.Count == 0) + return method; + return method.MakeConcreteGeneric(typeArguments, methodArguments); + } + } + + private static bool IsInterfaceSlot(MethodAnalysisContext method, int slot) + { + // Interface inheritance + foreach (var interfaceOffset in method.DeclaringType!.Definition!.InterfaceOffsets) + { + if (slot >= interfaceOffset.offset) + { + var interfaceTypeContext = interfaceOffset.Type.ToContext(method.CustomAttributeAssembly); + if (interfaceTypeContext != null && TryGetMethodForSlot(interfaceTypeContext, slot - interfaceOffset.offset, out _)) + { + return true; + } + } + } + return false; + } + + private static bool TryGetMethodForSlot(TypeAnalysisContext declaringType, int slot, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType) + { + var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot); + if (genericMethod is not null) + { + method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments, []); + return true; + } + } + else + { + var baseMethod = declaringType.Methods.FirstOrDefault(m => m.Slot == slot); + if (baseMethod is not null) + { + method = baseMethod; + return true; + } + } + + method = null; + return false; + } +} diff --git a/Il2CppInterop.Generator/Extensions/ParameterDefinitionEx.cs b/Il2CppInterop.Generator/Extensions/ParameterDefinitionEx.cs deleted file mode 100644 index a32f1719..00000000 --- a/Il2CppInterop.Generator/Extensions/ParameterDefinitionEx.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AsmResolver.DotNet.Collections; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.Extensions; - -internal static class ParameterDefinitionEx -{ - public static bool IsParamsArray(this Parameter self) - { - return self.ParameterType is SzArrayTypeSignature && (self.Definition?.CustomAttributes.Any(attribute => - attribute.Constructor?.DeclaringType?.FullName == typeof(ParamArrayAttribute).FullName) ?? false); - } -} diff --git a/Il2CppInterop.Generator/Extensions/PropertyAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/PropertyAnalysisContextExtensions.cs new file mode 100644 index 00000000..6b5ee065 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/PropertyAnalysisContextExtensions.cs @@ -0,0 +1,17 @@ +using System.Diagnostics.CodeAnalysis; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class PropertyAnalysisContextExtensions +{ + extension(PropertyAnalysisContext property) + { + [MaybeNull] + public FieldAnalysisContext OriginalField + { + get => property.GetExtraData("OriginalField"); + set => property.PutExtraData("OriginalField", value); + } + } +} diff --git a/Il2CppInterop.Generator/Extensions/StringEx.cs b/Il2CppInterop.Generator/Extensions/StringEx.cs deleted file mode 100644 index 39769153..00000000 --- a/Il2CppInterop.Generator/Extensions/StringEx.cs +++ /dev/null @@ -1,221 +0,0 @@ -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Text; -using AsmResolver; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.Extensions; - -public static class StringEx -{ - public static bool NameShouldBePrefixed(this string str, GeneratorOptions options) - { - if (options.Il2CppPrefixMode == GeneratorOptions.PrefixMode.OptIn) - { - foreach (var prefix in options.NamespacesAndAssembliesToPrefix) - if (str.StartsWith(prefix, StringComparison.Ordinal)) - return true; - return false; - } - else - { - foreach (var prefix in options.NamespacesAndAssembliesToNotPrefix) - if (str.StartsWith(prefix, StringComparison.Ordinal)) - return false; - - return true; - } - } - - public static bool NameShouldBePrefixed(this Utf8String? str, GeneratorOptions options) - { - return NameShouldBePrefixed(str?.Value ?? "", options); - } - - public static string UnSystemify(this string str, GeneratorOptions options) - { - const string Il2CppPrefix = "Il2Cpp"; - return str.NameShouldBePrefixed(options) ? Il2CppPrefix + str : str; - } - - public static string UnSystemify(this Utf8String? str, GeneratorOptions options) - { - return UnSystemify(str?.Value ?? "", options); - } - - public static string MakeValidInSource(this string str) - { - if (string.IsNullOrEmpty(str)) - return ""; - - char[]? chars = null; - for (var i = 0; i < str.Length; i++) - { - var it = str[i]; - if (IsValidInSource(it)) - continue; - - chars ??= str.ToCharArray(); - chars[i] = '_'; - } - - var result = chars is null ? str : new string(chars); - return char.IsDigit(result[0]) ? "_" + result : result; - } - - private static bool IsValidInSource(char c) => char.IsDigit(c) || (c is >= 'a' and <= 'z') || (c is >= 'A' and <= 'Z') || c == '_' || c == '`'; - - public static Utf8String MakeValidInSource(this Utf8String? str) - { - if (Utf8String.IsNullOrEmpty(str)) - return Utf8String.Empty; - - ReadOnlySpan data = str.GetBytesUnsafe(); - - var length = data.Length; - byte[]? rentedArray = null; - Span rentedArraySpan = default; - - if (char.IsDigit((char)data[0])) - { - length++; - rentedArray = ArrayPool.Shared.Rent(length); - rentedArray[0] = (byte)'_'; - rentedArraySpan = rentedArray.AsSpan(1); - data.CopyTo(rentedArraySpan); - } - - for (var i = 0; i < data.Length; i++) - { - if (IsValidInSource((char)data[i])) - continue; - - if (rentedArray is null) - { - rentedArray = ArrayPool.Shared.Rent(length); - rentedArraySpan = rentedArray.AsSpan(); - data.CopyTo(rentedArraySpan); - } - rentedArraySpan[i] = (byte)'_'; - } - - if (rentedArray is not null) - { - var result = new Utf8String(rentedArray, 0, length); - ArrayPool.Shared.Return(rentedArray); - return result; - } - else - { - return str; - } - } - - public static bool IsInvalidInSource([NotNullWhen(true)] this string? str) - { - if (str is null) - return false; - - for (var i = 0; i < str.Length; i++) - { - var it = str[i]; - if (!char.IsDigit(it) && !((it >= 'a' && it <= 'z') || (it >= 'A' && it <= 'Z')) && it != '_' && - it != '`') return true; - } - - return false; - } - - public static bool IsInvalidInSource([NotNullWhen(true)] this Utf8String? str) - { - return IsInvalidInSource(str?.Value); - } - - public static bool IsObfuscated([NotNullWhen(true)] this string? str, GeneratorOptions options) - { - if (str is null) - return false; - if (options.ObfuscatedNamesRegex != null) - return options.ObfuscatedNamesRegex.IsMatch(str); - - foreach (var it in str) - if (!char.IsDigit(it) && !((it >= 'a' && it <= 'z') || (it >= 'A' && it <= 'Z')) && it != '_' && - it != '`' && it != '.' && it != '<' && it != '>') - return true; - - return false; - } - - public static bool IsObfuscated(this Utf8String? str, GeneratorOptions options) - { - return IsObfuscated(str?.Value ?? "", options); - } - - public static ulong StableHash(this string str) - { - ulong hash = 0; - for (var i = 0; i < str.Length; i++) - hash = hash * 37 + str[i]; - - return hash; - } - - public static ulong StableHash(this Utf8String? str) - { - return StableHash(str?.Value ?? ""); - } - - public static bool StartsWith([NotNullWhen(true)] this Utf8String? str, string value) - { - return str is not null && str.Value.StartsWith(value, StringComparison.Ordinal); - } - - /// - /// Construct an unmangled name for a type signature. - /// - /// - /// The declaring type to use for resolving generic type parameter names. - /// The declaring method to use for resolving generic method parameter names. - /// - public static string GetUnmangledName(this TypeSignature typeRef, TypeDefinition? declaringType = null, MethodDefinition? declaringMethod = null) - { - var builder = new StringBuilder(); - if (typeRef is GenericInstanceTypeSignature genericInstance) - { - builder.Append(genericInstance.GenericType.ToTypeSignature().GetUnmangledName(declaringType, declaringMethod)); - foreach (var genericArgument in genericInstance.TypeArguments) - { - builder.Append("_"); - builder.Append(genericArgument.GetUnmangledName(declaringType, declaringMethod)); - } - } - else if (typeRef is ByReferenceTypeSignature byRef) - { - builder.Append("byref_"); - builder.Append(byRef.BaseType.GetUnmangledName(declaringType, declaringMethod)); - } - else if (typeRef is PointerTypeSignature pointer) - { - builder.Append("ptr_"); - builder.Append(pointer.BaseType.GetUnmangledName(declaringType, declaringMethod)); - } - else if (typeRef is GenericParameterSignature genericParameter) - { - if (genericParameter.ParameterType == GenericParameterType.Type) - builder.Append(declaringType!.GenericParameters[genericParameter.Index].Name.MakeValidInSource()); - else - builder.Append(declaringMethod!.GenericParameters[genericParameter.Index].Name.MakeValidInSource()); - } - else - { - if (typeRef.Namespace == "Il2CppInterop.Runtime" && (typeRef.Name?.StartsWith("Il2Cpp") ?? false) && - typeRef.Name.Contains("Array")) - builder.Append("ArrayOf"); - else - builder.Append(typeRef.Name?.Replace('`', '_')); - } - - return builder.ToString(); - } -} diff --git a/Il2CppInterop.Generator/Extensions/StringExtensions.cs b/Il2CppInterop.Generator/Extensions/StringExtensions.cs new file mode 100644 index 00000000..7a923b59 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/StringExtensions.cs @@ -0,0 +1,24 @@ +using System.Buffers; + +namespace Il2CppInterop.Generator.Extensions; + +public static class StringExtensions +{ + public static string MakeValidCSharpName(this string name) + { + ArgumentException.ThrowIfNullOrEmpty(name); + var array = ArrayPool.Shared.Rent(name.Length + 1); + array[0] = '_'; + name.AsSpan().CopyTo(array.AsSpan(1)); + for (var i = name.Length; i > 0; i--) + { + if (!char.IsLetterOrDigit(array[i])) + { + array[i] = '_'; + } + } + var result = char.IsDigit(array[1]) ? new string(array.AsSpan(0, name.Length + 1)) : new string(array.AsSpan(1, name.Length)); + ArrayPool.Shared.Return(array); + return result; + } +} diff --git a/Il2CppInterop.Generator/Extensions/TypeAnalysisContextExtensions.cs b/Il2CppInterop.Generator/Extensions/TypeAnalysisContextExtensions.cs new file mode 100644 index 00000000..2dfe2491 --- /dev/null +++ b/Il2CppInterop.Generator/Extensions/TypeAnalysisContextExtensions.cs @@ -0,0 +1,335 @@ +using System.Diagnostics.CodeAnalysis; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator.Extensions; + +internal static class TypeAnalysisContextExtensions +{ + extension(TypeAnalysisContext type) + { + public bool IsModuleType => type.DeclaringType is null && string.IsNullOrEmpty(type.DefaultNamespace) && type.DefaultName == ""; + public bool IsPrivateImplementationDetailsType => type.DeclaringType is null && string.IsNullOrEmpty(type.DefaultNamespace) && type.DefaultName == ""; + public bool HasGenericParameters => type.GenericParameters.Count > 0; + public bool IsIl2CppPrimitive + { + get + { + if (type is ReferencedTypeAnalysisContext) + return false; + + if (type.Namespace != "Il2CppSystem") + return false; + + if (type.DeclaringAssembly.Name != "Il2Cppmscorlib") + return false; + + return type.Name is + "Boolean" or + "Byte" or + "SByte" or + "Int16" or + "UInt16" or + "Int32" or + "UInt32" or + "Int64" or + "UInt64" or + "Single" or + "Double" or + "Char" or + "IntPtr" or + "UIntPtr" or + "Void"; + } + } + + public bool IsIl2CppDelegate => type is { BaseType: { Namespace: "Il2CppSystem", Name: "MulticastDelegate" } } or GenericInstanceTypeAnalysisContext { GenericType.BaseType: { Namespace: "Il2CppSystem", Name: "MulticastDelegate" } }; + + /// + /// The fields, methods, properties, and events of this type. + /// + public IEnumerable Members + { + get + { + return ((IEnumerable)type.Fields).Concat(type.Methods).Concat(type.Properties).Concat(type.Events); + } + } + + public string CSharpName + { + get + { + if (GenericTypeName.TryMatch(type.Name, out var result, out _)) + { + return result; + } + return type.Name; + } + } + + [MaybeNull] + public MethodAnalysisContext PointerConstructor + { + get => type.GetExtraData("PointerConstructor"); + set => type.PutExtraData("PointerConstructor", value); + } + + [MaybeNull] + public TypeAnalysisContext InitializationType + { + get => type.GetExtraData("InitializationType"); + set => type.PutExtraData("InitializationType", value); + } + + [MaybeNull] + public FieldAnalysisContext SizeStorage + { + get => type.GetExtraData("SizeStorage"); + set => type.PutExtraData("SizeStorage", value); + } + + [MaybeNull] + public TypeAnalysisContext EnumIl2CppUnderlyingType + { + get => type.GetExtraData("EnumIl2CppUnderlyingType"); + set => type.PutExtraData("EnumIl2CppUnderlyingType", value); + } + + [MaybeNull] + public TypeAnalysisContext EnumMonoUnderlyingType + { + get => type.GetExtraData("EnumMonoUnderlyingType"); + set => type.PutExtraData("EnumMonoUnderlyingType", value); + } + + [MaybeNull] + public InjectedTypeAnalysisContext SystemExceptionType + { + get => type.GetExtraData("SystemExceptionType"); + set => type.PutExtraData("SystemExceptionType", value); + } + + [MaybeNull] + public List StaticConstructorInstructions + { + get => type.GetExtraData>("StaticConstructorInstructions"); + set => type.PutExtraData("StaticConstructorInstructions", value); + } + + [MaybeNull] + public TypeAnalysisContext ManagedDelegateType + { + get => type.GetExtraData("ManagedDelegateType"); + set => type.PutExtraData("ManagedDelegateType", value); + } + + public KnownTypeCode KnownType + { + get => type.GetExtraStruct("KnownType", KnownTypeCode.None); + set => type.PutExtraStruct("KnownType", value); + } + + public List GetOrCreateStaticConstructorInstructions() + { + var instructions = type.StaticConstructorInstructions; + if (instructions is null) + { + instructions = []; + type.StaticConstructorInstructions = instructions; + } + return instructions; + } + + public MethodAnalysisContext? TryGetMethodByName(string name) + { + for (var i = type.Methods.Count - 1; i >= 0; i--) + { + var method = type.Methods[i]; + if (method.Name == name) + { + return method; + } + } + return null; + } + + public MethodAnalysisContext GetMethodByName(string name) + { + return type.TryGetMethodByName(name) ?? throw new Exception($"Method {name} not found in type {type.Name}"); + } + + public FieldAnalysisContext GetFieldByName(string? name) + { + return type.TryGetFieldByName(name) ?? throw new Exception($"Field {name} not found in type {type.Name}"); + } + + public FieldAnalysisContext? TryGetFieldByName(string? name) + { + for (var i = type.Fields.Count - 1; i >= 0; i--) + { + var field = type.Fields[i]; + if (field.Name == name) + { + return field; + } + } + return null; + } + + public bool TryGetFieldByName(string? name, [NotNullWhen(true)] out FieldAnalysisContext? field) + { + field = type.TryGetFieldByName(name); + return field is not null; + } + + public PropertyAnalysisContext GetPropertyByName(string? name) + { + return type.TryGetPropertyByName(name) ?? throw new Exception($"Property {name} not found in type {type.Name}"); + } + + public PropertyAnalysisContext? TryGetPropertyByName(string? name) + { + for (var i = type.Properties.Count - 1; i >= 0; i--) + { + var property = type.Properties[i]; + if (property.Name == name) + { + return property; + } + } + return null; + } + + public bool TryGetPropertyByName(string? name, [NotNullWhen(true)] out PropertyAnalysisContext? property) + { + property = type.TryGetPropertyByName(name); + return property is not null; + } + + public bool TryGetMethodInSlot(int slot, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + if (type is GenericInstanceTypeAnalysisContext genericInstanceType) + { + var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot); + if (genericMethod is not null) + { + method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments, []); + return true; + } + } + else + { + var baseMethod = type.Methods.FirstOrDefault(m => m.Slot == slot); + if (baseMethod is not null) + { + method = baseMethod; + return true; + } + } + + method = null; + return false; + } + + public MethodAnalysisContext GetImplicitConversionFrom(TypeAnalysisContext sourceType) + { + return type.TryGetImplicitConversionFrom(sourceType, out var method) + ? method + : throw new Exception($"No implicit conversion from {sourceType.Name} to {type.Name} found."); + } + + public MethodAnalysisContext GetImplicitConversionTo(TypeAnalysisContext targetType) + { + return type.TryGetImplicitConversionTo(targetType, out var method) + ? method + : throw new Exception($"No implicit conversion from {type.Name} to {targetType.Name} found."); + } + + public MethodAnalysisContext GetExplicitConversionFrom(TypeAnalysisContext sourceType) + { + return type.TryGetExplicitConversionFrom(sourceType, out var method) + ? method + : throw new Exception($"No explicit conversion from {sourceType.Name} to {type.Name} found."); + } + + public MethodAnalysisContext GetExplicitConversionTo(TypeAnalysisContext targetType) + { + return type.TryGetExplicitConversionTo(targetType, out var method) + ? method + : throw new Exception($"No explicit conversion from {type.Name} to {targetType.Name} found."); + } + + public bool TryGetImplicitConversionFrom(TypeAnalysisContext sourceType, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + method = GetConversion("op_Implicit", type, sourceType, type.SelfInstantiateIfGeneric()); + return method is not null; + } + + public bool TryGetImplicitConversionTo(TypeAnalysisContext targetType, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + method = GetConversion("op_Implicit", type, type.SelfInstantiateIfGeneric(), targetType); + return method is not null; + } + + public bool TryGetExplicitConversionFrom(TypeAnalysisContext sourceType, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + method = GetConversion("op_Explicit", type, sourceType, type.SelfInstantiateIfGeneric()); + return method is not null; + } + + public bool TryGetExplicitConversionTo(TypeAnalysisContext targetType, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + method = GetConversion("op_Explicit", type, type.SelfInstantiateIfGeneric(), targetType); + return method is not null; + } + + public bool TryGetConversionFrom(TypeAnalysisContext sourceType, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + return type.TryGetImplicitConversionFrom(sourceType, out method) || type.TryGetExplicitConversionFrom(sourceType, out method); + } + + public bool TryGetConversionTo(TypeAnalysisContext targetType, [NotNullWhen(true)] out MethodAnalysisContext? method) + { + return type.TryGetImplicitConversionTo(targetType, out method) || type.TryGetExplicitConversionTo(targetType, out method); + } + + public TypeAnalysisContext MaybeMakeGenericInstanceType(IReadOnlyCollection genericArguments) + { + if (type.GenericParameters.Count == 0) + { + return type; + } + else + { + return type.MakeGenericInstanceType(genericArguments); + } + } + + public TypeAnalysisContext SelfInstantiateIfGeneric() => type.MaybeMakeGenericInstanceType(type.GenericParameters); + + public bool ImplementsInterface(TypeAnalysisContext interfaceType) + { + foreach (var implementedInterface in type.InterfaceContexts) + { + if (TypeAnalysisContextEqualityComparer.Instance.Equals(implementedInterface, interfaceType)) + { + return true; + } + } + return false; + } + } + + private static MethodAnalysisContext? GetConversion([ConstantExpected] string name, TypeAnalysisContext declaringType, TypeAnalysisContext sourceType, TypeAnalysisContext targetType) + { + return declaringType.Methods.FirstOrDefault(m => + { + return m.Name == name + && m.IsStatic + && m.Parameters.Count == 1 + && TypeAnalysisContextEqualityComparer.Instance.Equals(m.ReturnType, targetType) + && TypeAnalysisContextEqualityComparer.Instance.Equals(m.Parameters[0].ParameterType, sourceType); + }); + } +} diff --git a/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs b/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs deleted file mode 100644 index 1ce59c58..00000000 --- a/Il2CppInterop.Generator/Extensions/TypeReferenceEx.cs +++ /dev/null @@ -1,58 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.Extensions; - -public static class TypeReferenceEx -{ - public static bool UnmangledNamesMatch(this TypeSignature typeRefA, TypeSignature typeRefB) - { - var aIsDefOrRef = typeRefA.GetType() == typeof(TypeDefOrRefSignature); - var bIsDefOrRef = typeRefB.GetType() == typeof(TypeDefOrRefSignature); - if (!(aIsDefOrRef && bIsDefOrRef) && typeRefA.GetType() != typeRefB.GetType()) - return false; - - switch (typeRefA) - { - case PointerTypeSignature pointer: - return pointer.BaseType.UnmangledNamesMatch(((PointerTypeSignature)typeRefB).BaseType); - case ByReferenceTypeSignature byRef: - return byRef.BaseType.UnmangledNamesMatch(((ByReferenceTypeSignature)typeRefB).BaseType); - case ArrayBaseTypeSignature array: - return array.BaseType.UnmangledNamesMatch(((ArrayBaseTypeSignature)typeRefB).BaseType); - case GenericInstanceTypeSignature genericInstance: - { - var elementA = genericInstance.GenericType.ToTypeSignature(); - var genericInstanceB = (GenericInstanceTypeSignature)typeRefB; - var elementB = genericInstanceB.GenericType.ToTypeSignature(); - if (!elementA.UnmangledNamesMatch(elementB)) - return false; - if (genericInstance.TypeArguments.Count != genericInstanceB.TypeArguments.Count) - return false; - - for (var i = 0; i < genericInstance.TypeArguments.Count; i++) - if (!genericInstance.TypeArguments[i].UnmangledNamesMatch(genericInstanceB.TypeArguments[i])) - return false; - - return true; - } - default: - return typeRefA.Name == typeRefB.Name; - } - } - - public static string? GetNamespacePrefix(this ITypeDefOrRef type) - { - if (type.DeclaringType is not null) - return $"{GetNamespacePrefix(type.DeclaringType)}.{type.DeclaringType.Name}"; - - return type.Namespace; - } - - // A temporary fix for AsmResolver returning IsValueType true for System.Enum - // See https://github.com/BepInEx/Il2CppInterop/issues/211 for the discussion - public static bool IsValueType(this ITypeDescriptor type) - { - return type.IsValueType && type.FullName != "System.Enum"; - } -} diff --git a/Il2CppInterop.Generator/Extensions/WriterEx.cs b/Il2CppInterop.Generator/Extensions/WriterEx.cs deleted file mode 100644 index 1c31abda..00000000 --- a/Il2CppInterop.Generator/Extensions/WriterEx.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Il2CppInterop.Generator.Extensions; - -public static class WriterEx -{ - [ThreadStatic] private static byte[]? ourBuffer; - - public static unsafe void Write(this BinaryWriter writer, T value) where T : unmanaged - { - var structSize = Marshal.SizeOf(); - - if (ourBuffer == null || ourBuffer.Length < structSize) - ourBuffer = new byte[structSize]; - - fixed (byte* bytes = ourBuffer) - { - *(T*)bytes = value; - } - - writer.Write(ourBuffer, 0, structSize); - } -} diff --git a/Il2CppInterop.Generator/FieldAccessorProcessingLayer.cs b/Il2CppInterop.Generator/FieldAccessorProcessingLayer.cs new file mode 100644 index 00000000..e9c288de --- /dev/null +++ b/Il2CppInterop.Generator/FieldAccessorProcessingLayer.cs @@ -0,0 +1,299 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Model.CustomAttributes; +using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.InteropTypes; + +namespace Il2CppInterop.Generator; + +public class FieldAccessorProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Field Accessor Processor"; + public override string Id => "field_accessor_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var fieldAccessHelper = appContext.ResolveTypeOrThrow(typeof(FieldAccess)); + var getStaticFieldValue = fieldAccessHelper.GetMethodByName(nameof(FieldAccess.GetStaticFieldValue)); + var setStaticFieldValue = fieldAccessHelper.GetMethodByName(nameof(FieldAccess.SetStaticFieldValue)); + var getInstanceFieldValue = fieldAccessHelper.GetMethodByName(nameof(FieldAccess.GetInstanceFieldValue)); + var setInstanceFieldValue_WriteBarrier = fieldAccessHelper.GetMethodByName(nameof(FieldAccess.SetInstanceFieldValue_WriteBarrier)); + var setInstanceFieldValue_Pointer = fieldAccessHelper.GetMethodByName(nameof(FieldAccess.SetInstanceFieldValue_Pointer)); + + var setInstanceFieldValue = appContext.Binary.GetExportedFunctions().Any(pair => pair.Key == "il2cpp_gc_wbarrier_set_field") + ? setInstanceFieldValue_WriteBarrier + : setInstanceFieldValue_Pointer; + + var il2CppFieldAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppFieldAttribute)); + var il2CppFieldAttributeConstructor = il2CppFieldAttribute.GetMethodByName(".ctor"); + var il2CppFieldAttributeIndex = il2CppFieldAttribute.GetPropertyByName(nameof(Il2CppFieldAttribute.Index)); + + var il2CppMemberAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppMemberAttribute)); + var il2CppMemberAttributeName = il2CppMemberAttribute.GetPropertyByName(nameof(Il2CppMemberAttribute.Name)); + + var byReference = appContext.ResolveTypeOrThrow(typeof(ByReference<>)); + var byReference_Constructor = byReference.GetMethodByName(".ctor"); + var byReferenceStatic = appContext.ResolveTypeOrThrow(typeof(ByReference)); + var byReferenceStatic_GetReferenceAtOffset = byReferenceStatic.GetMethodByName(nameof(ByReference.GetReferenceAtOffset)); + + var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + var il2CppSystemObject_get_Pointer = il2CppSystemObject.GetMethodByName($"get_{nameof(Object.Pointer)}"); + + var voidPointer = appContext.SystemTypes.SystemVoidType.MakePointerType(); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + var instantiatedType = type.SelfInstantiateIfGeneric(); + var isValueType = type.IsValueType; + HashSet existingNames = [type.CSharpName, .. type.Members.Select(m => m.Name)]; + + for (var i = type.Fields.Count - 1; i >= 0; i--) + { + var field = type.Fields[i]; + if (field.ConstantValue is not null) + { + Debug.Assert(field.IsStatic); + continue; // Skip fields with constant values, as they are not suitable for property conversion. + } + + if (field.IsInjected) + continue; + + if (!field.IsStatic && !type.IsIl2CppPrimitive) + { + var name = GetNonConflictingName($"GetFieldAddress_{field.Name}", existingNames); + var parameterType = isValueType ? byReference.MakeGenericInstanceType([instantiatedType]) : instantiatedType; + var getFieldAddress = new InjectedMethodAnalysisContext( + type, + name, + byReference.MakeGenericInstanceType([field.FieldType]), + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + [parameterType]) + { + IsInjected = true, + }; + type.Methods.Add(getFieldAddress); + + if (isValueType) + { + getFieldAddress.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg, getFieldAddress.Parameters[0]), + new Instruction(CilOpCodes.Ldsfld, field.GetInstantiatedOffsetStorage()), + new Instruction(CilOpCodes.Call, byReferenceStatic_GetReferenceAtOffset.MakeGenericInstanceMethod(instantiatedType, field.FieldType)), + new Instruction(CilOpCodes.Ret) + ], + }); + } + else + { + getFieldAddress.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg, getFieldAddress.Parameters[0]), + new Instruction(CilOpCodes.Callvirt, il2CppSystemObject_get_Pointer), + new Instruction(CilOpCodes.Ldsfld, field.GetInstantiatedOffsetStorage()), + new Instruction(CilOpCodes.Conv_I), + new Instruction(CilOpCodes.Add), + new Instruction(CilOpCodes.Newobj, byReference_Constructor.MakeConcreteGeneric([field.FieldType], [])), + new Instruction(CilOpCodes.Ret) + ], + }); + } + + field.FieldAddressAccessor = getFieldAddress; + } + + // Check for ByReference<> or Pointer<> in value types because they can break the runtime. + // https://github.com/dotnet/runtime/issues/121556 + var isPointerOrByRefInValueType = isValueType && !field.IsStatic && field.FieldType is GenericInstanceTypeAnalysisContext + { + GenericType.Namespace: "Il2CppInterop.Runtime.InteropTypes", + GenericType.Name: $"{nameof(ByReference<>)}`1" or $"{nameof(Pointer<>)}`1" + }; + + if (isValueType && !field.IsStatic && !isPointerOrByRefInValueType) + { + // Instance fields in value types do not get converted into properties. + + var attribute = new AnalyzedCustomAttribute(il2CppFieldAttributeConstructor); + if (field.Name != field.DefaultName) + { + var parameter = new CustomAttributePrimitiveParameter(field.DefaultName, attribute, CustomAttributeParameterKind.Property, attribute.Properties.Count); + attribute.Properties.Add(new CustomAttributeProperty(il2CppMemberAttributeName, parameter)); + } + var index = field.InitializationClassIndex; + if (index >= 0) + { + var parameter = new CustomAttributePrimitiveParameter(index, attribute, CustomAttributeParameterKind.Property, attribute.Properties.Count); + attribute.Properties.Add(new CustomAttributeProperty(il2CppFieldAttributeIndex, parameter)); + } + field.CustomAttributes ??= new(1); + field.CustomAttributes.Add(attribute); + + continue; + } + + var methodAttributes = field.IsStatic + ? MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Static + : MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName; + + var getMethod = new InjectedMethodAnalysisContext( + field.DeclaringType, + "get_" + field.Name, + field.FieldType, + methodAttributes, + []) + { + IsInjected = true, + }; + field.DeclaringType.Methods.Add(getMethod); + + // get accessor body + { + var instructions = new List(); + if (field.IsStatic) + { + instructions.Add(CilOpCodes.Ldsfld, field.GetInstantiatedFieldInfoAddressStorage()); + instructions.Add(CilOpCodes.Call, getStaticFieldValue.MakeGenericInstanceMethod([field.FieldType])); + instructions.Add(CilOpCodes.Ret); + } + else if (isPointerOrByRefInValueType) + { + var fieldType = (GenericInstanceTypeAnalysisContext)field.FieldType; + var genericArgument = fieldType.GenericArguments[0]; + var uninstantiatedConversion = fieldType.GenericType.GetExplicitConversionFrom(voidPointer); + instructions.Add(CilOpCodes.Ldarg, This.Instance); + instructions.Add(CilOpCodes.Ldfld, field.SelfInstantiateIfNecessary()); + instructions.Add(CilOpCodes.Call, uninstantiatedConversion.MakeConcreteGenericMethod(fieldType.GenericArguments, [])); + instructions.Add(CilOpCodes.Ret); + } + else + { + instructions.Add(CilOpCodes.Ldarg, This.Instance); + instructions.Add(CilOpCodes.Ldsfld, field.GetInstantiatedOffsetStorage()); + instructions.Add(CilOpCodes.Call, getInstanceFieldValue.MakeGenericInstanceMethod([field.FieldType])); + instructions.Add(CilOpCodes.Ret); + } + + getMethod.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + }); + } + + var setMethod = new InjectedMethodAnalysisContext( + field.DeclaringType, + "set_" + field.Name, + field.AppContext.SystemTypes.SystemVoidType, + methodAttributes, + [field.FieldType]) + { + IsInjected = true, + }; + field.DeclaringType.Methods.Add(setMethod); + + // set accessor body + { + var instructions = new List(); + if (field.IsStatic) + { + instructions.Add(CilOpCodes.Ldsfld, field.GetInstantiatedFieldInfoAddressStorage()); + instructions.Add(CilOpCodes.Ldarg, setMethod.Parameters[0]); + instructions.Add(CilOpCodes.Call, setStaticFieldValue.MakeGenericInstanceMethod([field.FieldType])); + instructions.Add(CilOpCodes.Ret); + } + else if (isPointerOrByRefInValueType) + { + var fieldType = (GenericInstanceTypeAnalysisContext)field.FieldType; + var genericArgument = fieldType.GenericArguments[0]; + var uninstantiatedConversion = fieldType.GenericType.GetExplicitConversionTo(voidPointer); + instructions.Add(CilOpCodes.Ldarg, This.Instance); + instructions.Add(CilOpCodes.Ldarg, setMethod.Parameters[0]); + instructions.Add(CilOpCodes.Call, uninstantiatedConversion.MakeConcreteGenericMethod(fieldType.GenericArguments, [])); + instructions.Add(CilOpCodes.Stfld, field.SelfInstantiateIfNecessary()); + instructions.Add(CilOpCodes.Ret); + } + else + { + instructions.Add(CilOpCodes.Ldarg, This.Instance); + instructions.Add(CilOpCodes.Ldsfld, field.GetInstantiatedOffsetStorage()); + instructions.Add(CilOpCodes.Ldarg, setMethod.Parameters[0]); + instructions.Add(CilOpCodes.Call, setInstanceFieldValue.MakeGenericInstanceMethod([field.FieldType])); + instructions.Add(CilOpCodes.Ret); + } + + setMethod.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + }); + } + + var property = new InjectedPropertyAnalysisContext( + field.Name, + field.FieldType, + getMethod, + setMethod, + PropertyAttributes.None, + field.DeclaringType) + { + IsInjected = true, + OriginalField = field, + }; + field.PropertyAccessor = property; + field.DeclaringType.Properties.Add(property); + + // Il2CppFieldAttribute + { + var attribute = new AnalyzedCustomAttribute(il2CppFieldAttributeConstructor); + if (property.Name != field.DefaultName) + { + var parameter = new CustomAttributePrimitiveParameter(field.DefaultName, attribute, CustomAttributeParameterKind.Property, attribute.Properties.Count); + attribute.Properties.Add(new CustomAttributeProperty(il2CppMemberAttributeName, parameter)); + } + var index = field.InitializationClassIndex; + if (index >= 0) + { + var parameter = new CustomAttributePrimitiveParameter(index, attribute, CustomAttributeParameterKind.Property, attribute.Properties.Count); + attribute.Properties.Add(new CustomAttributeProperty(il2CppFieldAttributeIndex, parameter)); + } + property.CustomAttributes = [attribute]; + } + + if (isPointerOrByRefInValueType) + { + field.OverrideFieldType = voidPointer; + field.OverrideName = GetNonConflictingName(field.Name + "_BackingField", existingNames); + field.Visibility = FieldAttributes.Private; + } + else + { + field.DeclaringType.Fields.RemoveAt(i); + } + } + } + } + } + + private static string GetNonConflictingName(string baseName, HashSet existingNames) + { + var name = baseName; + while (existingNames.Contains(name)) + { + name = $"{baseName}_"; + } + return name; + } +} diff --git a/Il2CppInterop.Generator/GeneratorOptions.cs b/Il2CppInterop.Generator/GeneratorOptions.cs deleted file mode 100644 index 59fe941e..00000000 --- a/Il2CppInterop.Generator/GeneratorOptions.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.IO.Compression; -using System.Text; -using System.Text.RegularExpressions; -using AsmResolver.DotNet; - -namespace Il2CppInterop.Generator; - -public class GeneratorOptions -{ - public List? Source { get; set; } - public string? OutputDir { get; set; } - - public string? UnityBaseLibsDir { get; set; } - public List AdditionalAssembliesBlacklist { get; } = new(); - public int TypeDeobfuscationCharsPerUniquifier { get; set; } = 2; - public int TypeDeobfuscationMaxUniquifiers { get; set; } = 10; - public string? GameAssemblyPath { get; set; } - public bool Verbose { get; set; } - public bool NoXrefCache { get; set; } - public Regex? ObfuscatedNamesRegex { get; set; } - public Dictionary RenameMap { get; } = new(); - public bool PassthroughNames { get; set; } - public bool Parallel { get; set; } = true; - - public PrefixMode Il2CppPrefixMode { get; set; } = PrefixMode.OptIn; - public HashSet NamespacesAndAssembliesToPrefix { get; } = - new() { "System", "mscorlib", "Microsoft", "Mono", "I18N" }; - public HashSet NamespacesAndAssembliesToNotPrefix { get; } = - new() { "Assembly-CSharp", "Unity" }; - - public List DeobfuscationGenerationAssemblies { get; } = new(); - public string? DeobfuscationNewAssembliesPath { get; set; } - - /// - /// Reads a rename map from the specified name into the specified instance of options - /// - public void ReadRenameMap(string fileName) - { - using var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read); - ReadRenameMap(fileStream, fileName.EndsWith(".gz")); - } - - /// - /// Reads a rename map from the specified name into the specified instance of options. - /// The stream is not closed by this method. - /// - public void ReadRenameMap(Stream fileStream, bool isGzip) - { - if (isGzip) - { - using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress, true); - ReadRenameMap(gzipStream, false); - return; - } - - using var reader = new StreamReader(fileStream, Encoding.UTF8, false, 65536, true); - while (!reader.EndOfStream) - { - var line = reader.ReadLine(); - if (string.IsNullOrEmpty(line) || line.StartsWith("#")) continue; - var split = line.Split(';'); - if (split.Length < 2) continue; - RenameMap[split[0]] = split[1]; - } - } - - public enum PrefixMode - { - /// - /// Only specified namespaces and assemblies will be renamed. - /// - OptIn, - /// - /// Only specified namespaces and assemblies will not be renamed. - /// - OptOut - } -} diff --git a/Il2CppInterop.Generator/GenericTypeName.cs b/Il2CppInterop.Generator/GenericTypeName.cs new file mode 100644 index 00000000..f64177f7 --- /dev/null +++ b/Il2CppInterop.Generator/GenericTypeName.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace Il2CppInterop.Generator; + +internal static partial class GenericTypeName +{ + public static bool TryMatch(string genericName, [NotNullWhen(true)] out string? typeName, [NotNullWhen(true)] out string? genericCount) + { + var match = GenericTypeRegex.Match(genericName); + if (match.Success) + { + typeName = match.Groups[1].Value; + genericCount = match.Groups[2].Value; + return true; + } + else + { + typeName = null; + genericCount = null; + return false; + } + } + + [GeneratedRegex(@"^(.+)`(\d+)$")] + private static partial Regex GenericTypeRegex { get; } +} diff --git a/Il2CppInterop.Generator/ICallProcessingLayer.cs b/Il2CppInterop.Generator/ICallProcessingLayer.cs new file mode 100644 index 00000000..775d86c1 --- /dev/null +++ b/Il2CppInterop.Generator/ICallProcessingLayer.cs @@ -0,0 +1,44 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class ICallProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "ICall Processor"; + public override string Id => "icall_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + foreach (var method in type.Methods) + { + if (!method.IsUnstripped || !method.DefaultImplAttributes.HasFlag(MethodImplAttributes.InternalCall)) + continue; + + Debug.Assert(type.InitializationType is not null); + Debug.Assert(!method.HasExtraData()); + Debug.Assert(!method.HasExtraData()); + Debug.Assert(!method.HasExtraData()); + Debug.Assert(method.GenericParameters.Count == 0 && type.GenericParameters.Count == 0, "Internal calls cannot be generic."); + + } + } + } + } + + private static string GetICallSignature(MethodAnalysisContext method) + { + return $"{method.DeclaringType!.DefaultFullName}::{method.DefaultName}"; + } +} diff --git a/Il2CppInterop.Generator/Il2CppGame.cs b/Il2CppInterop.Generator/Il2CppGame.cs new file mode 100644 index 00000000..418fcdf5 --- /dev/null +++ b/Il2CppInterop.Generator/Il2CppGame.cs @@ -0,0 +1,119 @@ +using Cpp2IL.Core; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.InstructionSets; +using Cpp2IL.Core.ProcessingLayers; +using Cpp2IL.Plugin.StrippedCodeRegSupport; +using LibCpp2IL; + +namespace Il2CppInterop.Generator; + +public static class Il2CppGame +{ + static Il2CppGame() + { + InstructionSetRegistry.RegisterInstructionSet(DefaultInstructionSets.X86_32); + InstructionSetRegistry.RegisterInstructionSet(DefaultInstructionSets.X86_64); + InstructionSetRegistry.RegisterInstructionSet(DefaultInstructionSets.WASM); + InstructionSetRegistry.RegisterInstructionSet(DefaultInstructionSets.ARM_V7); + InstructionSetRegistry.RegisterInstructionSet(DefaultInstructionSets.ARM_V8); + + LibCpp2IlBinaryRegistry.RegisterBuiltInBinarySupport(); + + new StrippedCodeRegSupportPlugin().OnLoad(); + } + + public static void Process(string gameExePath, string outputFolder, Cpp2IlOutputFormat outputFormat, List processingLayers, KeyValuePair[] extraData) + { + Process(gameExePath, processingLayers, extraData); + + outputFormat.DoOutput(Cpp2IlApi.CurrentAppContext!, outputFolder); + } + + public static void Process(string gameExePath, List processingLayers, KeyValuePair[] extraData) + { + var gameExeName = Path.GetFileNameWithoutExtension(gameExePath); + + var gameDirectory = Path.GetDirectoryName(gameExePath)!; + + var GameDataPath = Path.Join(gameDirectory, $"{gameExeName}_Data"); + + var GameAssemblyPath = GetGameAssemblyPath(gameDirectory); + + var MetaDataPath = Path.Join(GameDataPath, "il2cpp_data", "Metadata", "global-metadata.dat"); + + var UnityVersion = Cpp2IlApi.DetermineUnityVersion(null, GameDataPath); + + Cpp2IlApi.InitializeLibCpp2Il(GameAssemblyPath, MetaDataPath, UnityVersion, false); + + foreach ((var key, var value) in extraData) + { + Cpp2IlApi.CurrentAppContext.PutExtraData(key, value); + } + + foreach (var cpp2IlProcessingLayer in processingLayers) + { + cpp2IlProcessingLayer.PreProcess(Cpp2IlApi.CurrentAppContext, processingLayers); + } + + foreach (var cpp2IlProcessingLayer in processingLayers) + { + cpp2IlProcessingLayer.Process(Cpp2IlApi.CurrentAppContext); + } + } + + private static string GetGameAssemblyPath(string gameDirectory) + { + foreach (var fileName in (ReadOnlySpan)["GameAssembly.dll", "GameAssembly.dylib", "GameAssembly.so"]) + { + var path = Path.Join(gameDirectory, fileName); + if (File.Exists(path)) + return path; + } + throw new FileNotFoundException("Could not find GameAssembly binary in game directory."); + } + + public static List GetDefaultProcessingLayers() => + [ + new AttributeAnalysisProcessingLayer(), // Needed for recovery of unmanaged constraints + //new StableRenamingProcessingLayer(), + new OverrideRenamingProcessingLayer(), // Must be before CleanRenamingProcessingLayer + new UnstripProcessingLayer(), // Can be disabled for performance during development + new InterfaceOverrideProcessingLayer(), + new InvalidFieldRemovalProcessingLayer(), + new AttributesOverrideProcessingLayer(), // Must be before Il2CppRenamingProcessingLayer + new Il2CppRenamingProcessingLayer(), + new CleanRenamingProcessingLayer(), + new ConflictRenamingProcessingLayer(), + new PublicizerProcessingLayer(), + new MscorlibAssemblyInjectionProcessingLayer(), + new KnownTypeAssignmentProcessingLayer(), + new ReferenceAssemblyInjectionProcessingLayer(), + new InvisibleInterfaceProcessingLayer(), + new ObjectInterfaceProcessingLayer(), + new ReferenceReplacementProcessingLayer(), + new AttributeRemovalProcessingLayer(), + new IndexerAttributeInjectionProcessingLayer(), + new PointerConstructorProcessingLayer(), + new Il2CppTypeConstraintProcessingLayer(), + new InitializationClassProcessingLayer(), + new MarshallingProcessingLayer(), + new BoxingProcessingLayer(), + new PrimitiveImplicitConversionProcessingLayer(), + new EnumProcessingLayer(), + new ObjectOverridesProcessingLayer(), + new ObjectInternalsProcessingLayer(), + new MemberAttributeProcessingLayer(), + new FieldAccessorProcessingLayer(), + new EventProcessingLayer(), + new ExceptionHierarchyProcessingLayer(), + new MethodInvokerProcessingLayer(), + new MethodBodyTranslationProcessingLayer(), + new NativeMethodBodyProcessingLayer(), + new DelegateConversionProcessingLayer(), + new ByRefParameterOverloadProcessingLayer(), + new UserFriendlyOverloadProcessingLayer(), + new SystemInterfaceProcessingLayer(), + new ConstantInitializationProcessingLayer(), + new StaticConstructorProcessingLayer(), + ]; +} diff --git a/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj b/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj index bab17953..56de393e 100644 --- a/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj +++ b/Il2CppInterop.Generator/Il2CppInterop.Generator.csproj @@ -1,23 +1,16 @@ - + Il2CppInterop.Generator - knah, BepInEx et al. Library for generating assemblies and metadata for calling Il2Cpp functions from .NET - netstandard2.1;net472 - Il2CppInterop.Generator - Debug;Release - AnyCPU - latest - enable + net10.0 + true - - - MonoModBackports - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -25,5 +18,20 @@ + + + + + + + + + + + + + + + diff --git a/Il2CppInterop.Generator/Il2CppInteropGenerator.cs b/Il2CppInterop.Generator/Il2CppInteropGenerator.cs deleted file mode 100644 index a1a1aca5..00000000 --- a/Il2CppInterop.Generator/Il2CppInteropGenerator.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Il2CppInterop.Common; -using Il2CppInterop.Common.Host; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Generator.Runners; -using Il2CppInterop.Generator.XrefScans; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator; - -public sealed class Il2CppInteropGenerator : BaseHost -{ - private GeneratorOptions Options { get; init; } - - private readonly List _runners = new(); - - private Il2CppInteropGenerator(GeneratorOptions options) - { - Options = options; - } - - public static Il2CppInteropGenerator Create(GeneratorOptions options) - { - var generator = new Il2CppInteropGenerator(options); - generator.AddXrefScanner(); - return generator; - } - - public override void Start() - { - base.Start(); - - foreach (var runner in _runners) - { - Logger.Instance.LogTrace("Running {RunnerName}", runner.GetType().Name); - runner.Run(Options); - } - } - - public override void Dispose() - { - foreach (var runner in _runners) - runner.Dispose(); - _runners.Clear(); - base.Dispose(); - } - - public void Run() - { - Start(); - Dispose(); - } - - internal Il2CppInteropGenerator AddRunner() where T : IRunner, new() - { - _runners.Add(new T()); - return this; - } -} diff --git a/Il2CppInterop.Generator/Il2CppRenamingProcessingLayer.cs b/Il2CppInterop.Generator/Il2CppRenamingProcessingLayer.cs new file mode 100644 index 00000000..acc74063 --- /dev/null +++ b/Il2CppInterop.Generator/Il2CppRenamingProcessingLayer.cs @@ -0,0 +1,75 @@ +using System.Runtime.CompilerServices; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class Il2CppRenamingProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Il2Cpp Name Changes"; + + public override string Id => "il2cpprenamer"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + Logger.InfoNewline("Renaming assemblies and types to Il2Cpp", nameof(Il2CppRenamingProcessingLayer)); + + var assemblyCount = appContext.Assemblies.Count; + for (var i = 0; i < assemblyCount; i++) + { + var assembly = appContext.Assemblies[i]; + + if (!IsUnity(assembly.Name) && !IsAssemblyCSharp(assembly.Name)) + { + assembly.OverrideName = "Il2Cpp" + assembly.Name; + } + + foreach (var type in assembly.TopLevelTypes) + { + if (!IsUnity(type.Namespace)) + { + type.OverrideNamespace = "Il2Cpp" + type.Namespace; + } + } + + ResetTypesByName(assembly); + + progressCallback?.Invoke(i, assemblyCount); + } + + ResetAssembliesByName(appContext); + } + + private static bool IsUnity(string name) + { + return name.StartsWith("Unity", StringComparison.Ordinal); + } + + private static bool IsAssemblyCSharp(string name) + { + return name.StartsWith("Assembly-CSharp", StringComparison.Ordinal); + } + + private static void ResetAssembliesByName(ApplicationAnalysisContext appContext) + { + var dictionary = appContext.AssembliesByName; + dictionary.Clear(); + foreach (var assembly in appContext.Assemblies) + { + dictionary[assembly.Name] = assembly; + } + } + + private static void ResetTypesByName(AssemblyAnalysisContext assembly) + { + var dictionary = GetTypesByName(assembly); + dictionary.Clear(); + foreach (var type in assembly.Types) + { + dictionary[type.FullName] = type; + } + } + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "TypesByName")] + private static extern ref Dictionary GetTypesByName(AssemblyAnalysisContext assembly); +} diff --git a/Il2CppInterop.Generator/Il2CppTypeConstraintProcessingLayer.cs b/Il2CppInterop.Generator/Il2CppTypeConstraintProcessingLayer.cs new file mode 100644 index 00000000..884700cc --- /dev/null +++ b/Il2CppInterop.Generator/Il2CppTypeConstraintProcessingLayer.cs @@ -0,0 +1,46 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Generator; + +public class Il2CppTypeConstraintProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Il2CppType Constraint Processor"; + public override string Id => "il2cpptype_constraint_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var iil2CppTypeGeneric = appContext.ResolveTypeOrThrow(typeof(IIl2CppType<>)); + var iobject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IObject"); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + foreach (var genericParameter in type.GenericParameters) + { + genericParameter.ConstraintTypes.Add(iil2CppTypeGeneric.MakeGenericInstanceType([genericParameter])); + genericParameter.ConstraintTypes.Add(iobject); + } + + foreach (var method in type.Methods) + { + if (method.IsInjected) + continue; + + foreach (var genericParameter in method.GenericParameters) + { + genericParameter.ConstraintTypes.Add(iil2CppTypeGeneric.MakeGenericInstanceType([genericParameter])); + genericParameter.ConstraintTypes.Add(iobject); + } + } + } + } + } +} diff --git a/Il2CppInterop.Generator/IndexerAttributeInjectionProcessingLayer.cs b/Il2CppInterop.Generator/IndexerAttributeInjectionProcessingLayer.cs new file mode 100644 index 00000000..144edb2c --- /dev/null +++ b/Il2CppInterop.Generator/IndexerAttributeInjectionProcessingLayer.cs @@ -0,0 +1,43 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Model.CustomAttributes; + +namespace Il2CppInterop.Generator; + +public class IndexerAttributeInjectionProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "indexer_attribute_injection"; + public override string Name => "Indexer Attribute Injection"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var defaultMemberAttribute = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.Reflection.DefaultMemberAttribute"); + var defaultMemberAttributeConstructor = defaultMemberAttribute.Methods.First(m => m.IsInstanceConstructor && m.Parameters.Count == 1 && m.Parameters[0].ParameterType == appContext.SystemTypes.SystemStringType); + + HashSet indexerNames = []; + foreach (var type in appContext.AllTypes) + { + indexerNames.Clear(); + indexerNames.AddRange(type.Properties.Where(IsIndexerProperty).Select(p => p.Name)); + if (indexerNames.Count != 1) + continue; + + type.CustomAttributes ??= new(1); + + var customAttribute = new AnalyzedCustomAttribute(defaultMemberAttributeConstructor); + customAttribute.ConstructorParameters.Add(new CustomAttributePrimitiveParameter(indexerNames.First(), customAttribute, CustomAttributeParameterKind.ConstructorParam, 0)); + type.CustomAttributes.Add(customAttribute); + } + } + + private static bool IsIndexerProperty(PropertyAnalysisContext property) + { + if (property is { Getter.Parameters.Count: > 0, Getter.Overrides.Count: 0 }) + { + return property.Setter is null || property.Setter.Parameters.Count == property.Getter.Parameters.Count + 1; + } + else + { + return property is { Setter.Parameters.Count: > 1, Setter.Overrides.Count: 0 }; + } + } +} diff --git a/Il2CppInterop.Generator/InitializationClassProcessingLayer.cs b/Il2CppInterop.Generator/InitializationClassProcessingLayer.cs new file mode 100644 index 00000000..9a42776a --- /dev/null +++ b/Il2CppInterop.Generator/InitializationClassProcessingLayer.cs @@ -0,0 +1,616 @@ +using System.Buffers.Binary; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Model.CustomAttributes; +using Cpp2IL.Core.Utils; +using Il2CppInterop.Common; +using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.Injection; +using Il2CppInterop.Runtime.InteropTypes; + +namespace Il2CppInterop.Generator; + +public class InitializationClassProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "initialization_class_injector"; + public override string Name => "Inject initialization class into the Cpp2IL context system"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var runClassConstructor = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.Runtime.CompilerServices.RuntimeHelpers") + .Methods.First(m => m.Name == nameof(RuntimeHelpers.RunClassConstructor) && m.Parameters[0].ParameterType != appContext.SystemTypes.SystemIntPtrType); + + var fieldAccessClass = appContext.ResolveTypeOrThrow(typeof(FieldAccess)); + var getFieldInfo = fieldAccessClass.GetMethodByName(nameof(FieldAccess.GetFieldInfo)); + var getFieldOffset = fieldAccessClass.GetMethodByName(nameof(FieldAccess.GetFieldOffset)); + + var resolveICall = appContext.ResolveTypeOrThrow(typeof(RuntimeInvoke)).GetMethodByName(nameof(RuntimeInvoke.ResolveICall)); + + var generationInternalsType = appContext.ResolveTypeOrThrow(typeof(GenerationInternals)); + var getIl2CppMethod = generationInternalsType.GetMethodByName(nameof(GenerationInternals.GetIl2CppMethod)); + var getIl2CppMethodByToken = generationInternalsType.GetMethodByName(nameof(GenerationInternals.GetIl2CppMethodByToken)); + var getIl2CppGenericInstanceType = generationInternalsType.GetMethodByName(nameof(GenerationInternals.GetIl2CppGenericInstanceType)); + var getIl2CppNestedType = generationInternalsType.GetMethodByName(nameof(GenerationInternals.GetIl2CppNestedType)); + var getIl2CppClass = generationInternalsType.GetMethodByName(nameof(GenerationInternals.GetIl2CppClass)); + var il2CppRuntimeClassInit = generationInternalsType.GetMethodByName(nameof(GenerationInternals.Il2CppRuntimeClassInit)); + var getIl2CppGenericInstanceMethod = generationInternalsType.GetMethodByName(nameof(GenerationInternals.GetIl2CppGenericInstanceMethod)); + var getIl2CppValueSize = generationInternalsType.GetMethodByName(nameof(GenerationInternals.GetIl2CppValueSize)); + + var typeInjector = appContext.ResolveTypeOrThrow(typeof(TypeInjector)); + var registerTypeInIl2Cpp = typeInjector.Methods.Single(m => + { + return m.Name == nameof(TypeInjector.RegisterTypeInIl2Cpp) && m.Parameters.Count is 0 && m.GenericParameters.Count == 1; + }); + + var multicastDelegateType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.MulticastDelegate"); + var asyncCallbackType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.AsyncCallback"); + var iasyncResultType = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.IAsyncResult"); + + var byReference = appContext.ResolveTypeOrThrow(typeof(ByReference<>)); + var byReference_CopyFrom = byReference.GetMethodByName(nameof(ByReference<>.CopyFrom)); + var byReference_CopyTo = byReference.GetMethodByName(nameof(ByReference<>.CopyTo)); + var byReference_Constructor = byReference.GetMethodByName(".ctor"); + + var il2CppType = appContext.ResolveTypeOrThrow(typeof(Il2CppType)); + var il2CppType_SizeOf = il2CppType.GetMethodByName(nameof(Il2CppType.SizeOf)); + var il2CppType_GetClassPointer = il2CppType.Methods.Single(m => m.Name == nameof(Il2CppType.GetClassPointer) && m.GenericParameters.Count == 1); + var il2CppType_SetClassPointer = il2CppType.Methods.Single(m => m.Name == nameof(Il2CppType.SetClassPointer) && m.GenericParameters.Count == 1); + + var il2CppTypeAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppTypeAttribute)); + var il2CppTypeAttributeConstructor = il2CppTypeAttribute.GetMethodByName(".ctor"); + + var tokenLessMethodCount = 0; + + // 2 pointers + var headerSize = (object)(appContext.Binary.is32Bit ? 8 : 16); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + for (var i = 0; i < assembly.Types.Count; i++) + { + var type = assembly.Types[i]; + + if (type.IsInjected) + continue; + + var initializationType = assembly.InjectType( + "Il2CppInterop.Generated", + $"Il2CppInternals_{HashString(type.FullName):x16}", + appContext.SystemTypes.SystemObjectType, + TypeAttributes.NotPublic | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.Class); + initializationType.IsInjected = true; + initializationType.CopyGenericParameters(type, true, true); + + AddInstructionsToStaticConstructor(type, initializationType, runClassConstructor); + + // Il2CppTypeAttribute + { + var attribute = new AnalyzedCustomAttribute(il2CppTypeAttributeConstructor); + attribute.ConstructorParameters.Add(new CustomAttributeTypeParameter(initializationType, attribute, CustomAttributeParameterKind.ConstructorParam, 0)); + + type.CustomAttributes ??= new(1); + type.CustomAttributes.Add(attribute); + } + + // Initialization static constructor + { + var staticConstructor = new InjectedMethodAnalysisContext( + initializationType, + ".cctor", + type.AppContext.SystemTypes.SystemVoidType, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + []); + initializationType.Methods.Add(staticConstructor); + + var instructions = new List(); + var localVariables = new List(); + + var typeToInitialize = initializationType.GenericParameters.Count == 0 + ? type + : type.MakeGenericInstanceType(initializationType.GenericParameters); + + var getClassPointerMethodInstantiated = il2CppType_GetClassPointer.MakeGenericInstanceMethod(typeToInitialize); + if (type.IsUnstripped) + { + instructions.Add(new Instruction(CilOpCodes.Call, registerTypeInIl2Cpp.MakeGenericInstanceMethod(typeToInitialize))); + } + else + { + if (typeToInitialize.DeclaringType is not null) + { + // Ensure declaring type is initialized first + instructions.Add(new Instruction(CilOpCodes.Ldtoken, typeToInitialize.DeclaringType)); + instructions.Add(new Instruction(CilOpCodes.Call, runClassConstructor)); + + // Il2CppType.SetClassPointer(IL2CPP.GetIl2CppNestedType(Il2CppType.GetClassPointer(), "NestedClass")); + instructions.Add(new Instruction(CilOpCodes.Call, il2CppType_GetClassPointer.MakeGenericInstanceMethod(typeToInitialize.DeclaringType))); + instructions.Add(new Instruction(CilOpCodes.Ldstr, type.DefaultName));// typeToInitialize can have the wrong DefaultName + instructions.Add(new Instruction(CilOpCodes.Call, getIl2CppNestedType)); + } + else + { + // Il2CppType.SetClassPointer(IL2CPP.GetIl2CppClass("Assembly-CSharp.dll", "", "Class")); + instructions.Add(new Instruction(CilOpCodes.Ldstr, assembly.ImageName)); + instructions.Add(new Instruction(CilOpCodes.Ldstr, type.DefaultNamespace)); + instructions.Add(new Instruction(CilOpCodes.Ldstr, type.DefaultName)); + instructions.Add(new Instruction(CilOpCodes.Call, getIl2CppClass)); + } + if (type.GenericParameters.Count > 0) + { + instructions.Add(new Instruction(CilOpCodes.Ldc_I4, type.GenericParameters.Count)); + instructions.Add(new Instruction(CilOpCodes.Newarr, appContext.SystemTypes.SystemIntPtrType)); + for (var j = 0; j < type.GenericParameters.Count; j++) + { + instructions.Add(new Instruction(CilOpCodes.Dup)); + instructions.Add(new Instruction(CilOpCodes.Ldc_I4, j)); + instructions.Add(new Instruction(CilOpCodes.Call, il2CppType_GetClassPointer.MakeGenericInstanceMethod(initializationType.GenericParameters[j]))); + instructions.Add(new Instruction(CilOpCodes.Stelem_I)); + } + instructions.Add(new Instruction(CilOpCodes.Call, getIl2CppGenericInstanceType)); + } + instructions.Add(new Instruction(CilOpCodes.Call, il2CppType_SetClassPointer.MakeGenericInstanceMethod(typeToInitialize))); + + // Il2CppRuntimeClassInit(Il2CppType.GetClassPointer()); + instructions.Add(new Instruction(CilOpCodes.Call, getClassPointerMethodInstantiated)); + instructions.Add(new Instruction(CilOpCodes.Call, il2CppRuntimeClassInit)); + } + + // Size = GetIl2CppValueSize(Il2CppType.GetClassPointer()); + if (type.IsValueType) + { + var sizeStore = initializationType.InjectFieldContext( + "Size", + appContext.SystemTypes.SystemInt32Type, + FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.InitOnly); + type.SizeStorage = sizeStore; + + FieldAnalysisContext instantiatedSizeStore = initializationType.GenericParameters.Count > 0 + ? new ConcreteGenericFieldAnalysisContext(sizeStore, initializationType.MakeGenericInstanceType(initializationType.GenericParameters)) + : sizeStore; + + instructions.Add(new Instruction(CilOpCodes.Call, getClassPointerMethodInstantiated)); + instructions.Add(new Instruction(CilOpCodes.Call, getIl2CppValueSize)); + instructions.Add(new Instruction(CilOpCodes.Stsfld, instantiatedSizeStore)); + } + + // FieldOffset_0 = FieldAccess.GetFieldOffset(FieldAccess.GetFieldInfo(Il2CppType.GetClassPointer(), "field_name")); + for (var index = 0; index < type.Fields.Count; index++) + { + var field = type.Fields[index]; + + if (field.IsInjected) + continue; + + if (field.IsUnstripped && !type.IsUnstripped) + continue; + + field.InitializationClassIndex = index; + + var infoStore = initializationType.InjectFieldContext( + $"FieldInfoPtr_{index}", + appContext.SystemTypes.SystemIntPtrType, + FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.InitOnly); + field.FieldInfoAddressStorage = infoStore; + + FieldAnalysisContext instantiatedInfoStore = initializationType.GenericParameters.Count > 0 + ? new ConcreteGenericFieldAnalysisContext(infoStore, initializationType.MakeGenericInstanceType(initializationType.GenericParameters)) + : infoStore; + + var offsetStore = initializationType.InjectFieldContext( + $"FieldOffset_{index}", + appContext.SystemTypes.SystemInt32Type, + FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.InitOnly); + field.OffsetStorage = offsetStore; + + FieldAnalysisContext instantiatedOffsetStore = initializationType.GenericParameters.Count > 0 + ? new ConcreteGenericFieldAnalysisContext(offsetStore, initializationType.MakeGenericInstanceType(initializationType.GenericParameters)) + : offsetStore; + + instructions.Add(new Instruction(CilOpCodes.Call, getClassPointerMethodInstantiated)); + instructions.Add(new Instruction(CilOpCodes.Ldstr, field.DefaultName)); + instructions.Add(new Instruction(CilOpCodes.Call, getFieldInfo)); + instructions.Add(new Instruction(CilOpCodes.Dup)); + instructions.Add(new Instruction(CilOpCodes.Stsfld, instantiatedInfoStore)); + instructions.Add(new Instruction(CilOpCodes.Call, getFieldOffset)); + instructions.Add(new Instruction(CilOpCodes.Conv_I4)); + if (type.IsValueType) + { + // il2cpp_field_get_offset returns offset including the object header + // For value types, we need to subtract the header size to get the offset of the field within the struct + instructions.Add(new Instruction(CilOpCodes.Ldc_I4, headerSize)); + instructions.Add(new Instruction(CilOpCodes.Sub)); + } + instructions.Add(new Instruction(CilOpCodes.Stsfld, instantiatedOffsetStore)); + } + + // MethodInfoPtr_0 + for (var index = 0; index < type.Methods.Count; index++) + { + var method = type.Methods[index]; + + if (method.IsUnstripped || method.IsInjected) + continue; + + method.InitializationClassIndex = index; + + var methodInfoStore = initializationType.InjectFieldContext( + $"MethodInfoPtr_{index}", + appContext.SystemTypes.SystemIntPtrType, + FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.InitOnly); + method.MethodInfoField = methodInfoStore; + + FieldAnalysisContext concreteMethodInfoStore = initializationType.GenericParameters.Count > 0 + ? new ConcreteGenericFieldAnalysisContext(methodInfoStore, initializationType.MakeGenericInstanceType(initializationType.GenericParameters)) + : methodInfoStore; + + if (method.Token == 0) + { + tokenLessMethodCount++; + + instructions.Add(new Instruction(CilOpCodes.Call, getClassPointerMethodInstantiated)); + instructions.Add(new Instruction(method.GenericParameters.Count == 0 ? CilOpCodes.Ldc_I4_0 : CilOpCodes.Ldc_I4_1)); + instructions.Add(new Instruction(CilOpCodes.Ldstr, method.DefaultName)); + instructions.Add(new Instruction(CilOpCodes.Ldstr, method.DefaultReturnType.DefaultFullName)); + instructions.Add(new Instruction(CilOpCodes.Ldc_I4, method.Parameters.Count)); + instructions.Add(new Instruction(CilOpCodes.Newarr, method.AppContext.SystemTypes.SystemStringType)); + + for (var parameterIndex = 0; i < method.Parameters.Count; i++) + { + instructions.Add(new Instruction(CilOpCodes.Dup)); + instructions.Add(new Instruction(CilOpCodes.Ldc_I4, parameterIndex)); + instructions.Add(new Instruction(CilOpCodes.Ldstr, method.Parameters[i].DefaultParameterType.DefaultFullName)); + instructions.Add(new Instruction(CilOpCodes.Stelem_Ref)); + } + + instructions.Add(new Instruction(CilOpCodes.Call, getIl2CppMethod)); + } + else + { + instructions.Add(new Instruction(CilOpCodes.Call, getClassPointerMethodInstantiated)); + instructions.Add(new Instruction(CilOpCodes.Ldc_I4, unchecked((int)method.Token))); + instructions.Add(new Instruction(CilOpCodes.Call, getIl2CppMethodByToken)); + } + instructions.Add(new Instruction(CilOpCodes.Stsfld, concreteMethodInfoStore)); + + if (method.GenericParameters.Count > 0) + { + var methodInfoPtrGenericClass = initializationType.InjectNestedType( + $"MethodInfoPtrGeneric_{index}", + appContext.SystemTypes.SystemObjectType, + TypeAttributes.NestedAssembly | TypeAttributes.Abstract | TypeAttributes.Sealed); + methodInfoPtrGenericClass.IsInjected = true; + methodInfoPtrGenericClass.CopyGenericParameters(initializationType, false, true); + methodInfoPtrGenericClass.CopyGenericParameters(method, false, true); + methodInfoPtrGenericClass.GenericParameters.CopyConstraintsFrom([.. initializationType.GenericParameters, .. method.GenericParameters]); + + var methodInfoPtrGenericField = methodInfoPtrGenericClass.InjectFieldContext( + "Pointer", + appContext.SystemTypes.SystemIntPtrType, + FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.InitOnly); + method.MethodInfoField = methodInfoPtrGenericField; // A generic method's real MethodInfoField is the generically instantiated one. + + var methodInfoPtrGenericStaticConstructor = new InjectedMethodAnalysisContext( + methodInfoPtrGenericClass, + ".cctor", + appContext.SystemTypes.SystemVoidType, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + []); + methodInfoPtrGenericClass.Methods.Add(methodInfoPtrGenericStaticConstructor); + + FieldAnalysisContext concreteMethodInfoStore2 = initializationType.GenericParameters.Count > 0 + ? new ConcreteGenericFieldAnalysisContext(methodInfoStore, initializationType.MakeGenericInstanceType(methodInfoPtrGenericClass.GenericParameters.Take(initializationType.GenericParameters.Count))) + : methodInfoStore; + + var instructions2 = new List(); + instructions2.Add(new Instruction(CilOpCodes.Ldsfld, concreteMethodInfoStore2)); + instructions2.Add(new Instruction(CilOpCodes.Call, getClassPointerMethodInstantiated)); + instructions2.Add(new Instruction(CilOpCodes.Ldc_I4, method.GenericParameters.Count)); + instructions2.Add(new Instruction(CilOpCodes.Newarr, appContext.SystemTypes.SystemIntPtrType)); + for (var j = 0; j < method.GenericParameters.Count; j++) + { + var genericParameter = methodInfoPtrGenericClass.GenericParameters[j + initializationType.GenericParameters.Count]; + instructions2.Add(new Instruction(CilOpCodes.Dup)); + instructions2.Add(new Instruction(CilOpCodes.Ldc_I4, j)); + instructions2.Add(new Instruction(CilOpCodes.Call, il2CppType_GetClassPointer.MakeGenericInstanceMethod(genericParameter))); + instructions2.Add(new Instruction(CilOpCodes.Stelem_I)); + } + instructions2.Add(new Instruction(CilOpCodes.Call, getIl2CppGenericInstanceMethod)); + instructions2.Add(new Instruction(CilOpCodes.Stsfld, new ConcreteGenericFieldAnalysisContext(methodInfoPtrGenericField, methodInfoPtrGenericClass.MakeGenericInstanceType(methodInfoPtrGenericClass.GenericParameters)))); + instructions2.Add(new Instruction(CilOpCodes.Ret)); + + methodInfoPtrGenericStaticConstructor.PutExtraData(new NativeMethodBody() + { + Instructions = instructions2, + }); + } + } + + // Internal call methods + for (var index = 0; index < type.Methods.Count; index++) + { + var method = type.Methods[index]; + + if (!method.IsUnstripped || !method.DefaultImplAttributes.HasFlag(MethodImplAttributes.InternalCall)) + continue; + + Debug.Assert(!method.HasExtraData()); + Debug.Assert(!method.HasExtraData()); + Debug.Assert(!method.HasExtraData()); + Debug.Assert(method.GenericParameters.Count == 0 && type.GenericParameters.Count == 0, "Internal calls cannot be generic."); + + // ICall_Delegate_Type_0 + TypeAnalysisContext delegateType; + { + delegateType = initializationType.InjectNestedType( + $"ICall_Delegate_Type_{index}", + multicastDelegateType); + + var returnType = method.ReturnType; + IEnumerable parameterTypes; + IEnumerable parameterNames; + if (method.IsStatic) + { + parameterTypes = method.Parameters.Select(p => p.ParameterType); + parameterNames = Enumerable.Range(0, method.Parameters.Count).Select(i => $"param_{i}"); + } + else + { + var thisParameterType = type.IsValueType ? byReference.MakeGenericInstanceType([type]) : type; + parameterTypes = method.Parameters.Select(p => p.ParameterType).Prepend(thisParameterType); + parameterNames = Enumerable.Range(0, method.Parameters.Count).Select(i => $"param_{i}").Prepend("this"); + } + + // Constructor + { + delegateType.Methods.Add(new InjectedMethodAnalysisContext( + delegateType, + ".ctor", + appContext.SystemTypes.SystemVoidType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + [appContext.SystemTypes.SystemObjectType, appContext.SystemTypes.SystemIntPtrType], + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + + // Invoke + { + delegateType.Methods.Add(new InjectedMethodAnalysisContext( + delegateType, + "Invoke", + returnType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + parameterTypes.ToArray(), + parameterNames.ToArray(), + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + + // BeginInvoke + { + delegateType.Methods.Add(new InjectedMethodAnalysisContext( + delegateType, + "BeginInvoke", + iasyncResultType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + parameterTypes.Append(asyncCallbackType).Append(appContext.SystemTypes.SystemObjectType).ToArray(), + parameterNames.Append("callback").Append("object").ToArray(), + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + + // EndInvoke + { + delegateType.Methods.Add(new InjectedMethodAnalysisContext( + delegateType, + "EndInvoke", + returnType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + [iasyncResultType], + ["result"], + defaultImplAttributes: MethodImplAttributes.Runtime)); + } + } + + // ICall_Delegate_Field_0 + FieldAnalysisContext delegateField; + { + delegateField = initializationType.InjectFieldContext( + $"ICall_Delegate_Field_{index}", + delegateType, + FieldAttributes.Assembly | FieldAttributes.Static | FieldAttributes.InitOnly); + + method.ICallDelegateField = delegateField; + } + + // Static constructor instructions + { + instructions.Add(new Instruction(CilOpCodes.Ldstr, $"{type.DefaultFullName}::{method.DefaultName}")); + instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(resolveICall, [], [delegateType]))); + instructions.Add(new Instruction(CilOpCodes.Stsfld, delegateField)); + } + } + + instructions.Add(new Instruction(CilOpCodes.Ret)); + + staticConstructor.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = localVariables.Count > 0 ? localVariables : [], + }); + } + } + } + + Logger.Info($"Tokenless method count: {tokenLessMethodCount}", nameof(InitializationClassProcessingLayer)); + + // Il2CppInterop.Initialization.dll + { + var injectedAssembly = appContext.InjectAssembly("Il2CppInterop.Initialization"); + injectedAssembly.IsInjected = true; + + var initializationType = injectedAssembly.InjectType( + "Il2CppInterop.Initialization", + "Il2CppInitialization", + appContext.SystemTypes.SystemObjectType, + TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.Class); + + var initializeMethod = initializationType.InjectMethodContext( + "Initialize", + appContext.SystemTypes.SystemVoidType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + []); + + var instructions = new List(); + + var typeConverter = TypeConversionVisitor.Create(appContext); + + var processedTypes = new HashSet(TypeAnalysisContextEqualityComparer.Instance); + // Ensure all generic instantiations are initialized + foreach (var typeToResolve in appContext.Binary.AllTypes) + { + var typeContext = injectedAssembly.ResolveIl2CppType(typeToResolve); + + if (typeContext is not ReferencedTypeAnalysisContext && typeContext.GenericParameters.Count > 0) + continue; // Skip open generics + + if (InvalidTypeChecker.ContainsInvalidType(typeContext)) + continue; + + typeContext = typeConverter.Replace(typeContext); + + // Must happen after type conversion + if (InvalidConstraintChecker.ContainsInvalidConstraint(typeContext)) + continue; + + if (!processedTypes.Add(typeContext)) + continue; + + // Ensure the type is initialized + instructions.Add(new Instruction(CilOpCodes.Ldtoken, typeContext)); + instructions.Add(new Instruction(CilOpCodes.Call, runClassConstructor)); + } + + instructions.Add(new Instruction(CilOpCodes.Ret)); + + initializeMethod.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + }); + } + } + + private static void AddInstructionsToStaticConstructor(TypeAnalysisContext type, InjectedTypeAnalysisContext initializationType, MethodAnalysisContext runClassConstructor) + { + var instructions = type.GetOrCreateStaticConstructorInstructions(); + + var typeToInitialize = type.GenericParameters.Count == 0 ? initializationType : (TypeAnalysisContext)initializationType.MakeGenericInstanceType(type.GenericParameters); + + instructions.Add(new Instruction(CilOpCodes.Ldtoken, typeToInitialize)); + instructions.Add(new Instruction(CilOpCodes.Call, runClassConstructor)); + } + + private static ulong HashString(ReadOnlySpan chars) + { + var bytes = MemoryMarshal.AsBytes(chars); + Span hash = stackalloc byte[MD5.HashSizeInBytes]; + MD5.HashData(bytes, hash); + return BinaryPrimitives.ReadUInt64LittleEndian(hash); + } + + private sealed class InvalidTypeChecker : BooleanOrTypeVisitor + { + public static InvalidTypeChecker Instance { get; } = new InvalidTypeChecker(); + + public static bool ContainsInvalidType(TypeAnalysisContext type) + { + return Instance.Visit(type); + } + + public override bool Visit(BoxedTypeAnalysisContext type) => true; + public override bool Visit(ByRefTypeAnalysisContext type) => true; + public override bool Visit(GenericParameterTypeAnalysisContext type) => true; + public override bool Visit(PinnedTypeAnalysisContext type) => true; + public override bool Visit(SentinelTypeAnalysisContext type) => true; + } + + /// + /// Unity uses System.Object as a type argument when it deduplicates generic instantiations. + /// However, that might violate the constraints, so we need to detect those cases and skip generating initialization code for them. + /// + /// + /// This only checks for IObject because Object should have already been replaced.
+ /// + /// The fact that we need to check for these invalid constraints could indicate a major issue in the generated code. Either:
+ /// * The other instantiations don't exist in the Il2Cpp runtime (calling GetType() returns a type with object as the type argument). + /// This would mean the types are unusable unless we remove the constraints.
+ /// * The other instantiations do exist in the Il2Cpp runtime, and we need to use Il2Cpp reflection to construct the .NET Core type. + /// That is how System.__Canon works in .NET Core. + ///
+ private sealed class InvalidConstraintChecker : BooleanOrTypeVisitor + { + public static InvalidConstraintChecker Instance { get; } = new InvalidConstraintChecker(); + public static bool ContainsInvalidConstraint(TypeAnalysisContext type) + { + return Instance.Visit(type); + } + + public override bool Visit(GenericInstanceTypeAnalysisContext type) + { + for (var i = 0; i < type.GenericArguments.Count; i++) + { + if (!IsObject(type.GenericArguments[i])) + continue; + + // Check if any of the constraints on this generic parameter are not satisfied by object. + foreach (var constraint in type.GenericType.GenericParameters[i].ConstraintTypes) + { + if (IsObject(constraint)) + continue; + if (!IsInjectedOrReference(constraint)) + return true; + } + + // Check if any of the constraints on any of the declaring types' generic parameters might not be satisfied by object. + // Specifically, we check if a constraint references the target generic parameter. + // Obviously, this could have false positives, but it shouldn't have any false negatives. + var visitor = new TargetTypeFinder(type.GenericType.GenericParameters[i]); + for (var j = 0; j < type.GenericType.GenericParameters.Count; j++) + { + if (j == i) + continue; + foreach (var constraint in type.GenericType.GenericParameters[j].ConstraintTypes) + { + if (visitor.Visit(constraint)) + return true; + } + } + } + return base.Visit(type); + } + + private static bool IsInjectedOrReference(TypeAnalysisContext type) + { + type = (type as GenericInstanceTypeAnalysisContext)?.GenericType ?? type; + + return type.IsInjected || type.DeclaringAssembly.IsReferenceAssembly; + } + + private static bool IsObject(TypeAnalysisContext type) + { + return type.KnownType is KnownTypeCode.Il2CppSystem_IObject; + } + + private sealed class TargetTypeFinder(GenericParameterTypeAnalysisContext targetType) : BooleanOrTypeVisitor + { + public override bool Visit(GenericParameterTypeAnalysisContext type) => type == targetType; + } + } +} diff --git a/Il2CppInterop.Generator/InterfaceOverrideProcessingLayer.cs b/Il2CppInterop.Generator/InterfaceOverrideProcessingLayer.cs new file mode 100644 index 00000000..d8594d68 --- /dev/null +++ b/Il2CppInterop.Generator/InterfaceOverrideProcessingLayer.cs @@ -0,0 +1,168 @@ +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils; +using Il2CppInterop.Generator.Operands; +using LibCpp2IL; + +namespace Il2CppInterop.Generator; + +public class InterfaceOverrideProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Interface Override Renaming"; + public override string Id => "interfaceoverrides"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + HashSet<(MethodAnalysisContext ImplementingMethod, MethodAnalysisContext InterfaceMethod)> set = new(MethodAnalysisContextEqualityComparer.Instance); + List<(TypeAnalysisContext Type, MethodAnalysisContext ImplementingMethod, MethodAnalysisContext InterfaceMethod)> list = new(); + foreach (var type in appContext.AllTypes) + { + if (type.IsInterface) + { + continue; + } + + var definition = type.Definition; + if (definition == null) + continue; + var vtable = definition.VTable; + + for (var index = 0; index < vtable.Length; index++) + { + var vtableEntry = vtable[index]; + var interfaceMethod = GetImplementedMethod(type, definition, index); + if (interfaceMethod == null) + continue; + + var implementingMethod = vtableEntry?.Type switch + { + MetadataUsageType.MethodDef => appContext.ResolveContextForMethod(vtableEntry.AsMethod()), + MetadataUsageType.MethodRef => appContext.ResolveContextForMethod(vtableEntry.AsGenericMethodRef()), + _ => null + }; + if (implementingMethod == null) + continue; + + if (implementingMethod.DeclaringType is null or { IsInterface: false }) + continue; + + if (implementingMethod.Name == interfaceMethod.Name) + continue; + + if (interfaceMethod is not ConcreteGenericMethodAnalysisContext) + { + var baseImplementingMethod = (implementingMethod as ConcreteGenericMethodAnalysisContext)?.BaseMethodContext ?? implementingMethod; + set.Add((baseImplementingMethod, interfaceMethod)); + } + else if (implementingMethod is not ConcreteGenericMethodAnalysisContext) + { + set.Add((implementingMethod, interfaceMethod)); + } + else + { + // Complex case - both are concrete generic methods. + list.Add((type, implementingMethod, interfaceMethod)); + } + } + } + + foreach ((var implementingMethod, var interfaceMethod) in set) + { + if (!implementingMethod.Overrides.Contains(interfaceMethod, MethodAnalysisContextEqualityComparer.Instance)) + { + implementingMethod.Overrides.Add(interfaceMethod); + } + } + + foreach (var (type, implementingMethod, interfaceMethod) in list) + { + var methodName = $"{interfaceMethod.DeclaringType?.FullName}.{interfaceMethod.Name}"; + var newMethod = new InjectedMethodAnalysisContext( + type, + methodName, + implementingMethod.ReturnType, + implementingMethod.Attributes | MethodAttributes.NewSlot, + implementingMethod.Parameters.Select(p => p.ParameterType).ToArray(), + implementingMethod.Parameters.Select(p => p.Name).ToArray(), + implementingMethod.Parameters.Select(p => p.Attributes).ToArray(), + implementingMethod.ImplAttributes) + { + IsInjected = true + }; + type.Methods.Add(newMethod); + + newMethod.Overrides.Add(interfaceMethod); + + var instructions = new List + { + { CilOpCodes.Ldarg, This.Instance } + }; + foreach (var param in newMethod.Parameters) + { + instructions.Add(CilOpCodes.Ldarg, param); + } + instructions.Add(CilOpCodes.Call, implementingMethod); + instructions.Add(CilOpCodes.Ret); + + newMethod.PutExtraData(new NativeMethodBody() { Instructions = instructions }); + } + + foreach ((_, var implementingMethod, _) in list) + { + implementingMethod.OverrideAttributes = implementingMethod.Attributes & ~(MethodAttributes.Final | MethodAttributes.Virtual); + implementingMethod.Visibility = MethodAttributes.Public; + } + } + + private static MethodAnalysisContext? GetImplementedMethod(TypeAnalysisContext type, LibCpp2IL.Metadata.Il2CppTypeDefinition definition, int index) + { + foreach (var interfaceOffset in definition.InterfaceOffsets) + { + if (index >= interfaceOffset.offset) + { + var interfaceTypeContext = interfaceOffset.Type.ToContext(type.DeclaringAssembly); + if (interfaceTypeContext != null && interfaceTypeContext.TryGetMethodInSlot(index - interfaceOffset.offset, out var method)) + { + return method; + } + } + } + return null; + } + + private sealed class MethodAnalysisContextEqualityComparer : IEqualityComparer, IEqualityComparer<(MethodAnalysisContext, MethodAnalysisContext)> + { + public static MethodAnalysisContextEqualityComparer Instance { get; } = new(); + public bool Equals(MethodAnalysisContext? x, MethodAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x == null || y == null) + return false; + + if (x is ConcreteGenericMethodAnalysisContext xConcrete && y is ConcreteGenericMethodAnalysisContext yConcrete) + { + return Equals(xConcrete.BaseMethodContext, yConcrete.BaseMethodContext) + && xConcrete.TypeGenericParameters.SequenceEqual(yConcrete.TypeGenericParameters, TypeAnalysisContextEqualityComparer.Instance) + && xConcrete.MethodGenericParameters.SequenceEqual(yConcrete.MethodGenericParameters, TypeAnalysisContextEqualityComparer.Instance); + } + + return false; // Non-generic methods have unique instances. + } + + public bool Equals((MethodAnalysisContext, MethodAnalysisContext) x, (MethodAnalysisContext, MethodAnalysisContext) y) + { + return Equals(x.Item1, y.Item1) && Equals(x.Item2, y.Item2); + } + + public int GetHashCode(MethodAnalysisContext obj) + { + return (obj as ConcreteGenericMethodAnalysisContext)?.BaseMethodContext.GetHashCode() ?? obj.GetHashCode(); + } + + public int GetHashCode((MethodAnalysisContext, MethodAnalysisContext) obj) + { + return HashCode.Combine(GetHashCode(obj.Item1), GetHashCode(obj.Item2)); + } + } +} diff --git a/Il2CppInterop.Generator/InvalidFieldRemovalProcessingLayer.cs b/Il2CppInterop.Generator/InvalidFieldRemovalProcessingLayer.cs new file mode 100644 index 00000000..e90573c8 --- /dev/null +++ b/Il2CppInterop.Generator/InvalidFieldRemovalProcessingLayer.cs @@ -0,0 +1,51 @@ +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class InvalidFieldRemovalProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Invalid Field Removal"; + public override string Id => "invalid_field_removal"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + // C# allows developers to define explicit layout structs with reference type fields, but these are invalid in .NET when the field overlaps with another field. + // This processing layer handles the most common case of this - when there are multiple fields at the same offset, and at least one of them is a reference type. + // https://github.com/rlabrecque/Steamworks.NET/blob/55a9fc8eb4df4dd4cfc11b23003a5ea987317cae/CodeGen/templates/custom_types/SteamDatagramTickets/SteamDatagramRelayAuthTicket.cs#L98-L110 + + Dictionary offsetsToFieldCount = new(); + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + foreach (var type in assembly.Types) + { + if (type.IsInjected || !type.IsValueType || (type.Attributes & TypeAttributes.ExplicitLayout) == 0) + continue; + + offsetsToFieldCount.Clear(); + foreach (var field in type.Fields) + { + if (field.IsStatic) + continue; + var offset = field.Offset; + offsetsToFieldCount[offset] = offsetsToFieldCount.GetValueOrDefault(offset, 0) + 1; + } + + for (var i = type.Fields.Count - 1; i >= 0; i--) + { + var field = type.Fields[i]; + if (field.IsStatic) + continue; + + var offset = field.Offset; + if (offsetsToFieldCount[offset] > 1 && field.FieldType is { IsValueType: false } and not PointerTypeAnalysisContext) + { + type.Fields.RemoveAt(i); + } + } + } + } + } +} diff --git a/Il2CppInterop.Generator/InvisibleInterfaceProcessingLayer.cs b/Il2CppInterop.Generator/InvisibleInterfaceProcessingLayer.cs new file mode 100644 index 00000000..b07d86b0 --- /dev/null +++ b/Il2CppInterop.Generator/InvisibleInterfaceProcessingLayer.cs @@ -0,0 +1,40 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class InvisibleInterfaceProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Invisible Interface Processor"; + public override string Id => "invisible_interface_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + foreach (var method in type.Methods) + { + foreach (var overriddenMethod in method.Overrides) + { + if (overriddenMethod.DeclaringType is not { IsInterface: true } @interface) + { + continue; + } + + if (!type.ImplementsInterface(@interface)) + { + type.InterfaceContexts.Add(@interface); + } + } + } + } + } + } +} diff --git a/Il2CppInterop.Generator/KnownTypeAssignmentProcessingLayer.cs b/Il2CppInterop.Generator/KnownTypeAssignmentProcessingLayer.cs new file mode 100644 index 00000000..622caf92 --- /dev/null +++ b/Il2CppInterop.Generator/KnownTypeAssignmentProcessingLayer.cs @@ -0,0 +1,28 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public sealed class KnownTypeAssignmentProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Known Type Assignment"; + public override string Id => "known_type_assignment"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var knownType in Enum.GetValues()) + { + if (knownType is KnownTypeCode.None or KnownTypeCode.Il2CppSystem_IObject or KnownTypeCode.Il2CppSystem_IValueType or KnownTypeCode.Il2CppSystem_IEnum) + continue; + + var typeName = knownType.ToString().Replace('_', '.'); + if (typeName.StartsWith("Il2Cpp", StringComparison.Ordinal)) + { + appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow(typeName).KnownType = knownType; + } + else + { + appContext.Mscorlib.GetTypeByFullNameOrThrow(typeName).KnownType = knownType; + } + } + } +} diff --git a/Il2CppInterop.Generator/KnownTypeCode.cs b/Il2CppInterop.Generator/KnownTypeCode.cs new file mode 100644 index 00000000..6845011f --- /dev/null +++ b/Il2CppInterop.Generator/KnownTypeCode.cs @@ -0,0 +1,159 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public enum KnownTypeCode +{ + None, + System_Object, + System_ValueType, + System_Enum, + System_Attribute, + System_String, + System_Boolean, + System_Byte, + System_SByte, + System_Int16, + System_UInt16, + System_Int32, + System_UInt32, + System_Int64, + System_UInt64, + System_Single, + System_Double, + System_Char, + System_IntPtr, + System_UIntPtr, + System_Void, + System_Array, + Il2CppSystem_Object, + Il2CppSystem_ValueType, + Il2CppSystem_Enum, + Il2CppSystem_Attribute, + Il2CppSystem_String, + Il2CppSystem_Boolean, + Il2CppSystem_Byte, + Il2CppSystem_SByte, + Il2CppSystem_Int16, + Il2CppSystem_UInt16, + Il2CppSystem_Int32, + Il2CppSystem_UInt32, + Il2CppSystem_Int64, + Il2CppSystem_UInt64, + Il2CppSystem_Single, + Il2CppSystem_Double, + Il2CppSystem_Char, + Il2CppSystem_IntPtr, + Il2CppSystem_UIntPtr, + Il2CppSystem_Void, + Il2CppSystem_IObject, + Il2CppSystem_IValueType, + Il2CppSystem_IEnum, +} +internal static class KnownTypeCodeExtensions +{ + extension(KnownTypeCode code) + { + public bool IsSystemType => code >= KnownTypeCode.System_Object && code <= KnownTypeCode.System_Array; + + public bool IsIl2CppSystemType => code >= KnownTypeCode.Il2CppSystem_Object && code <= KnownTypeCode.Il2CppSystem_IEnum; + + /// + /// Boolean, Char, and all the numeric types + /// + public bool IsIl2CppPrimitiveType => code >= KnownTypeCode.Il2CppSystem_Boolean && code <= KnownTypeCode.Il2CppSystem_UIntPtr; + + public string Name => code switch + { + KnownTypeCode.None => "", + KnownTypeCode.System_Object or KnownTypeCode.Il2CppSystem_Object => "Object", + KnownTypeCode.System_ValueType or KnownTypeCode.Il2CppSystem_ValueType => "ValueType", + KnownTypeCode.System_Enum or KnownTypeCode.Il2CppSystem_Enum => "Enum", + KnownTypeCode.System_Attribute or KnownTypeCode.Il2CppSystem_Attribute => "Attribute", + KnownTypeCode.System_String or KnownTypeCode.Il2CppSystem_String => "String", + KnownTypeCode.System_Boolean or KnownTypeCode.Il2CppSystem_Boolean => "Boolean", + KnownTypeCode.System_Byte or KnownTypeCode.Il2CppSystem_Byte => "Byte", + KnownTypeCode.System_SByte or KnownTypeCode.Il2CppSystem_SByte => "SByte", + KnownTypeCode.System_Int16 or KnownTypeCode.Il2CppSystem_Int16 => "Int16", + KnownTypeCode.System_UInt16 or KnownTypeCode.Il2CppSystem_UInt16 => "UInt16", + KnownTypeCode.System_Int32 or KnownTypeCode.Il2CppSystem_Int32 => "Int32", + KnownTypeCode.System_UInt32 or KnownTypeCode.Il2CppSystem_UInt32 => "UInt32", + KnownTypeCode.System_Int64 or KnownTypeCode.Il2CppSystem_Int64 => "Int64", + KnownTypeCode.System_UInt64 or KnownTypeCode.Il2CppSystem_UInt64 => "UInt64", + KnownTypeCode.System_Single or KnownTypeCode.Il2CppSystem_Single => "Single", + KnownTypeCode.System_Double or KnownTypeCode.Il2CppSystem_Double => "Double", + KnownTypeCode.System_Char or KnownTypeCode.Il2CppSystem_Char => "Char", + KnownTypeCode.System_IntPtr or KnownTypeCode.Il2CppSystem_IntPtr => "IntPtr", + KnownTypeCode.System_UIntPtr or KnownTypeCode.Il2CppSystem_UIntPtr => "UIntPtr", + KnownTypeCode.System_Void or KnownTypeCode.Il2CppSystem_Void => "Void", + KnownTypeCode.System_Array => "Array", + KnownTypeCode.Il2CppSystem_IObject => "IObject", + KnownTypeCode.Il2CppSystem_IValueType => "IValueType", + KnownTypeCode.Il2CppSystem_IEnum => "IEnum", + _ => throw new InvalidOperationException($"Unknown KnownTypeCode: {code}") + }; + + public string Namespace + { + get + { + if (code.IsSystemType) + return "System"; + if (code.IsIl2CppSystemType) + return "Il2CppSystem"; + return ""; + } + } + + public string FullName + { + get + { + var @namespace = code.Namespace; + return string.IsNullOrEmpty(@namespace) ? code.Name : $"{@namespace}.{code.Name}"; + } + } + + public KnownTypeCode ToSystemType() => code switch + { + KnownTypeCode.Il2CppSystem_Object => KnownTypeCode.System_Object, + KnownTypeCode.Il2CppSystem_ValueType => KnownTypeCode.System_ValueType, + KnownTypeCode.Il2CppSystem_Enum => KnownTypeCode.System_Enum, + KnownTypeCode.Il2CppSystem_Attribute => KnownTypeCode.System_Attribute, + KnownTypeCode.Il2CppSystem_String => KnownTypeCode.System_String, + KnownTypeCode.Il2CppSystem_Boolean => KnownTypeCode.System_Boolean, + KnownTypeCode.Il2CppSystem_Byte => KnownTypeCode.System_Byte, + KnownTypeCode.Il2CppSystem_SByte => KnownTypeCode.System_SByte, + KnownTypeCode.Il2CppSystem_Int16 => KnownTypeCode.System_Int16, + KnownTypeCode.Il2CppSystem_UInt16 => KnownTypeCode.System_UInt16, + KnownTypeCode.Il2CppSystem_Int32 => KnownTypeCode.System_Int32, + KnownTypeCode.Il2CppSystem_UInt32 => KnownTypeCode.System_UInt32, + KnownTypeCode.Il2CppSystem_Int64 => KnownTypeCode.System_Int64, + KnownTypeCode.Il2CppSystem_UInt64 => KnownTypeCode.System_UInt64, + KnownTypeCode.Il2CppSystem_Single => KnownTypeCode.System_Single, + KnownTypeCode.Il2CppSystem_Double => KnownTypeCode.System_Double, + KnownTypeCode.Il2CppSystem_Char => KnownTypeCode.System_Char, + KnownTypeCode.Il2CppSystem_IntPtr => KnownTypeCode.System_IntPtr, + KnownTypeCode.Il2CppSystem_UIntPtr => KnownTypeCode.System_UIntPtr, + KnownTypeCode.Il2CppSystem_Void => KnownTypeCode.System_Void, + KnownTypeCode.Il2CppSystem_IObject => KnownTypeCode.System_Object, + KnownTypeCode.Il2CppSystem_IValueType => KnownTypeCode.System_ValueType, + KnownTypeCode.Il2CppSystem_IEnum => KnownTypeCode.System_Enum, + _ => code + }; + + public AssemblyAnalysisContext GetAssemblyContext(ApplicationAnalysisContext appContext) + { + if (code.IsSystemType) + return appContext.Mscorlib; + if (code.IsIl2CppSystemType) + return appContext.Il2CppMscorlib; + throw new InvalidOperationException($"KnownTypeCode {code} does not correspond to a known assembly"); + } + + public TypeAnalysisContext ToContext(ApplicationAnalysisContext appContext) + { + return code.GetAssemblyContext(appContext).GetTypeByFullNameOrThrow(code.FullName); + } + } +} diff --git a/Il2CppInterop.Generator/MarshallingProcessingLayer.cs b/Il2CppInterop.Generator/MarshallingProcessingLayer.cs new file mode 100644 index 00000000..a0f9399c --- /dev/null +++ b/Il2CppInterop.Generator/MarshallingProcessingLayer.cs @@ -0,0 +1,444 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class MarshallingProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Marshalling Processor"; + public override string Id => "marshalling_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var iil2CppType = appContext.ResolveTypeOrThrow(typeof(IIl2CppType)); + var iil2CppType_get_ObjectClass = iil2CppType.GetMethodByName($"get_{nameof(IIl2CppType.ObjectClass)}"); + + var iil2CppTypeGeneric = appContext.ResolveTypeOrThrow(typeof(IIl2CppType<>)); + var iil2CppTypeGeneric_get_Size = iil2CppTypeGeneric.GetMethodByName($"get_{nameof(IIl2CppType<>.Size)}"); + var iil2CppTypeGeneric_get_AssemblyName = iil2CppTypeGeneric.GetMethodByName($"get_{nameof(IIl2CppType<>.AssemblyName)}"); + var iil2CppTypeGeneric_get_Namespace = iil2CppTypeGeneric.GetMethodByName($"get_{nameof(IIl2CppType<>.Namespace)}"); + var iil2CppTypeGeneric_get_Name = iil2CppTypeGeneric.GetMethodByName($"get_{nameof(IIl2CppType<>.Name)}"); + var iil2CppTypeGeneric_ReadFromSpan = iil2CppTypeGeneric.GetMethodByName(nameof(IIl2CppType<>.ReadFromSpan)); + var iil2CppTypeGeneric_WriteToSpan = iil2CppTypeGeneric.GetMethodByName(nameof(IIl2CppType<>.WriteToSpan)); + + var il2CppTypeHelper = appContext.ResolveTypeOrThrow(typeof(Il2CppType)); + var il2CppTypeHelper_ReadFromSpanAtOffset = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.ReadFromSpanAtOffset)); + var il2CppTypeHelper_WriteToSpanAtOffset = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.WriteToSpanAtOffset)); + var il2CppTypeHelper_ReadFromSpanBlittable = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.ReadFromSpanBlittable)); + var il2CppTypeHelper_WriteToSpanBlittable = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.WriteToSpanBlittable)); + var il2CppTypeHelper_WriteToSpan = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.WriteToSpan)); + var il2CppTypeHelper_GetClassPointer = il2CppTypeHelper.Methods.Single(m => m.Name == nameof(Il2CppType.GetClassPointer) && m.GenericParameters.Count == 1); + + var il2CppSystemIObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IObject"); + var il2CppSystemIValueType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IValueType"); + var il2CppSystemIEnum = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IEnum"); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected && type != il2CppSystemIObject && type != il2CppSystemIValueType && type != il2CppSystemIEnum) + continue; + + type.InterfaceContexts.Add(iil2CppType); + + var instantiatedType = type.SelfInstantiateIfGeneric(); + var instantiatedIl2CppTypeGeneric = iil2CppTypeGeneric.MakeGenericInstanceType([instantiatedType]); + type.InterfaceContexts.Add(instantiatedIl2CppTypeGeneric); + + TypeAnalysisContext nameReferenceType; + TypeAnalysisContext classReferenceType; + if (type == il2CppSystemIObject) + { + nameReferenceType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + classReferenceType = nameReferenceType; + } + else if (type == il2CppSystemIValueType) + { + nameReferenceType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.ValueType"); + classReferenceType = nameReferenceType; + } + else if (type == il2CppSystemIEnum) + { + nameReferenceType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Enum"); + classReferenceType = nameReferenceType; + } + else + { + nameReferenceType = type; + classReferenceType = instantiatedType; + } + + // ObjectClass + { + var methodName = $"{iil2CppType.FullName}.get_{nameof(IIl2CppType.ObjectClass)}"; + var method = new InjectedMethodAnalysisContext( + type, + methodName, + iil2CppType_get_ObjectClass.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.SpecialName | (type.IsInterface ? MethodAttributes.ReuseSlot : MethodAttributes.NewSlot), + []) + { + IsInjected = true, + }; + type.Methods.Add(method); + method.Overrides.Add(iil2CppType_get_ObjectClass); + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Call, il2CppTypeHelper_GetClassPointer.MakeGenericInstanceMethod(classReferenceType)), + new Instruction(CilOpCodes.Ret), + ], + }); + + var propertyName = $"{iil2CppType.FullName}.{nameof(IIl2CppType.ObjectClass)}"; + var property = new InjectedPropertyAnalysisContext( + propertyName, + method.ReturnType, + method, + null, + PropertyAttributes.None, + type) + { + IsInjected = true, + }; + type.Properties.Add(property); + } + + // Size + { + var instantiatedSizeStorage = type.SizeStorage is null + ? null + : type.GenericParameters.Count > 0 + ? type.SizeStorage!.MakeConcreteGeneric(type.GenericParameters) + : type.SizeStorage; + + // IIl2CppType.Size + if (instantiatedSizeStorage is null) + { + // Reference types have a default implementation that returns IntPtr.Size, so we don't need to do anything. + Debug.Assert(!type.IsValueType); + } + else + { + Debug.Assert(type.IsValueType); + var instantiated_iil2CppTypeGeneric_get_Size = new ConcreteGenericMethodAnalysisContext(iil2CppTypeGeneric_get_Size, [instantiatedType], []); + var methodName = $"{instantiatedIl2CppTypeGeneric.FullName}.get_{nameof(IIl2CppType<>.Size)}"; + var method = new InjectedMethodAnalysisContext( + type, + methodName, + instantiated_iil2CppTypeGeneric_get_Size.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + []) + { + IsInjected = true, + }; + type.Methods.Add(method); + method.Overrides.Add(instantiated_iil2CppTypeGeneric_get_Size); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldsfld, instantiatedSizeStorage), + new Instruction(CilOpCodes.Ret), + ], + }); + + var propertyName = $"{instantiatedIl2CppTypeGeneric.FullName}.{nameof(IIl2CppType<>.Size)}"; + var property = new InjectedPropertyAnalysisContext( + propertyName, + method.ReturnType, + method, + null, + PropertyAttributes.None, + type) + { + IsInjected = true, + }; + type.Properties.Add(property); + } + } + + // AssemblyName + if (assembly.Name != assembly.ImageName) + { + var instantiated_iil2CppTypeGeneric_get_AssemblyName = new ConcreteGenericMethodAnalysisContext(iil2CppTypeGeneric_get_AssemblyName, [instantiatedType], []); + var methodName = $"{instantiatedIl2CppTypeGeneric.FullName}.get_{nameof(IIl2CppType<>.AssemblyName)}"; + var method = new InjectedMethodAnalysisContext( + type, + methodName, + instantiated_iil2CppTypeGeneric_get_AssemblyName.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + []) + { + IsInjected = true, + }; + type.Methods.Add(method); + method.Overrides.Add(instantiated_iil2CppTypeGeneric_get_AssemblyName); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldstr, assembly.ImageName), + new Instruction(CilOpCodes.Ret), + ], + }); + + var propertyName = $"{instantiatedIl2CppTypeGeneric.FullName}.{nameof(IIl2CppType<>.AssemblyName)}"; + var property = new InjectedPropertyAnalysisContext( + propertyName, + method.ReturnType, + method, + null, + PropertyAttributes.None, + type) + { + IsInjected = true, + }; + type.Properties.Add(property); + } + + // Namespace + if (type.Namespace != nameReferenceType.DefaultNamespace) + { + var instantiated_iil2CppTypeGeneric_get_Namespace = new ConcreteGenericMethodAnalysisContext(iil2CppTypeGeneric_get_Namespace, [instantiatedType], []); + var methodName = $"{instantiatedIl2CppTypeGeneric.FullName}.get_{nameof(IIl2CppType<>.Namespace)}"; + var method = new InjectedMethodAnalysisContext( + type, + methodName, + instantiated_iil2CppTypeGeneric_get_Namespace.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + []) + { + IsInjected = true, + }; + type.Methods.Add(method); + method.Overrides.Add(instantiated_iil2CppTypeGeneric_get_Namespace); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldstr, nameReferenceType.DefaultNamespace), + new Instruction(CilOpCodes.Ret), + ], + }); + + var propertyName = $"{instantiatedIl2CppTypeGeneric.FullName}.{nameof(IIl2CppType<>.Namespace)}"; + var property = new InjectedPropertyAnalysisContext( + propertyName, + method.ReturnType, + method, + null, + PropertyAttributes.None, + type) + { + IsInjected = true, + }; + type.Properties.Add(property); + } + + // Name + if (type.Name != nameReferenceType.DefaultName) + { + var instantiated_iil2CppTypeGeneric_get_Name = new ConcreteGenericMethodAnalysisContext(iil2CppTypeGeneric_get_Name, [instantiatedType], []); + var methodName = $"{instantiatedIl2CppTypeGeneric.FullName}.get_{nameof(IIl2CppType<>.Name)}"; + var method = new InjectedMethodAnalysisContext( + type, + methodName, + instantiated_iil2CppTypeGeneric_get_Name.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName, + []) + { + IsInjected = true, + }; + type.Methods.Add(method); + method.Overrides.Add(instantiated_iil2CppTypeGeneric_get_Name); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldstr, nameReferenceType.DefaultName), + new Instruction(CilOpCodes.Ret), + ], + }); + + var propertyName = $"{instantiatedIl2CppTypeGeneric.FullName}.{nameof(IIl2CppType<>.Name)}"; + var property = new InjectedPropertyAnalysisContext( + propertyName, + method.ReturnType, + method, + null, + PropertyAttributes.None, + type) + { + IsInjected = true, + }; + type.Properties.Add(property); + } + + var instanceFieldCount = type.Fields.Count(f => !f.IsStatic); + + // ReadFromSpan + if (type.IsValueType) + { + var instantiated_iil2CppTypeGeneric_ReadFromSpan = new ConcreteGenericMethodAnalysisContext(iil2CppTypeGeneric_ReadFromSpan, [instantiatedType], []); + var methodName = $"{instantiatedIl2CppTypeGeneric.FullName}.{nameof(IIl2CppType<>.ReadFromSpan)}"; + var method = new InjectedMethodAnalysisContext( + type, + methodName, + instantiated_iil2CppTypeGeneric_ReadFromSpan.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + [instantiated_iil2CppTypeGeneric_ReadFromSpan.Parameters[0].ParameterType]) + { + IsInjected = true, + }; + type.Methods.Add(method); + method.Overrides.Add(instantiated_iil2CppTypeGeneric_ReadFromSpan); + + if (instanceFieldCount == 0) + { + LocalVariable local = new(instantiatedType); + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldloca, local), + new Instruction(CilOpCodes.Initobj, instantiatedType), + new Instruction(CilOpCodes.Ldloc, local), + new Instruction(CilOpCodes.Ret), + ], + LocalVariables = [local], + }); + } + else if (type.IsIl2CppPrimitive) + { + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, il2CppTypeHelper_ReadFromSpanBlittable.MakeGenericInstanceMethod(instantiatedType)), + new Instruction(CilOpCodes.Ret), + ], + }); + } + else + { + LocalVariable local = new(instantiatedType); + List instructions = new(2 + instanceFieldCount * 5 + 2) + { + new Instruction(CilOpCodes.Ldloca, local), + new Instruction(CilOpCodes.Initobj, instantiatedType) + }; + + foreach (var field in type.Fields) + { + if (field.IsStatic) + continue; + + Debug.Assert(!field.IsInjected); + Debug.Assert(field.OffsetStorage is not null); + + var instantiatedField = field.SelfInstantiateIfNecessary(); + + instructions.Add(new Instruction(CilOpCodes.Ldloca, local)); + instructions.Add(new Instruction(CilOpCodes.Ldarg_0)); + instructions.Add(new Instruction(CilOpCodes.Ldsfld, field.OffsetStorage!.MaybeMakeConcreteGeneric(type.GenericParameters))); + instructions.Add(new Instruction(CilOpCodes.Call, il2CppTypeHelper_ReadFromSpanAtOffset.MakeGenericInstanceMethod(instantiatedField.FieldType))); + instructions.Add(new Instruction(CilOpCodes.Stfld, instantiatedField)); + } + + instructions.Add(new Instruction(CilOpCodes.Ldloc, local)); + instructions.Add(new Instruction(CilOpCodes.Ret)); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = [local], + }); + } + } + + // WriteToSpan + if (type.IsValueType) + { + var instantiated_iil2CppTypeGeneric_WriteToSpan = new ConcreteGenericMethodAnalysisContext(iil2CppTypeGeneric_WriteToSpan, [instantiatedType], []); + var methodName = $"{instantiatedIl2CppTypeGeneric.FullName}.{nameof(IIl2CppType<>.WriteToSpan)}"; + var method = new InjectedMethodAnalysisContext( + type, + methodName, + instantiated_iil2CppTypeGeneric_WriteToSpan.ReturnType, + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + instantiated_iil2CppTypeGeneric_WriteToSpan.Parameters.Select(p => p.ParameterType).ToArray()) + { + IsInjected = true, + }; + type.Methods.Add(method); + method.Overrides.Add(instantiated_iil2CppTypeGeneric_WriteToSpan); + + if (instanceFieldCount == 0) + { + // Struct with no instance fields - nothing to do. + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ret), + ], + }); + } + else if (type.IsIl2CppPrimitive) + { + method.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, il2CppTypeHelper_WriteToSpanBlittable.MakeGenericInstanceMethod(instantiatedType)), + new Instruction(CilOpCodes.Ret), + ], + }); + } + else + { + List instructions = new(instanceFieldCount * 5 + 1); + foreach (var field in type.Fields) + { + if (field.IsStatic) + continue; + + Debug.Assert(!field.IsInjected); + Debug.Assert(field.OffsetStorage is not null); + + var instantiatedField = field.SelfInstantiateIfNecessary(); + + instructions.Add(new Instruction(CilOpCodes.Ldarga, method.Parameters[0])); + instructions.Add(new Instruction(CilOpCodes.Ldfld, instantiatedField)); + instructions.Add(new Instruction(CilOpCodes.Ldarg_1)); + instructions.Add(new Instruction(CilOpCodes.Ldsfld, field.OffsetStorage!.MaybeMakeConcreteGeneric(type.GenericParameters))); + instructions.Add(new Instruction(CilOpCodes.Call, il2CppTypeHelper_WriteToSpanAtOffset.MakeGenericInstanceMethod(instantiatedField.FieldType))); + } + instructions.Add(new Instruction(CilOpCodes.Ret)); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + }); + } + } + } + } + } +} diff --git a/Il2CppInterop.Generator/MemberAttributeProcessingLayer.cs b/Il2CppInterop.Generator/MemberAttributeProcessingLayer.cs new file mode 100644 index 00000000..28523625 --- /dev/null +++ b/Il2CppInterop.Generator/MemberAttributeProcessingLayer.cs @@ -0,0 +1,78 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Model.CustomAttributes; +using Il2CppInterop.Common.Attributes; + +namespace Il2CppInterop.Generator; + +public class MemberAttributeProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Member Attribute Processor"; + + public override string Id => "member_attribute_processor"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + // This layer is responsible for adding Il2CppMethodAttribute and Il2CppPropertyAttribute to methods and properties. + // Fields are handled in FieldAccessorProcessingLayer + // Events are handled in EventProcessingLayer + + var il2CppMethodAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppMethodAttribute)); + var il2CppMethodAttributeConstructor = il2CppMethodAttribute.GetMethodByName(".ctor"); + var il2CppMethodAttributeIndex = il2CppMethodAttribute.GetPropertyByName(nameof(Il2CppMethodAttribute.Index)); + + var il2CppPropertyAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppPropertyAttribute)); + var il2CppPropertyAttributeConstructor = il2CppPropertyAttribute.GetMethodByName(".ctor"); + + var il2CppMemberAttribute = appContext.ResolveTypeOrThrow(typeof(Il2CppMemberAttribute)); + var il2CppMemberAttributeName = il2CppMemberAttribute.GetPropertyByName(nameof(Il2CppMemberAttribute.Name)); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + foreach (var method in type.Methods) + { + if (method.IsInjected) + continue; + + var attribute = new AnalyzedCustomAttribute(il2CppMethodAttributeConstructor); + if (method.Name != method.DefaultName) + { + var parameter = new CustomAttributePrimitiveParameter(method.DefaultName, attribute, CustomAttributeParameterKind.Property, attribute.Properties.Count); + attribute.Properties.Add(new CustomAttributeProperty(il2CppMemberAttributeName, parameter)); + } + var index = method.InitializationClassIndex; + if (index >= 0) + { + var parameter = new CustomAttributePrimitiveParameter(index, attribute, CustomAttributeParameterKind.Property, attribute.Properties.Count); + attribute.Properties.Add(new CustomAttributeProperty(il2CppMethodAttributeIndex, parameter)); + } + method.CustomAttributes ??= new(1); + method.CustomAttributes.Add(attribute); + } + + foreach (var property in type.Properties) + { + if (property.IsInjected) + continue; + + var attribute = new AnalyzedCustomAttribute(il2CppPropertyAttributeConstructor); + if (property.Name != property.DefaultName) + { + var parameter = new CustomAttributePrimitiveParameter(property.DefaultName, attribute, CustomAttributeParameterKind.Property, 0); + attribute.Properties.Add(new CustomAttributeProperty(il2CppMemberAttributeName, parameter)); + } + property.CustomAttributes ??= new(1); + property.CustomAttributes.Add(attribute); + } + } + } + } +} diff --git a/Il2CppInterop.Generator/MetadataAccess/AssemblyMetadataAccess.cs b/Il2CppInterop.Generator/MetadataAccess/AssemblyMetadataAccess.cs deleted file mode 100644 index e0cbd108..00000000 --- a/Il2CppInterop.Generator/MetadataAccess/AssemblyMetadataAccess.cs +++ /dev/null @@ -1,77 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.MetadataAccess; - -public class AssemblyMetadataAccess : IIl2CppMetadataAccess -{ - private readonly Il2CppAssemblyResolver myAssemblyResolver = new(); - private readonly List myAssemblies = new(); - private readonly Dictionary myAssembliesByName = new(); - private readonly Dictionary<(string AssemblyName, string TypeName), TypeDefinition> myTypesByName = new(); - - public AssemblyMetadataAccess(IEnumerable assemblyPaths) - { - Load(assemblyPaths.Select(AssemblyDefinition.FromFile)); - } - - public AssemblyMetadataAccess(IEnumerable assemblies) - { - // Note: At the moment this assumes that passed assemblies have their own assembly resolver set up - // If this is not true, this can cause issues with reference resolving - Load(assemblies); - } - - public void Dispose() - { - myAssemblyResolver.ClearCache(); - myAssemblies.Clear(); - myAssembliesByName.Clear(); - } - - public AssemblyDefinition? GetAssemblyBySimpleName(string name) - { - return myAssembliesByName.TryGetValue(name, out var result) ? result : null; - } - - public TypeDefinition? GetTypeByName(string assemblyName, string typeName) - { - return myTypesByName.TryGetValue((assemblyName, typeName), out var result) ? result : null; - } - - public IList Assemblies => myAssemblies; - - public IList? GetKnownInstantiationsFor(TypeDefinition genericDeclaration) - { - return null; - } - - public string? GetStringStoredAtAddress(long offsetInMemory) - { - return null; - } - - public MemberReference? GetMethodRefStoredAt(long offsetInMemory) - { - return null; - } - - private void Load(IEnumerable assemblies) - { - foreach (var sourceAssembly in assemblies) - { - myAssemblies.Add(sourceAssembly); - myAssembliesByName[sourceAssembly.Name!] = sourceAssembly; - sourceAssembly.ManifestModule!.MetadataResolver = new DefaultMetadataResolver(myAssemblyResolver); - myAssemblyResolver.AddToCache(sourceAssembly); - } - - foreach (var sourceAssembly in myAssemblies) - { - var sourceAssemblyName = sourceAssembly.Name!; - foreach (var type in sourceAssembly.ManifestModule!.TopLevelTypes) - // todo: nested types? - myTypesByName[(sourceAssemblyName, type.FullName)] = type; - } - } -} diff --git a/Il2CppInterop.Generator/MetadataAccess/IIl2CppMetadataAccess.cs b/Il2CppInterop.Generator/MetadataAccess/IIl2CppMetadataAccess.cs deleted file mode 100644 index f4f09d26..00000000 --- a/Il2CppInterop.Generator/MetadataAccess/IIl2CppMetadataAccess.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.MetadataAccess; - -public interface IIl2CppMetadataAccess : IMetadataAccess -{ - IList? GetKnownInstantiationsFor(TypeDefinition genericDeclaration); - string? GetStringStoredAtAddress(long offsetInMemory); - MemberReference? GetMethodRefStoredAt(long offsetInMemory); -} diff --git a/Il2CppInterop.Generator/MetadataAccess/IMetadataAccess.cs b/Il2CppInterop.Generator/MetadataAccess/IMetadataAccess.cs deleted file mode 100644 index 63f0b585..00000000 --- a/Il2CppInterop.Generator/MetadataAccess/IMetadataAccess.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AsmResolver.DotNet; - -namespace Il2CppInterop.Generator.MetadataAccess; - -public interface IMetadataAccess : IDisposable -{ - IList Assemblies { get; } - - AssemblyDefinition? GetAssemblyBySimpleName(string name); - TypeDefinition? GetTypeByName(string assemblyName, string typeName); -} diff --git a/Il2CppInterop.Generator/MetadataAccess/Il2CppAssemblyResolver.cs b/Il2CppInterop.Generator/MetadataAccess/Il2CppAssemblyResolver.cs deleted file mode 100644 index 8874998a..00000000 --- a/Il2CppInterop.Generator/MetadataAccess/Il2CppAssemblyResolver.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.IO; - -namespace Il2CppInterop.Generator.MetadataAccess; - -internal sealed class Il2CppAssemblyResolver : AssemblyResolverBase -{ - public Il2CppAssemblyResolver() : base(new ByteArrayFileService()) - { - } - - protected override string? ProbeRuntimeDirectories(AssemblyDescriptor assembly) => null; - - public void AddToCache(AssemblyDefinition assembly) - { - AddToCache(assembly, assembly); - } -} diff --git a/Il2CppInterop.Generator/MetadataAccess/NullMetadataAccess.cs b/Il2CppInterop.Generator/MetadataAccess/NullMetadataAccess.cs deleted file mode 100644 index dc1e4682..00000000 --- a/Il2CppInterop.Generator/MetadataAccess/NullMetadataAccess.cs +++ /dev/null @@ -1,40 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.MetadataAccess; - -public class NullMetadataAccess : IMetadataAccess -{ - public static readonly NullMetadataAccess Instance = new(); - - public void Dispose() - { - } - - public IList Assemblies => Array.Empty(); - - public AssemblyDefinition? GetAssemblyBySimpleName(string name) - { - return null; - } - - public TypeDefinition? GetTypeByName(string assemblyName, string typeName) - { - return null; - } - - public IList? GetKnownInstantiationsFor(TypeReference genericDeclaration) - { - return null; - } - - public string? GetStringStoredAtAddress(long offsetInMemory) - { - return null; - } - - public MemberReference? GetMethodRefStoredAt(long offsetInMemory) - { - return null; - } -} diff --git a/Il2CppInterop.Generator/MethodBodyBase.cs b/Il2CppInterop.Generator/MethodBodyBase.cs new file mode 100644 index 00000000..3810e437 --- /dev/null +++ b/Il2CppInterop.Generator/MethodBodyBase.cs @@ -0,0 +1,492 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AssetRipper.CIL; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils.AsmResolver; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.StackTypes; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace Il2CppInterop.Generator; + +public abstract class MethodBodyBase +{ + public required IReadOnlyList Instructions { get; init; } + public IReadOnlyList LocalVariables { get; init; } = []; + public IReadOnlyList ExceptionHandlers { get; init; } = []; + + public void FillMethodBody(MethodDefinition method) + { + if (!method.IsManagedMethodWithBody()) + { + return; + } + + var body = new CilMethodBody() + { + InitializeLocals = LocalVariables.Count > 0 + }; + method.CilMethodBody = body; + var module = method.DeclaringModule!; + var instructions = body.Instructions; + + var labels = new Dictionary(Instructions.Count + 1); + for (var i = Instructions.Count - 1; i >= 0; i--) + { + labels.Add(Instructions[i], new CilInstructionLabel()); + } + labels.Add(EndLabel.Instance, instructions.EndLabel); + + foreach (var exceptionHandler in ExceptionHandlers) + { + var handler = new CilExceptionHandler + { + HandlerType = exceptionHandler.HandlerType, + TryStart = labels.GetValue(exceptionHandler.TryStart), + TryEnd = labels.GetValue(exceptionHandler.TryEnd), + HandlerStart = labels.GetValue(exceptionHandler.HandlerStart), + HandlerEnd = labels.GetValue(exceptionHandler.HandlerEnd) ?? instructions.EndLabel, + FilterStart = labels.GetValue(exceptionHandler.FilterStart), + ExceptionType = exceptionHandler.ExceptionType?.ToTypeSignature(module).ToTypeDefOrRef(), + }; + body.ExceptionHandlers.Add(handler); + } + + Dictionary localVariableMap = new(LocalVariables.Count); + for (var i = LocalVariables.Count - 1; i >= 0; i--) + { + var localVariable = LocalVariables[i]; + var localVariableType = localVariable.Type.ToTypeSignature(module); + var value = new CilLocalVariable(localVariableType); + body.LocalVariables.Add(value); + localVariableMap.Add(localVariable, value); + } + + for (var i = 0; i < Instructions.Count; i++) + { + var instruction = Instructions[i]; + var operand = instruction.Operand switch + { + This => method.Parameters.ThisParameter ?? throw new NullReferenceException("This parameter should not be null."), + LocalVariable localVariable => localVariableMap[localVariable], + TypeAnalysisContext type => type.ToTypeSignature(module).ToTypeDefOrRef(), + MethodAnalysisContext methodOperand => methodOperand.ToMethodDescriptor(module), + FieldAnalysisContext field => field.ToFieldDescriptor(module), + ParameterAnalysisContext parameter => method.Parameters[parameter.ParameterIndex], + MultiDimensionalArrayMethod arrayMethod => arrayMethod.ToMethodDescriptor(module), + ILabel label => labels[label], + IReadOnlyList labelArray => labelArray.Select(labels.GetValue).ToArray(), + _ => instruction.Operand, + }; + var cilInstruction = new CilInstruction(instruction.Code, operand); + instructions.Add(cilInstruction); + ((CilInstructionLabel)labels[instruction]).Instruction = cilInstruction; + } + + instructions.OptimizeMacros(); + } + + internal void AnalyzeControlFlow(out Dictionary> instructionPredecessors, out Dictionary> instructionSuccessors) + { + instructionPredecessors = new Dictionary>(Instructions.Count); + instructionSuccessors = new Dictionary>(Instructions.Count); + foreach (var instruction in Instructions) + { + instructionPredecessors[instruction] = []; + instructionSuccessors[instruction] = []; + } + for (var i = 0; i < Instructions.Count; i++) + { + var instruction = Instructions[i]; + if (instruction.Code.FlowControl is CilFlowControl.Return or CilFlowControl.Throw) + { + // No successors. + } + else if (instruction.Operand is ILabel label) + { + Debug.Assert(instruction.Code.FlowControl is CilFlowControl.Branch or CilFlowControl.ConditionalBranch); + Instruction? targetInstruction = label as Instruction; + if (targetInstruction is not null) + { + instructionSuccessors[instruction].Add(targetInstruction); + instructionPredecessors[targetInstruction].Add(instruction); + } + + if (instruction.Code.Code is CilCode.Br or CilCode.Leave) + { + // Unconditional branches do not have fallthrough. + } + else if (i + 1 >= Instructions.Count) + { + // No fallthrough, this is the last instruction. + } + else if (Instructions[i + 1] == targetInstruction) + { + // Do nothing, already added above. + } + else + { + var nextInstruction = Instructions[i + 1]; + instructionSuccessors[instruction].Add(nextInstruction); + instructionPredecessors[nextInstruction].Add(instruction); + } + } + else if (instruction.Operand is IReadOnlyList labels) + { + Debug.Assert(instruction.Code.FlowControl is CilFlowControl.ConditionalBranch); + HashSet targetInstructions = new(); + foreach (var label1 in labels) + { + if (label1 is Instruction targetInstruction && targetInstructions.Add(targetInstruction)) + { + instructionSuccessors[instruction].Add(targetInstruction); + instructionPredecessors[targetInstruction].Add(instruction); + } + } + if (i + 1 < Instructions.Count) + { + var nextInstruction = Instructions[i + 1]; + if (!targetInstructions.Contains(nextInstruction)) + { + instructionSuccessors[instruction].Add(nextInstruction); + instructionPredecessors[nextInstruction].Add(instruction); + } + } + } + else + { + Debug.Assert(instruction.Code.FlowControl is CilFlowControl.Break or CilFlowControl.Call or CilFlowControl.Meta or CilFlowControl.Next); + if (i + 1 < Instructions.Count) + { + var nextInstruction = Instructions[i + 1]; + instructionSuccessors[instruction].Add(nextInstruction); + instructionPredecessors[nextInstruction].Add(instruction); + } + } + } + } + + internal Dictionary AnalyzeStackTypes(MethodAnalysisContext owner, TypeReplacementVisitor? visitor = null, bool il2CppTypes = false) + { + AnalyzeControlFlow(out var instructionPredecessors, out var instructionSuccessors); + + Dictionary result = new(Instructions.Count); + + if (Instructions.Count == 0) + { + return result; + } + + visitor ??= TypeReplacementVisitor.Null; + + result[Instructions[0]] = []; + + foreach (var exceptionHandler in ExceptionHandlers) + { + if (exceptionHandler.HandlerType is CilExceptionHandlerType.Finally) + { + if (exceptionHandler.HandlerStart is Instruction instruction) + { + Debug.Assert(instructionPredecessors[instruction].Count == 0); + result.Add(instruction, []); + } + } + else + { + StackType exceptionType = exceptionHandler.ExceptionType is null ? UnknownStackType.Instance : GetExactType(exceptionHandler.ExceptionType, visitor); + + if (exceptionHandler.HandlerStart is Instruction handlerInstruction) + { + Debug.Assert(instructionPredecessors[handlerInstruction].Count == 0); + result.Add(handlerInstruction, [exceptionType]); + } + + if (exceptionHandler.FilterStart is Instruction filterInstruction) + { + Debug.Assert(instructionPredecessors[filterInstruction].Count == 0); + result.Add(filterInstruction, [exceptionType]); + } + } + } + + bool changed; + do + { + changed = false; + + foreach (var instruction in Instructions) + { + if (!result.TryGetValue(instruction, out var stackInitial)) + { + continue; + } + + var popCount = instruction.GetPopCount(owner); + if (popCount is -1) + { + // -1 is special case for "pop all" + popCount = stackInitial.Length; + } + var stackAfterPop = stackInitial.AsSpan(0, stackInitial.Length - popCount); + + var poppedTypes = stackInitial.AsSpan(stackAfterPop.Length, stackInitial.Length - stackAfterPop.Length); + + var pushCount = instruction.GetPushCount(owner); + var stackAfterPushSize = stackAfterPop.Length + pushCount; + Span stackAfterPush = stackAfterPushSize is 0 ? [] : new StackType[stackAfterPushSize]; + stackAfterPop.CopyTo(stackAfterPush); + + // Set pushed types + if (pushCount is 1) + { + stackAfterPush[^1] = instruction.Code.Code switch + { + CilCode.Add or CilCode.Sub or CilCode.Mul or CilCode.Div or CilCode.Rem + or CilCode.Add_Ovf or CilCode.Add_Ovf_Un or CilCode.Sub_Ovf or CilCode.Sub_Ovf_Un + or CilCode.Mul_Ovf or CilCode.Mul_Ovf_Un or CilCode.Div_Un or CilCode.Rem_Un + or CilCode.And or CilCode.Or or CilCode.Xor => StackType.MergeForMathOperation(poppedTypes[0], poppedTypes[1]), + CilCode.Neg or CilCode.Not => poppedTypes[0], + CilCode.Shl or CilCode.Shr or CilCode.Shr_Un => poppedTypes[0], + CilCode.Ldobj => GetExactType((TypeAnalysisContext)instruction.Operand!, visitor), + CilCode.Castclass or CilCode.Isinst => GetExactType((TypeAnalysisContext)instruction.Operand!, visitor), + CilCode.Ldstr => GetExactType(il2CppTypes ? owner.AppContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.String") : owner.AppContext.SystemTypes.SystemStringType, visitor), + CilCode.Ldlen => IntegerStackType32.Instance, + CilCode.Ceq or CilCode.Cgt or CilCode.Cgt_Un or CilCode.Clt or CilCode.Clt_Un => IntegerStackType32.Instance, + CilCode.Sizeof => IntegerStackType32.Instance, + CilCode.Ldind_I + or CilCode.Ldelem_I + or CilCode.Conv_I or CilCode.Conv_U + or CilCode.Conv_Ovf_I or CilCode.Conv_Ovf_U + or CilCode.Conv_Ovf_I_Un or CilCode.Conv_Ovf_U_Un => IntegerStackTypeNative.Instance, + CilCode.Ldc_I4 or CilCode.Ldind_I1 or CilCode.Ldind_I2 or CilCode.Ldind_I4 + or CilCode.Ldind_U1 or CilCode.Ldind_U2 or CilCode.Ldind_U4 + or CilCode.Ldelem_I1 or CilCode.Ldelem_I2 or CilCode.Ldelem_I4 + or CilCode.Ldelem_U1 or CilCode.Ldelem_U2 or CilCode.Ldelem_U4 + or CilCode.Conv_I1 or CilCode.Conv_I2 or CilCode.Conv_I4 + or CilCode.Conv_U1 or CilCode.Conv_U2 or CilCode.Conv_U4 + or CilCode.Conv_Ovf_I1 or CilCode.Conv_Ovf_I2 or CilCode.Conv_Ovf_I4 + or CilCode.Conv_Ovf_I1_Un or CilCode.Conv_Ovf_I2_Un or CilCode.Conv_Ovf_I4_Un + or CilCode.Conv_Ovf_U1 or CilCode.Conv_Ovf_U2 or CilCode.Conv_Ovf_U4 + or CilCode.Conv_Ovf_U1_Un or CilCode.Conv_Ovf_U2_Un or CilCode.Conv_Ovf_U4_Un => IntegerStackType32.Instance, + CilCode.Ldc_I8 or CilCode.Ldind_I8 or CilCode.Ldelem_I8 + or CilCode.Conv_I8 or CilCode.Conv_U8 + or CilCode.Conv_Ovf_I8 or CilCode.Conv_Ovf_U8 + or CilCode.Conv_Ovf_I8_Un or CilCode.Conv_Ovf_U8_Un => IntegerStackType64.Instance, + CilCode.Ldc_R4 or CilCode.Ldind_R4 or CilCode.Ldelem_R4 or CilCode.Conv_R4 => SingleStackType.Instance, + CilCode.Ldc_R8 or CilCode.Ldind_R8 or CilCode.Ldelem_R8 or CilCode.Conv_R8 or CilCode.Conv_R_Un => DoubleStackType.Instance, + CilCode.Ldarg_0 or CilCode.Ldarg_1 or CilCode.Ldarg_2 or CilCode.Ldarg_3 => throw new InvalidOperationException("Ldarg_* should have been replaced with Ldarg."), + CilCode.Ldloc_0 or CilCode.Ldloc_1 or CilCode.Ldloc_2 or CilCode.Ldloc_3 => throw new InvalidOperationException("Ldloc_* should have been replaced with Ldloc."), + CilCode.Ldarg or CilCode.Ldarg_S => instruction.Operand switch + { + ParameterAnalysisContext parameter => GetExactType(visitor.Replace(parameter.ParameterType), visitor), + This => owner.DeclaringType!.IsValueType + ? GetExactType(visitor.Replace(owner.DeclaringType).MakeByReferenceType(), visitor) + : GetExactType(owner.DeclaringType, visitor), + _ => throw new InvalidOperationException("Ldarg operand should be ParameterAnalysisContext or This"), + }, + CilCode.Ldloc or CilCode.Ldloc_S => instruction.Operand switch + { + LocalVariable localVariable => GetExactType(localVariable.Type, visitor), + _ => throw new InvalidOperationException("Ldloc operand should be LocalVariable"), + }, + CilCode.Ldarga or CilCode.Ldarga_S => instruction.Operand switch + { + ParameterAnalysisContext parameter => GetExactType(parameter.ParameterType.MakeByReferenceType(), visitor), + This => throw new NotSupportedException("Ldarga on 'this' is not supported."), + _ => throw new InvalidOperationException("Ldarg operand should be ParameterAnalysisContext or This"), + }, + CilCode.Ldloca or CilCode.Ldloca_S => instruction.Operand switch + { + LocalVariable localVariable => GetExactType(localVariable.Type.MakeByReferenceType(), visitor), + _ => throw new InvalidOperationException("Ldloc operand should be LocalVariable"), + }, + CilCode.Newobj => instruction.Operand switch + { + MethodAnalysisContext methodOperand => GetExactType(methodOperand.DeclaringType!, visitor), + MultiDimensionalArrayMethod arrayMethod => GetExactType(arrayMethod.ArrayType, visitor), + _ => throw new InvalidOperationException("Newobj operand should be MethodAnalysisContext or MultiDimensionalArrayMethod"), + }, + CilCode.Call or CilCode.Callvirt => instruction.Operand switch + { + MethodAnalysisContext methodOperand => GetExactType(methodOperand.ReturnType, visitor), + MultiDimensionalArrayMethod arrayMethod => arrayMethod.MethodType switch + { + MultiDimensionalArrayMethodType.Get => GetExactType(arrayMethod.ArrayType.ElementType, visitor), + MultiDimensionalArrayMethodType.Address => GetExactType(arrayMethod.ArrayType.ElementType.MakeByReferenceType(), visitor), + _ => throw new NotSupportedException($"Method type {arrayMethod.MethodType} does not return a value."), + }, + _ => throw new InvalidOperationException("Call/Callvirt operand should be MethodAnalysisContext or MultiDimensionalArrayMethod"), + }, + CilCode.Ldfld or CilCode.Ldsfld => GetExactType(((FieldAnalysisContext)instruction.Operand!).FieldType, visitor), + CilCode.Ldflda or CilCode.Ldsflda => GetExactType(((FieldAnalysisContext)instruction.Operand!).FieldType.MakeByReferenceType(), visitor), + CilCode.Ldind_Ref => GetStackType_Ldind_Ref(stackInitial, visitor), + CilCode.Ldelem_Ref => GetStackType_Ldelem_Ref(stackInitial, visitor), + CilCode.Ldelema => GetExactType(((TypeAnalysisContext)instruction.Operand!).MakeByReferenceType(), visitor), + CilCode.Ldelem => GetExactType((TypeAnalysisContext)instruction.Operand!, visitor), + CilCode.Box => GetExactType((TypeAnalysisContext)instruction.Operand!, visitor), + CilCode.Unbox or CilCode.Unbox_Any => GetExactType((TypeAnalysisContext)instruction.Operand!, visitor), + CilCode.Newarr => GetExactType(((TypeAnalysisContext)instruction.Operand!).MakeSzArrayType(), visitor), + CilCode.Ldnull => UnknownStackType.Instance, + CilCode.Ldftn or CilCode.Ldvirtftn => IntegerStackTypeNative.Instance, + CilCode.Localloc => IntegerStackTypeNative.Instance, + CilCode.Ldtoken => UnknownStackType.Instance,// Todo + CilCode.Mkrefany => UnknownStackType.Instance,// Todo + CilCode.Refanytype => UnknownStackType.Instance,// Todo + CilCode.Refanyval => UnknownStackType.Instance,// Todo + CilCode.Arglist => UnknownStackType.Instance,// Todo + _ => UnknownStackType.Instance, + }; + } + else if (pushCount is 2) + { + Debug.Assert(instruction.Code == CilOpCodes.Dup); + stackAfterPush[^2] = stackAfterPush[^1] = poppedTypes[0]; + } + else + { + Debug.Assert(pushCount is 0); + } + + foreach (var successor in instructionSuccessors[instruction]) + { + if (result.TryGetValue(successor, out var existingStack)) + { + if (existingStack.Length != stackAfterPush.Length) + { + throw new InvalidOperationException($"Inconsistent stack heights at instruction {successor}: existing {existingStack.Length}, new {stackAfterPush.Length}"); + } + + for (var i = 0; i < existingStack.Length; i++) + { + var merged = StackType.Merge(existingStack[i], stackAfterPush[i]); + if (!EqualityComparer.Default.Equals(merged, existingStack[i])) + { + existingStack[i] = merged; + changed = true; + } + } + } + else + { + result[successor] = stackAfterPush.ToArray(); + changed = true; + } + } + } + } while (changed); + + Debug.Assert(result.Count == Instructions.Count); + + return result; + } + + private static StackType GetStackType_Ldind_Ref(StackType[] stackInitial, TypeReplacementVisitor visitor) + { + return stackInitial[^1] is ExactStackType exactType && TryGetByReferenceElementType(exactType.Type, out var elementType) + ? GetExactType(elementType!, visitor) + : UnknownStackType.Instance; + } + + private static StackType GetStackType_Ldelem_Ref(StackType[] stackInitial, TypeReplacementVisitor visitor) + { + return stackInitial[^2] is ExactStackType exactType && TryGetArrayElementType(exactType.Type, out var elementType) + ? GetExactType(elementType!, visitor) + : UnknownStackType.Instance; + } + + private static StackType GetExactType(TypeAnalysisContext type, TypeReplacementVisitor visitor) + { + var replacedType = visitor.Replace(type); + if (IsPointerType(replacedType)) + { + return IntegerStackTypeNative.Instance; + } + if (IsEnum(replacedType, out var enumElementType)) + { + return GetExactType(enumElementType, visitor); + } + return replacedType.KnownType switch + { + KnownTypeCode.Il2CppSystem_Boolean => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_Char => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_Byte => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_SByte => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_Int16 => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_UInt16 => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_Int32 => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_UInt32 => IntegerStackType32.Instance, + KnownTypeCode.Il2CppSystem_Int64 => IntegerStackType64.Instance, + KnownTypeCode.Il2CppSystem_UInt64 => IntegerStackType64.Instance, + KnownTypeCode.Il2CppSystem_IntPtr => IntegerStackTypeNative.Instance, + KnownTypeCode.Il2CppSystem_UIntPtr => IntegerStackTypeNative.Instance, + KnownTypeCode.Il2CppSystem_Single => SingleStackType.Instance, + KnownTypeCode.Il2CppSystem_Double => DoubleStackType.Instance, + _ => new ExactStackType(replacedType), + }; + } + + private static bool IsPointerType(TypeAnalysisContext type) + { + if (type is PointerTypeAnalysisContext) + return true; + + return type is GenericInstanceTypeAnalysisContext { GenericType: { Name: $"{nameof(Pointer<>)}`1" } genericType } && genericType.Namespace == typeof(Pointer<>).Namespace; + } + + protected static bool IsByReferenceType(TypeAnalysisContext type) + { + return TryGetByReferenceElementType(type, out _); + } + + protected static bool TryGetByReferenceElementType(TypeAnalysisContext type, [NotNullWhen(true)] out TypeAnalysisContext? elementType) + { + if (type is ByRefTypeAnalysisContext byRefType) + { + elementType = byRefType.ElementType; + return true; + } + if (type is GenericInstanceTypeAnalysisContext { GenericType: { Name: $"{nameof(ByReference<>)}`1" } genericType } && genericType.Namespace == typeof(ByReference<>).Namespace) + { + elementType = ((GenericInstanceTypeAnalysisContext)type).GenericArguments[0]; + return true; + } + elementType = null; + return false; + } + + private static bool TryGetArrayElementType(TypeAnalysisContext type, [NotNullWhen(true)] out TypeAnalysisContext? elementType) + { + if (type is ArrayTypeAnalysisContext arrayType) + { + elementType = arrayType.ElementType; + return true; + } + if (type is SzArrayTypeAnalysisContext szArrayType) + { + elementType = szArrayType.ElementType; + return true; + } + if (type is GenericInstanceTypeAnalysisContext { GenericType: { Name: $"{nameof(Il2CppArrayRank1<>)}`1" } genericType } && genericType.Namespace == typeof(Il2CppArrayRank1<>).Namespace) + { + elementType = ((GenericInstanceTypeAnalysisContext)type).GenericArguments[0]; + return true; + } + elementType = null; + return false; + } + + private static bool IsEnum(TypeAnalysisContext type, [NotNullWhen(true)] out TypeAnalysisContext? elementType) + { + if (type is { DefaultBaseType.KnownType: KnownTypeCode.Il2CppSystem_Enum or KnownTypeCode.System_Enum }) + { + elementType = type.Fields.FirstOrDefault(f => f.Name == "value__")?.FieldType; + return elementType is not null; + } + elementType = null; + return false; + } +} diff --git a/Il2CppInterop.Generator/MethodBodyTranslationProcessingLayer.cs b/Il2CppInterop.Generator/MethodBodyTranslationProcessingLayer.cs new file mode 100644 index 00000000..33f33774 --- /dev/null +++ b/Il2CppInterop.Generator/MethodBodyTranslationProcessingLayer.cs @@ -0,0 +1,32 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class MethodBodyTranslationProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "method_body_translation"; + public override string Name => "Method Body Translation"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + Logger.InfoNewline("Translating method bodies...", nameof(MethodBodyTranslationProcessingLayer)); + var successfulCount = 0; + var totalCount = 0; + foreach (var method in appContext.AllTypes.SelectMany(t => t.Methods)) + { + if (method.HasExtraData()) + { + totalCount++; + } + if (TranslatedMethodBody.TryTranslateOriginalMethodBody(method)) + { + successfulCount++; + } + method.RemoveExtraData(); + } + + // Report how many method bodies were successfully translated. + // This total count should be the same as the count of methods with original bodies that were unstripped earlier. + Logger.InfoNewline($"Translated the original method body for {successfulCount}/{totalCount} attempts.", nameof(MethodBodyTranslationProcessingLayer)); + } +} diff --git a/Il2CppInterop.Generator/MethodInvokerProcessingLayer.cs b/Il2CppInterop.Generator/MethodInvokerProcessingLayer.cs new file mode 100644 index 00000000..fe9017e5 --- /dev/null +++ b/Il2CppInterop.Generator/MethodInvokerProcessingLayer.cs @@ -0,0 +1,451 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime.InteropTypes; + +namespace Il2CppInterop.Generator; + +// Todo: add attributes making these not show up in IntelliSense +public class MethodInvokerProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Method Invoker Processor"; + public override string Id => "method_invoker_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var byReference = appContext.ResolveTypeOrThrow(typeof(ByReference<>)); + var byReference_GetValue = byReference.GetMethodByName(nameof(ByReference<>.GetValue)); + var byReference_SetValue = byReference.GetMethodByName(nameof(ByReference<>.SetValue)); + var byReference_CopyFrom = byReference.GetMethodByName(nameof(ByReference<>.CopyFrom)); + var byReference_CopyTo = byReference.GetMethodByName(nameof(ByReference<>.CopyTo)); + var byReference_Clear = byReference.GetMethodByName(nameof(ByReference<>.Clear)); + var byReference_ToPointer = byReference.GetMethodByName(nameof(ByReference<>.ToPointer)); + var byReference_Constructor = byReference.GetMethodByName(".ctor"); + + var il2CppTypeHelper = appContext.ResolveTypeOrThrow(typeof(Il2CppType)); + var il2CppTypeHelper_SizeOf = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.SizeOf)); + + var newObjectPointer = appContext.ResolveTypeOrThrow(typeof(ObjectPointer)).GetMethodByName(nameof(ObjectPointer.New)); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + var instantiatedType = type.SelfInstantiateIfGeneric(); + + HashSet existingNames = [type.CSharpName, .. type.Members.Select(m => m.Name)]; + + for (var methodIndex = 0; methodIndex < type.Methods.Count; methodIndex++) + { + var method = type.Methods[methodIndex]; + if (method.IsInjected) + continue; + if (method.IsAbstract) + continue; + if (method.IsUnstripped && method.DefaultAttributes.HasFlag(MethodAttributes.Abstract)) + { + // Unstripped abstract methods get converted to virtual methods with a throw implementation. + Debug.Assert(method.IsVirtual); + continue; + } + + var redirectedType = instantiatedType.KnownType switch + { + _ when method.IsInstanceConstructor => instantiatedType, + KnownTypeCode.Il2CppSystem_Object => appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IObject"), + KnownTypeCode.Il2CppSystem_Enum => appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IEnum"), + KnownTypeCode.Il2CppSystem_ValueType => appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IValueType"), + _ => instantiatedType, + }; + + InjectedMethodAnalysisContext? helper = null; + InjectedMethodAnalysisContext? valueTypeHelper = null; + InjectedMethodAnalysisContext? referenceTypeHelper = null; + if (method.IsStatic) + { + // No helper needed + } + else + { + var name = method.IsInstanceConstructor + ? GetNonConflictingName("UnsafeConstruct", existingNames) + : GetNonConflictingName($"UnsafeInvoke_{method.Name.MakeValidCSharpName()}", existingNames); + + helper = new InjectedMethodAnalysisContext( + type, + name, + type, // Placeholder return type + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + []) + { + IsInjected = true, + }; + type.Methods.Add(helper); + + method.UnsafeInvokeMethod = helper; + + helper.CopyGenericParameters(method, true, true); + + var visitor = TypeReplacementVisitor.CreateForMethodCopying(method, helper); + + helper.SetDefaultReturnType(visitor.Replace(method.ReturnType)); + + // "this" parameter + if (type.IsValueType) + { + var newParameter = new InjectedParameterAnalysisContext( + null, + byReference.MakeGenericInstanceType([redirectedType]), + ParameterAttributes.None, + 0, + helper); + helper.Parameters.Add(newParameter); + } + else + { + var newParameter = new InjectedParameterAnalysisContext( + null, + redirectedType, + ParameterAttributes.None, + 0, + helper); + helper.Parameters.Add(newParameter); + } + + foreach (var originalParameter in method.Parameters) + { + var newParameterType = visitor.Replace(originalParameter.ParameterType); + + var newParameter = new InjectedParameterAnalysisContext( + originalParameter.Name, + newParameterType, + originalParameter.Attributes, + helper.Parameters.Count, + helper); + helper.Parameters.Add(newParameter); + } + + if (type.IsValueType) + { + valueTypeHelper = helper; + } + else + { + referenceTypeHelper = helper; + } + } + + var isUnstrippedICall = method.IsUnstripped && method.ICallDelegateField is not null; + + InjectedMethodAnalysisContext? implementationMethod; + if (isUnstrippedICall) + { + // The ICall delegate is the implementation, so we don't need to generate a separate method for it. + implementationMethod = null; + } + else + { + var name = GetNonConflictingName(method.IsInstanceConstructor ? "UnsafeConstructor" : $"UnsafeImplementation_{method.Name.Replace('.', '_')}", existingNames); + + implementationMethod = new InjectedMethodAnalysisContext( + type, + name, + type, // Placeholder return type + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + []) + { + IsInjected = true, + }; + type.Methods.Add(implementationMethod); + + method.UnsafeImplementationMethod = implementationMethod; + + implementationMethod.CopyGenericParameters(method, true, true); + + var visitor = TypeReplacementVisitor.CreateForMethodCopying(method, implementationMethod); + + implementationMethod.SetDefaultReturnType(visitor.Replace(method.ReturnType)); + + if (!method.IsStatic) + { + var newParameter = new InjectedParameterAnalysisContext( + null, + byReference.MakeGenericInstanceType([redirectedType]), + ParameterAttributes.None, + 0, + implementationMethod); + implementationMethod.Parameters.Add(newParameter); + } + + foreach (var originalParameter in method.Parameters) + { + var newParameterType = byReference.MakeGenericInstanceType([visitor.Replace(originalParameter.ParameterType)]); + + var newParameter = new InjectedParameterAnalysisContext( + originalParameter.Name, + newParameterType, + originalParameter.Attributes, + implementationMethod.Parameters.Count, + implementationMethod); + implementationMethod.Parameters.Add(newParameter); + } + } + + if (helper is not null) + { + if (isUnstrippedICall) + { + Debug.Assert(method.DeclaringType?.GenericParameters.Count == 0 && method.GenericParameters.Count == 0); + + List instructions = new(1 + helper.Parameters.Count + 2); + instructions.Add(CilOpCodes.Ldsfld, method.ICallDelegateField); + foreach (var parameter in helper.Parameters) + { + instructions.Add(CilOpCodes.Ldarg, parameter); + } + instructions.Add(CilOpCodes.Callvirt, method.ICallDelegateField!.FieldType.GetMethodByName("Invoke")); + instructions.Add(CilOpCodes.Ret); + + helper.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = [], + }); + } + else + { + List instructions = new(); + + var thisIsLocal = !type.IsValueType; + var localVariablesOffset = thisIsLocal ? 0 : 1; + var localVariablesCount = thisIsLocal ? method.Parameters.Count + 1 : method.Parameters.Count; + LocalVariable[] localVariables = new LocalVariable[localVariablesCount]; + + for (var i = localVariablesOffset; i < helper.Parameters.Count; i++) + { + var parameter = helper.Parameters[i]; + var parameterLocal = new LocalVariable(byReference.MakeGenericInstanceType([parameter.ParameterType])); + localVariables[i - localVariablesOffset] = parameterLocal; + + instructions.Add(CilOpCodes.Call, il2CppTypeHelper_SizeOf.MakeGenericInstanceMethod(parameter.ParameterType)); + instructions.Add(CilOpCodes.Conv_U); + instructions.Add(CilOpCodes.Localloc); + instructions.Add(CilOpCodes.Newobj, byReference_Constructor.MakeConcreteGeneric([parameter.ParameterType], [])); + instructions.Add(CilOpCodes.Stloc, parameterLocal); + + instructions.Add(CilOpCodes.Ldloca, parameterLocal); + instructions.Add(CilOpCodes.Ldarg, parameter); + instructions.Add(CilOpCodes.Call, byReference_SetValue.MakeConcreteGeneric([parameter.ParameterType], [])); + } + + if (!thisIsLocal) + { + instructions.Add(CilOpCodes.Ldarg, helper.Parameters[0]); + } + + foreach (var parameterLocal in localVariables) + { + instructions.Add(CilOpCodes.Ldloc, parameterLocal); + } + + instructions.Add(CilOpCodes.Call, implementationMethod!.MaybeMakeConcreteGeneric(type.GenericParameters, helper.GenericParameters)); + + instructions.Add(CilOpCodes.Ret); + + helper.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = localVariables, + }); + } + } + + // Method body + Debug.Assert(!method.HasExtraData()); + if (valueTypeHelper is not null) + { + Debug.Assert(!method.IsStatic); + Debug.Assert(type.IsValueType); + + List instructions = new(); + + LocalVariable instanceLocal = new(byReference.MakeGenericInstanceType([instantiatedType])); + + instructions.Add(CilOpCodes.Call, il2CppTypeHelper_SizeOf.MakeGenericInstanceMethod(instantiatedType)); + instructions.Add(CilOpCodes.Conv_U); + instructions.Add(CilOpCodes.Localloc); + instructions.Add(CilOpCodes.Newobj, new ConcreteGenericMethodAnalysisContext(byReference_Constructor, [instantiatedType], [])); + instructions.Add(CilOpCodes.Stloc, instanceLocal); + + instructions.Add(CilOpCodes.Ldloca, instanceLocal); + instructions.Add(CilOpCodes.Ldarg, This.Instance); + instructions.Add(CilOpCodes.Call, byReference_CopyFrom.MakeConcreteGeneric([instantiatedType], [])); + + instructions.Add(CilOpCodes.Ldloc, instanceLocal); + + foreach (var parameter in method.Parameters) + { + instructions.Add(CilOpCodes.Ldarg, parameter); + } + + instructions.Add(CilOpCodes.Call, valueTypeHelper.MaybeMakeConcreteGeneric(type.GenericParameters, method.GenericParameters)); + + instructions.Add(CilOpCodes.Ldloca, instanceLocal); + instructions.Add(CilOpCodes.Ldarg, This.Instance); + instructions.Add(CilOpCodes.Call, byReference_CopyTo.MakeConcreteGeneric([instantiatedType], [])); + + instructions.Add(CilOpCodes.Ret); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = [instanceLocal], + }); + } + else if (referenceTypeHelper is not null) + { + Debug.Assert(!method.IsStatic); + Debug.Assert(!type.IsValueType); + + List instructions = []; + + if (method.IsInstanceConstructor) + { + Debug.Assert(type.PointerConstructor is not null); + instructions.Add(CilOpCodes.Ldarg, This.Instance); + instructions.Add(CilOpCodes.Call, newObjectPointer.MakeGenericInstanceMethod(instantiatedType)); + instructions.Add(CilOpCodes.Call, type.PointerConstructor!.MaybeMakeConcreteGeneric(type.GenericParameters, [])); + } + + instructions.Add(CilOpCodes.Ldarg, This.Instance); + foreach (var parameter in method.Parameters) + { + instructions.Add(CilOpCodes.Ldarg, parameter); + } + instructions.Add(CilOpCodes.Call, referenceTypeHelper.MaybeMakeConcreteGeneric(type.GenericParameters, method.GenericParameters)); + instructions.Add(CilOpCodes.Ret); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = [], + }); + } + else if (isUnstrippedICall) + { + Debug.Assert(method.IsStatic); + Debug.Assert(method.DeclaringType?.GenericParameters.Count == 0 && method.GenericParameters.Count == 0); + + List instructions = new(1 + method.Parameters.Count + 2); + instructions.Add(CilOpCodes.Ldsfld, method.ICallDelegateField); + foreach (var parameter in method.Parameters) + { + instructions.Add(CilOpCodes.Ldarg, parameter); + } + instructions.Add(CilOpCodes.Callvirt, method.ICallDelegateField!.FieldType.GetMethodByName("Invoke")); + instructions.Add(CilOpCodes.Ret); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = [], + }); + } + else + { + Debug.Assert(method.IsStatic); + + List instructions = new(); + + LocalVariable[] parameterLocals = new LocalVariable[method.Parameters.Count]; + + for (var i = 0; i < method.Parameters.Count; i++) + { + var parameter = method.Parameters[i]; + var parameterLocal = new LocalVariable(byReference.MakeGenericInstanceType([parameter.ParameterType])); + parameterLocals[i] = parameterLocal; + + instructions.Add(CilOpCodes.Call, il2CppTypeHelper_SizeOf.MakeGenericInstanceMethod(parameter.ParameterType)); + instructions.Add(CilOpCodes.Conv_U); + instructions.Add(CilOpCodes.Localloc); + instructions.Add(CilOpCodes.Newobj, byReference_Constructor.MakeConcreteGeneric([parameter.ParameterType], [])); + instructions.Add(CilOpCodes.Stloc, parameterLocal); + + instructions.Add(CilOpCodes.Ldloca, parameterLocal); + instructions.Add(CilOpCodes.Ldarg, parameter); + instructions.Add(CilOpCodes.Call, byReference_SetValue.MakeConcreteGeneric([parameter.ParameterType], [])); + } + + foreach (var parameterLocal in parameterLocals) + { + instructions.Add(CilOpCodes.Ldloc, parameterLocal); + } + + instructions.Add(CilOpCodes.Call, implementationMethod!.MaybeMakeConcreteGeneric(type.GenericParameters, method.GenericParameters)); + + instructions.Add(CilOpCodes.Ret); + + method.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = parameterLocals, + }); + } + + // Interface redirect body + if (method.InterfaceRedirectMethod is not null) + { + Debug.Assert(method.GenericParameters.Count == 0); + Debug.Assert(method.DeclaringType is { GenericParameters.Count: 0 }); + + var methodBody = method.GetExtraData(); + Debug.Assert(methodBody is { ExceptionHandlers.Count: 0 }); + + var localVariables = new LocalVariable[methodBody.LocalVariables.Count]; + var operandRedirects = new Dictionary(methodBody.LocalVariables.Count); + for (var i = 0; i < localVariables.Length; i++) + { + var originalLocal = methodBody.LocalVariables[i]; + var newLocal = new LocalVariable(originalLocal.Type); + localVariables[i] = newLocal; + operandRedirects.Add(originalLocal, newLocal); + } + + method.InterfaceRedirectMethod!.PutExtraData(new NativeMethodBody() + { + Instructions = methodBody.Instructions.Select(i => + { + return new Instruction(i.Code, i.Operand is not null && operandRedirects.TryGetValue(i.Operand, out var redirectedOperand) ? redirectedOperand : i.Operand); + }).ToArray(), + LocalVariables = localVariables, + }); + + if (method.UnsafeInvokeMethod is not null) + { + method.InterfaceRedirectMethod!.UnsafeInvokeMethod = method.UnsafeInvokeMethod!; + } + } + } + } + } + } + + private static string GetNonConflictingName(string baseName, HashSet existingNames) + { + var name = baseName; + while (existingNames.Contains(name)) + { + name = $"{baseName}_"; + } + return name; + } +} diff --git a/Il2CppInterop.Generator/MonoIl2CppConversion.cs b/Il2CppInterop.Generator/MonoIl2CppConversion.cs new file mode 100644 index 00000000..cc35ba6e --- /dev/null +++ b/Il2CppInterop.Generator/MonoIl2CppConversion.cs @@ -0,0 +1,137 @@ +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Runtime.InteropTypes; + +namespace Il2CppInterop.Generator; + +internal static class MonoIl2CppConversion +{ + /// + /// Does not convert strings, which are treated as normal Il2Cpp objects. + /// + public static bool AddIl2CppToMonoConversion(List instructions, TypeAnalysisContext il2CppType) + { + // Depending on the type of the local variable, we may need to convert it. + // If the local variable is Pointer or ByReference, we need to convert it to void*. + // If the local variable is an Il2Cpp primitive (like Il2CppSystem.Int32), we need to convert it to the corresponding C# type. + // If the local variable is an Il2Cpp enum, we need to convert it to the underlying C# primitive type. + + if (IsIl2CppPrimitiveValueType(il2CppType)) + { + var monoType = il2CppType.AppContext.Mscorlib.GetTypeByFullNameOrThrow($"System.{il2CppType.Name}"); + var conversionMethod = il2CppType.GetImplicitConversionTo(monoType); + instructions.Add(new Instruction(CilOpCodes.Call, conversionMethod)); + return true; + } + else if (il2CppType is GenericInstanceTypeAnalysisContext { GenericArguments.Count: 1, GenericType.DeclaringType: null } genericInstanceType) + { + if (IsPointerOrByReference(genericInstanceType)) + { + var elementType = genericInstanceType.GenericArguments[0]; + var conversionMethod = genericInstanceType.GenericType.Methods.First(m => m.Name == "op_Explicit" && m.ReturnType is PointerTypeAnalysisContext); + instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(conversionMethod, [elementType], []))); + return true; + } + } + else if (il2CppType.EnumMonoUnderlyingType is { } underlyingType) + { + var conversionMethod = il2CppType.GetExplicitConversionTo(underlyingType); + instructions.Add(new Instruction(CilOpCodes.Call, conversionMethod)); + return true; + } + return false; + } + + /// + /// Does not convert strings, which are treated as normal Il2Cpp objects. + /// + public static bool AddMonoToIl2CppConversion(List instructions, TypeAnalysisContext il2CppType) + { + if (IsIl2CppPrimitiveValueType(il2CppType)) + { + var monoType = il2CppType.AppContext.Mscorlib.GetTypeByFullNameOrThrow($"System.{il2CppType.Name}"); + var conversionMethod = il2CppType.GetImplicitConversionFrom(monoType); + instructions.Add(new Instruction(CilOpCodes.Call, conversionMethod)); + return true; + } + else if (il2CppType is GenericInstanceTypeAnalysisContext { GenericArguments.Count: 1, GenericType.DeclaringType: null } genericInstanceType) + { + if (IsPointerOrByReference(genericInstanceType)) + { + var elementType = genericInstanceType.GenericArguments[0]; + var conversionMethod = genericInstanceType.GenericType.Methods.First(m => m.Name == "op_Explicit" && m.Parameters.Count == 1 && m.Parameters[0].ParameterType is PointerTypeAnalysisContext); + instructions.Add(new Instruction(CilOpCodes.Call, new ConcreteGenericMethodAnalysisContext(conversionMethod, [elementType], []))); + return true; + } + } + else if (il2CppType.EnumMonoUnderlyingType is { } underlyingType) + { + var conversionMethod = il2CppType.GetExplicitConversionFrom(underlyingType); + instructions.Add(new Instruction(CilOpCodes.Call, conversionMethod)); + return true; + } + return false; + } + + private static bool IsPointer(GenericInstanceTypeAnalysisContext genericInstanceType) + { + return genericInstanceType.GenericType.Name == $"{nameof(Pointer<>)}`1" && genericInstanceType.GenericType.Namespace == typeof(Pointer<>).Namespace; + } + + private static bool IsByReference(GenericInstanceTypeAnalysisContext genericInstanceType) + { + return genericInstanceType.GenericType.Name == $"{nameof(ByReference<>)}`1" && genericInstanceType.GenericType.Namespace == typeof(ByReference<>).Namespace; + } + + private static bool IsPointerOrByReference(GenericInstanceTypeAnalysisContext genericInstanceType) + { + return IsPointer(genericInstanceType) || IsByReference(genericInstanceType); + } + + public static void AddIl2CppToMonoStringConversion(List instructions, ApplicationAnalysisContext appContext) + { + var il2CppSystemString = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.String"); + var monoSystemString = appContext.SystemTypes.SystemStringType; + var conversionMethod = il2CppSystemString.GetImplicitConversionTo(monoSystemString); + instructions.Add(new Instruction(CilOpCodes.Call, conversionMethod)); + } + + public static void AddMonoToIl2CppStringConversion(List instructions, ApplicationAnalysisContext appContext) + { + var il2CppSystemString = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.String"); + var monoSystemString = appContext.SystemTypes.SystemStringType; + var conversionMethod = il2CppSystemString.GetImplicitConversionFrom(monoSystemString); + instructions.Add(new Instruction(CilOpCodes.Call, conversionMethod)); + } + + private static bool IsIl2CppPrimitiveValueType(TypeAnalysisContext type) + { + if (type is ReferencedTypeAnalysisContext) + return false; + + if (type.DeclaringType is not null) + return false; + + if (type.DeclaringAssembly != type.AppContext.Il2CppMscorlib) + return false; + + if (type.Namespace != "Il2CppSystem") + return false; + + return type.Name + is "Byte" + or "SByte" + or "Int16" + or "UInt16" + or "Int32" + or "UInt32" + or "IntPtr" + or "UIntPtr" + or "Int64" + or "UInt64" + or "Single" + or "Double" + or "Boolean" + or "Char"; + } +} diff --git a/Il2CppInterop.Generator/MscorlibAssemblyInjectionProcessingLayer.cs b/Il2CppInterop.Generator/MscorlibAssemblyInjectionProcessingLayer.cs new file mode 100644 index 00000000..59b4d3ae --- /dev/null +++ b/Il2CppInterop.Generator/MscorlibAssemblyInjectionProcessingLayer.cs @@ -0,0 +1,86 @@ +using System.Diagnostics; +using AsmResolver.DotNet; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class MscorlibAssemblyInjectionProcessingLayer : UnstripBaseProcessingLayer +{ + public override string Name => "Inject a new mscorlib into the Cpp2IL context system"; + + public override string Id => "mscorlib_injector"; + + private static readonly string[] injectedAssemblies = + [ + "mscorlib", + "System.Collections", + ]; + internal static ReadOnlySpan InjectedAssemblies => injectedAssemblies; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + Logger.InfoNewline($"Injecting new mscorlib...", nameof(MscorlibAssemblyInjectionProcessingLayer)); + + var mscorlib = LoadSystemRuntime(); + var runtimeContext = new RuntimeContext(DotNetRuntimeInfo.NetCoreApp(10, 0), new AssemblyResolver(), mscorlib); + runtimeContext.AddAssembly(mscorlib); + + var assembliesToInject = new AssemblyDefinition[injectedAssemblies.Length]; + assembliesToInject[0] = mscorlib; + for (var i = 1; i < injectedAssemblies.Length; i++) + { + var assemblyName = injectedAssemblies[i]; + assembliesToInject[i] = LoadAssembly(assemblyName, runtimeContext); + } + + InjectAssemblies(appContext, assembliesToInject, false, true); + + // Need to reset the system types context to use the new corlib + appContext.SystemTypes = new SystemTypesContext(appContext); + + foreach (var assemblyName in injectedAssemblies) + { + appContext.AssembliesByName[assemblyName].IsReferenceAssembly = true; + } + } + + private static AssemblyDefinition LoadSystemRuntime() + { + AssemblyDefinition assembly = AssemblyDefinition.FromBytes(Basic.Reference.Assemblies.Net100.ReferenceInfos.SystemRuntime.ImageBytes, createRuntimeContext: false); + assembly.Name = "mscorlib"; + assembly.ManifestModule!.Name = "mscorlib.dll"; + return assembly; + } + + private static AssemblyDefinition LoadAssembly(string assemblyName, RuntimeContext runtimeContext) + { + var assembly = AssemblyDefinition.FromBytes(GetAssemblyBytes($"{assemblyName}.dll"), createRuntimeContext: false); + foreach (var module in assembly.Modules) + { + foreach (var assemblyReference in module.AssemblyReferences) + { + if (assemblyReference.Name == "System.Runtime") + { + assemblyReference.Name = "mscorlib"; + } + } + } + runtimeContext.AddAssembly(assembly); + return assembly; + } + + private static byte[] GetAssemblyBytes(string fileName) + { + return Basic.Reference.Assemblies.Net100.ReferenceInfos.All.First(x => x.FileName == fileName).ImageBytes; + } + + private sealed class AssemblyResolver : IAssemblyResolver + { + public ResolutionStatus Resolve(AssemblyDescriptor assembly, ModuleDefinition? originModule, out AssemblyDefinition? result) + { + Debug.Fail("Resolution of external assemblies should not be necessary."); + result = null; + return ResolutionStatus.AssemblyNotFound; + } + } +} diff --git a/Il2CppInterop.Generator/NativeMethodBody.cs b/Il2CppInterop.Generator/NativeMethodBody.cs new file mode 100644 index 00000000..a76d6332 --- /dev/null +++ b/Il2CppInterop.Generator/NativeMethodBody.cs @@ -0,0 +1,6 @@ +namespace Il2CppInterop.Generator; + +public class NativeMethodBody : MethodBodyBase +{ + +} diff --git a/Il2CppInterop.Generator/NativeMethodBodyProcessingLayer.cs b/Il2CppInterop.Generator/NativeMethodBodyProcessingLayer.cs new file mode 100644 index 00000000..3f3d0924 --- /dev/null +++ b/Il2CppInterop.Generator/NativeMethodBodyProcessingLayer.cs @@ -0,0 +1,180 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Runtime; + +namespace Il2CppInterop.Generator; + +public class NativeMethodBodyProcessingLayer : Cpp2IlProcessingLayer +{ + private const int ParameterOffset = 2; + public override string Name => "Native Method Body Processor"; + public override string Id => "native_method_body_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var runtimeInvokeHelper = appContext.ResolveTypeOrThrow(typeof(RuntimeInvoke)); + var invokeAction = runtimeInvokeHelper.GetMethodByName(nameof(RuntimeInvoke.InvokeAction)); + var invokeFunction = runtimeInvokeHelper.GetMethodByName(nameof(RuntimeInvoke.InvokeFunction)); + var getPointerForThis = runtimeInvokeHelper.GetMethodByName(nameof(RuntimeInvoke.GetPointerForThis)); + var getPointerForParameter = runtimeInvokeHelper.GetMethodByName(nameof(RuntimeInvoke.GetPointerForParameter)); + + var intptrPointerType = appContext.SystemTypes.SystemIntPtrType.MakePointerType(); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + foreach (var method in type.Methods) + { + if (method.IsInjected) + continue; + if (method.IsAbstract) + continue; + if (method.IsUnstripped && method.DefaultAttributes.HasFlag(MethodAttributes.Abstract)) + { + // Unstripped abstract methods get converted to virtual methods with a throw implementation. + Debug.Assert(method.IsVirtual); + continue; + } + + if (method.IsUnstripped && method.ICallDelegateField is not null) + { + // Unstripped methods with an ICall delegate field are handled in the method invoker processing layer, so skip them here. + Debug.Assert(method.UnsafeImplementationMethod is null); + continue; + } + + var implementation = method.UnsafeImplementationMethod; + Debug.Assert(implementation is not null); + Debug.Assert(implementation.IsStatic); + + var hasThis = !method.IsStatic; + + Debug.Assert(implementation.Parameters.Count == method.Parameters.Count + (hasThis ? 1 : 0)); + + if (implementation.HasExtraData()) + continue; // Already has a translated body, skip. + + Debug.Assert(!implementation.HasExtraData()); + + if (method.IsUnstripped) + { + // Unstripped methods have no native code, so we skip them here. + continue; + } + + var argumentCount = method.Parameters.Count; + + List instructions = new(); + + LocalVariable? argumentsArrayLocal = null; + if (argumentCount > 0) + { + argumentsArrayLocal = new LocalVariable(intptrPointerType); + + instructions.Add(CilOpCodes.Ldc_I4, argumentCount); + instructions.Add(CilOpCodes.Conv_U); + instructions.Add(CilOpCodes.Sizeof, appContext.SystemTypes.SystemIntPtrType); + instructions.Add(CilOpCodes.Mul_Ovf_Un); + instructions.Add(CilOpCodes.Localloc); + + var startIndex = hasThis ? 1 : 0; + for (var i = 0; i < argumentCount; i++) + { + var parameter = implementation.Parameters[startIndex + i]; + var dataType = ((GenericInstanceTypeAnalysisContext)parameter.ParameterType).GenericArguments[0]; + instructions.Add(CilOpCodes.Dup); + AddOffsetForPointerIndex(instructions, i, appContext); + instructions.Add(CilOpCodes.Ldarg, parameter); + instructions.Add(CilOpCodes.Call, getPointerForParameter.MakeGenericInstanceMethod(dataType)); + instructions.Add(CilOpCodes.Stind_I); + } + instructions.Add(CilOpCodes.Stloc, argumentsArrayLocal); + } + + // Method info + { + var methodInfoField = method.MethodInfoField; + Debug.Assert(methodInfoField is not null); + Debug.Assert(implementation.DeclaringType is not null); + + IReadOnlyList methodInfoGenericArguments = [.. implementation.DeclaringType.GenericParameters, .. implementation.GenericParameters]; + + instructions.Add(CilOpCodes.Ldsfld, methodInfoField.MaybeMakeConcreteGeneric(methodInfoGenericArguments)); + } + + // Object pointer + if (hasThis) + { + var parameter = implementation.Parameters[0]; + var dataType = ((GenericInstanceTypeAnalysisContext)parameter.ParameterType).GenericArguments[0]; + + instructions.Add(CilOpCodes.Ldarg, parameter); + instructions.Add(CilOpCodes.Call, getPointerForThis.MakeGenericInstanceMethod(dataType)); + } + else + { + instructions.Add(CilOpCodes.Ldc_I4_0); + instructions.Add(CilOpCodes.Conv_I); + } + + // Arguments array + if (argumentsArrayLocal is not null) + { + instructions.Add(CilOpCodes.Ldloc, argumentsArrayLocal); + } + else + { + instructions.Add(CilOpCodes.Ldc_I4_0); + instructions.Add(CilOpCodes.Conv_U); + } + + if (implementation.IsVoid) + { + instructions.Add(CilOpCodes.Call, invokeAction); + } + else + { + instructions.Add(CilOpCodes.Call, invokeFunction.MakeGenericInstanceMethod(implementation.ReturnType)); + } + + instructions.Add(CilOpCodes.Ret); + + implementation.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + LocalVariables = argumentsArrayLocal is not null ? [argumentsArrayLocal] : [], + }); + } + } + } + } + + private static void AddOffsetForPointerIndex(List instructions, int index, ApplicationAnalysisContext appContext) + { + if (index != 0) + { + if (index > 1) + { + instructions.Add(CilOpCodes.Ldc_I4, index); + instructions.Add(CilOpCodes.Conv_I); + } + + instructions.Add(CilOpCodes.Sizeof, appContext.SystemTypes.SystemIntPtrType); + + if (index > 1) + { + instructions.Add(CilOpCodes.Mul); + } + + instructions.Add(CilOpCodes.Add); + } + } +} diff --git a/Il2CppInterop.Generator/ObjectInterfaceProcessingLayer.cs b/Il2CppInterop.Generator/ObjectInterfaceProcessingLayer.cs new file mode 100644 index 00000000..ec16173b --- /dev/null +++ b/Il2CppInterop.Generator/ObjectInterfaceProcessingLayer.cs @@ -0,0 +1,229 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class ObjectInterfaceProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Object Interface Processor"; + public override string Id => "object_interface_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + // Find references + var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + var il2CppSystemValueType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.ValueType"); + var il2CppSystemEnum = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Enum"); + + // Inject interfaces + var il2CppSystemIObject = InjectInterface(appContext, "IObject"); + var il2CppSystemIValueType = InjectInterface(appContext, "IValueType"); + var il2CppSystemIEnum = InjectInterface(appContext, "IEnum"); + + // Set KnownType + il2CppSystemIObject.KnownType = KnownTypeCode.Il2CppSystem_IObject; + il2CppSystemIValueType.KnownType = KnownTypeCode.Il2CppSystem_IValueType; + il2CppSystemIEnum.KnownType = KnownTypeCode.Il2CppSystem_IEnum; + + // Il2CppSystem.IValueType : Il2CppSystem.IObject + il2CppSystemIValueType.InterfaceContexts.Add(il2CppSystemIObject); + + // Il2CppSystem.IEnum : Il2CppSystem.IObject, Il2CppSystem.IValueType /* and the other interfaces that Il2CppSystem.Enum implements */ + il2CppSystemIEnum.InterfaceContexts.Add(il2CppSystemIObject); + il2CppSystemIEnum.InterfaceContexts.Add(il2CppSystemIValueType); + foreach (var interfaceContext in il2CppSystemEnum.InterfaceContexts) + { + il2CppSystemIEnum.InterfaceContexts.Add(interfaceContext); + } + + // Il2CppSystem.ValueType : Il2CppSystem.IValueType + il2CppSystemValueType.InterfaceContexts.Add(il2CppSystemIValueType); + + // Il2CppSystem.Enum : Il2CppSystem.IEnum + il2CppSystemEnum.InterfaceContexts.Add(il2CppSystemIEnum); + + // Add methods to interfaces + foreach (var method in il2CppSystemObject.Methods) + { + if (ShouldCreateInterfaceMethod(method)) + { + CreateInterfaceMethod(method, il2CppSystemObject, il2CppSystemIObject); + } + } + foreach (var method in il2CppSystemValueType.Methods) + { + if (ShouldCreateInterfaceMethod(method)) + { + CreateInterfaceMethod(method, il2CppSystemValueType, il2CppSystemIValueType); + } + } + foreach (var method in il2CppSystemEnum.Methods) + { + if (ShouldCreateInterfaceMethod(method)) + { + CreateInterfaceMethod(method, il2CppSystemEnum, il2CppSystemIEnum); + } + } + + // Add interfaces to types + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + type.InterfaceContexts.Add(il2CppSystemIObject); + + if (type.DefaultBaseType == il2CppSystemValueType) + { + type.InterfaceContexts.Add(il2CppSystemIValueType); + } + else if (type.DefaultBaseType == il2CppSystemEnum) + { + type.InterfaceContexts.Add(il2CppSystemIValueType); + type.InterfaceContexts.Add(il2CppSystemIEnum); + } + } + } + + // Implement static constructors for interfaces to set class pointers + ImplementStaticConstructor(il2CppSystemObject, il2CppSystemIObject); + ImplementStaticConstructor(il2CppSystemValueType, il2CppSystemIValueType); + ImplementStaticConstructor(il2CppSystemEnum, il2CppSystemIEnum); + } + + private static bool ShouldCreateInterfaceMethod(MethodAnalysisContext method) + { + if (method.IsConstructor || method.IsStatic || method.IsInjected) + { + return false; + } + if (method.DefaultName == "Finalize") + { + // Only reference types can have a Finalize method, and it can't be called, except by derived finalizers, + // so we can safely exclude it from the interface. + return false; + } + return true; + } + + private static InjectedTypeAnalysisContext InjectInterface(ApplicationAnalysisContext appContext, string name) + { + var result = appContext.Il2CppMscorlib.InjectType("Il2CppSystem", name, null, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract); + result.IsInjected = true; + return result; + } + + private static void CreateInterfaceMethod(MethodAnalysisContext classMethod, TypeAnalysisContext classType, TypeAnalysisContext interfaceType) + { + Debug.Assert(classMethod.DeclaringType == classType); + Debug.Assert(classMethod.GenericParameters.Count == 0); + Debug.Assert(classType.GenericParameters.Count == 0); + Debug.Assert(interfaceType.IsInjected); + + var @explicitlyImplements = classMethod.DefaultName.Contains('.'); + + var interfaceMethod = new InjectedMethodAnalysisContext( + interfaceType, + classMethod.DefaultName, + classMethod.ReturnType, + explicitlyImplements + ? MethodAttributes.Private | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final + : MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.NewSlot, + classMethod.Parameters.Select(p => p.ParameterType).ToArray(), + classMethod.Parameters.Select(p => p.Name).ToArray(), + classMethod.Parameters.Select(p => p.Attributes).ToArray()) + { + OverrideName = classMethod.OverrideName, + }; + interfaceType.Methods.Add(interfaceMethod); + + foreach (var @override in classMethod.Overrides) + { + if (@override.DeclaringType is not { IsInterface: true }) + { + continue; + } + if (@explicitlyImplements) + { + interfaceMethod.Overrides.Add(@override); + @explicitlyImplements = true; + continue; + } + + var overrideImplementation = new InjectedMethodAnalysisContext( + interfaceType, + $"{@override.DeclaringType!.FullName}.{interfaceMethod.Name}", + classMethod.ReturnType, + MethodAttributes.Private | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final, + classMethod.Parameters.Select(p => p.ParameterType).ToArray(), + classMethod.Parameters.Select(p => p.Name).ToArray(), + classMethod.Parameters.Select(p => p.Attributes).ToArray()) + { + OverrideName = classMethod.OverrideName, + IsInjected = true, + }; + interfaceType.Methods.Add(overrideImplementation); + overrideImplementation.Overrides.Add(@override); + + List instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + ..Enumerable.Range(0, classMethod.Parameters.Count).Select(i => new Instruction + { + Code = CilOpCodes.Ldarg, + Operand = overrideImplementation.Parameters[i], + }), + new Instruction(CilOpCodes.Callvirt, interfaceMethod), + new Instruction(CilOpCodes.Ret), + ]; + overrideImplementation.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + }); + } + + classMethod.InterfaceRedirectMethod = interfaceMethod; + if (!classMethod.IsVirtual) + { + // If the class method isn't virtual, we want to make sure it can't be overridden in subclasses. + classMethod.Attributes |= MethodAttributes.Final | MethodAttributes.NewSlot; + } + classMethod.Attributes |= MethodAttributes.Virtual | MethodAttributes.HideBySig; + } + + private static void ImplementStaticConstructor(TypeAnalysisContext classType, TypeAnalysisContext interfaceType) + { + // Set the class pointer for the interface to the class pointer of the class + var il2CppType = interfaceType.AppContext.ResolveTypeOrThrow(typeof(Il2CppType)); + var getClassPointerMethod = il2CppType.Methods.Single(m => m.Name == nameof(Il2CppType.GetClassPointer) && m.GenericParameters.Count == 1); + var setClassPointerMethod = il2CppType.Methods.Single(m => m.Name == nameof(Il2CppType.SetClassPointer) && m.GenericParameters.Count == 1); + + var cctor = new InjectedMethodAnalysisContext( + interfaceType, + ".cctor", + interfaceType.AppContext.SystemTypes.SystemVoidType, + MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + []) + { + IsInjected = true, + }; + interfaceType.Methods.Add(cctor); + cctor.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Call, getClassPointerMethod.MakeGenericInstanceMethod(classType)), + new Instruction(CilOpCodes.Call, setClassPointerMethod.MakeGenericInstanceMethod(interfaceType)), + new Instruction(CilOpCodes.Ret), + ], + }); + } +} diff --git a/Il2CppInterop.Generator/ObjectInternalsProcessingLayer.cs b/Il2CppInterop.Generator/ObjectInternalsProcessingLayer.cs new file mode 100644 index 00000000..14966667 --- /dev/null +++ b/Il2CppInterop.Generator/ObjectInternalsProcessingLayer.cs @@ -0,0 +1,197 @@ +using System.Reflection; +using AsmResolver.DotNet.Code.Cil; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Runtime; + +namespace Il2CppInterop.Generator; + +public class ObjectInternalsProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Object Internals Processor"; + public override string Id => "object_internals_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + + var il2CppStaticClass = appContext.ResolveTypeOrThrow(typeof(IL2CPP)); + var generationInternalsType = appContext.ResolveTypeOrThrow(typeof(GenerationInternals)); + + var il2CppObjectPool = appContext.ResolveTypeOrThrow(typeof(Il2CppObjectPool)); + + var pooledPointerField = new InjectedFieldAnalysisContext( + "_pooledPointer", + appContext.SystemTypes.SystemIntPtrType, + FieldAttributes.Private, + il2CppSystemObject) + { + IsInjected = true, + }; + il2CppSystemObject.Fields.Add(pooledPointerField); + + var gcHandleField = new InjectedFieldAnalysisContext( + "_gcHandle", + appContext.SystemTypes.SystemIntPtrType, + FieldAttributes.Private, + il2CppSystemObject) + { + IsInjected = true, + }; + il2CppSystemObject.Fields.Add(gcHandleField); + + // Constructor + { + var constructor = il2CppSystemObject.PointerConstructor!; + var baseConstructor = appContext.SystemTypes.SystemObjectType.Methods.Single(m => m.IsInstanceConstructor); + var objectPointerType = constructor.Parameters[0].ParameterType; + var objectPointerToSystemIntPtr = objectPointerType.GetExplicitConversionTo(appContext.SystemTypes.SystemIntPtrType); + + constructor.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, baseConstructor), + + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, objectPointerToSystemIntPtr), + new Instruction(CilOpCodes.Stfld, pooledPointerField), + + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, objectPointerToSystemIntPtr), + new Instruction(CilOpCodes.Ldc_I4_0), // false for "pinned" + new Instruction(CilOpCodes.Call, il2CppStaticClass.GetMethodByName(nameof(IL2CPP.il2cpp_gchandle_new))), + new Instruction(CilOpCodes.Stfld, gcHandleField), + + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // Pointer property + { + var getMethod = new InjectedMethodAnalysisContext( + il2CppSystemObject, + $"get_{nameof(Object.Pointer)}", + appContext.SystemTypes.SystemIntPtrType, + MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + []) + { + IsInjected = true, + }; + il2CppSystemObject.Methods.Add(getMethod); + + var property = new InjectedPropertyAnalysisContext( + nameof(Object.Pointer), + appContext.SystemTypes.SystemIntPtrType, + getMethod, + null, + PropertyAttributes.None, + il2CppSystemObject) + { + IsInjected = true, + }; + il2CppSystemObject.Properties.Add(property); + + getMethod.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldfld, gcHandleField), + new Instruction(CilOpCodes.Call, generationInternalsType.GetMethodByName(nameof(GenerationInternals.Il2CppGCHandleGetTargetOrThrow))), + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // WasCollected property + { + var getMethod = new InjectedMethodAnalysisContext( + il2CppSystemObject, + $"get_{nameof(Object.WasCollected)}", + appContext.SystemTypes.SystemBooleanType, + MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName, + []) + { + IsInjected = true, + }; + + il2CppSystemObject.Methods.Add(getMethod); + var property = new InjectedPropertyAnalysisContext( + nameof(Object.WasCollected), + appContext.SystemTypes.SystemBooleanType, + getMethod, + null, + PropertyAttributes.None, + il2CppSystemObject) + { + IsInjected = true, + }; + il2CppSystemObject.Properties.Add(property); + + getMethod.PutExtraData(new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldfld, gcHandleField), + new Instruction(CilOpCodes.Call, generationInternalsType.GetMethodByName(nameof(GenerationInternals.Il2CppGCHandleGetTargetWasCollected))), + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // Finalizer + { + var baseMethod = appContext.SystemTypes.SystemObjectType.GetMethodByName("Finalize"); + var method = new InjectedMethodAnalysisContext( + il2CppSystemObject, + "Finalize", + appContext.SystemTypes.SystemVoidType, + MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual, + []) + { + IsInjected = true, + }; + il2CppSystemObject.Methods.Add(method); + + method.Overrides.Add(baseMethod); // Currently doesn't work because Cpp2IL excludes overrides from non-interface methods + + var instructions = new List(); + + var returnInstruction = new Instruction(CilOpCodes.Ret); + + var tryStart = instructions.Add(CilOpCodes.Ldarg_0); + instructions.Add(CilOpCodes.Ldfld, gcHandleField); + instructions.Add(CilOpCodes.Call, il2CppStaticClass.GetMethodByName(nameof(IL2CPP.il2cpp_gchandle_free))); + + instructions.Add(CilOpCodes.Ldarg_0); + instructions.Add(CilOpCodes.Ldfld, pooledPointerField); + instructions.Add(CilOpCodes.Call, il2CppObjectPool.GetMethodByName(nameof(Il2CppObjectPool.Remove))); + instructions.Add(CilOpCodes.Leave, returnInstruction); + + var handlerStart = instructions.Add(CilOpCodes.Ldarg_0); + instructions.Add(CilOpCodes.Call, baseMethod); + instructions.Add(CilOpCodes.Endfinally); + + instructions.Add(returnInstruction); + + var exceptionHandler = new ExceptionHandler() + { + HandlerType = CilExceptionHandlerType.Finally, + TryStart = tryStart, + TryEnd = handlerStart, + HandlerStart = handlerStart, + HandlerEnd = returnInstruction, + }; + + method.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + ExceptionHandlers = [exceptionHandler], + }); + } + } +} diff --git a/Il2CppInterop.Generator/ObjectOverridesProcessingLayer.cs b/Il2CppInterop.Generator/ObjectOverridesProcessingLayer.cs new file mode 100644 index 00000000..94cfb7f2 --- /dev/null +++ b/Il2CppInterop.Generator/ObjectOverridesProcessingLayer.cs @@ -0,0 +1,124 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class ObjectOverridesProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Object Overrides Processor"; + public override string Id => "object_overrides_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var systemObject = appContext.SystemTypes.SystemObjectType; + var il2CppSystemIObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IObject"); + + ImplementVirtualMethodsOnIl2CppSystemObject(appContext, systemObject, il2CppSystemIObject); + ImplementVirtualMethodsOnStructsAndEnums(appContext, systemObject, il2CppSystemIObject); + } + + private static void ImplementVirtualMethodsOnIl2CppSystemObject(ApplicationAnalysisContext appContext, TypeAnalysisContext systemObject, TypeAnalysisContext il2CppSystemIObject) + { + var il2CppSystemObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + + (MethodAnalysisContext, MethodAnalysisContext)[] virtualMethods = systemObject.Methods + .Where(m => m.IsVirtual && m.DefaultName is not "Finalize") + .Select(m => + ( + m, + il2CppSystemObject.Methods.Single(i => i.DefaultName == m.DefaultName && !i.IsStatic) + )) + .ToArray(); + + ImplementVirtualMethods(systemObject, il2CppSystemIObject, virtualMethods, il2CppSystemObject, false); + } + + private static void ImplementVirtualMethodsOnStructsAndEnums(ApplicationAnalysisContext appContext, TypeAnalysisContext systemObject, TypeAnalysisContext il2CppSystemIObject) + { + var il2CppSystemValueType = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.ValueType"); + var il2CppSystemEnum = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Enum"); + + (MethodAnalysisContext, MethodAnalysisContext)[] virtualMethods = systemObject.Methods + .Where(m => m.IsVirtual && m.DefaultName is not "Finalize") + .Select(m => + ( + m, + il2CppSystemIObject.Methods.Single(i => i.DefaultName == m.DefaultName) + )) + .ToArray(); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (!type.IsValueType) + continue; + + Debug.Assert(type.DefaultBaseType == il2CppSystemEnum || type.DefaultBaseType == il2CppSystemValueType); + Debug.Assert(type != il2CppSystemEnum); + + if (type.IsInjected) + continue; + ImplementVirtualMethods(systemObject, il2CppSystemIObject, virtualMethods, type, true); + } + } + } + + private static void ImplementVirtualMethods(TypeAnalysisContext systemObject, TypeAnalysisContext il2CppSystemIObject, (MethodAnalysisContext, MethodAnalysisContext)[] virtualMethods, TypeAnalysisContext type, bool isValueType) + { + foreach ((var systemMethod, var il2CppMethod) in virtualMethods) + { + // Overrides on value types cannot be sealed + var attributes = isValueType + ? MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig + : MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; + var newMethod = new InjectedMethodAnalysisContext( + type, + systemMethod.DefaultName, + systemMethod.ReturnType, + attributes, + systemMethod.Parameters.Select(p => p.ParameterType).ToArray(), + systemMethod.Parameters.Select(p => p.Name).ToArray(), + systemMethod.Parameters.Select(p => p.Attributes).ToArray()) + { + IsInjected = true, + }; + type.Methods.Add(newMethod); + + List instructions = + [ + new Instruction(CilOpCodes.Ldarg_0), + ]; + if (isValueType) + { + // Value types need to be boxed + var instantiatedType = type.SelfInstantiateIfGeneric(); + instructions.Add(CilOpCodes.Ldobj, instantiatedType); + instructions.Add(CilOpCodes.Box, instantiatedType); + } + for (var i = 0; i < newMethod.Parameters.Count; i++) + { + var parameter = newMethod.Parameters[i]; + instructions.Add(CilOpCodes.Ldarg, parameter); + if (parameter.ParameterType == systemObject) + { + Debug.Assert(il2CppMethod.Parameters[i].ParameterType == il2CppSystemIObject); + instructions.Add(CilOpCodes.Isinst, il2CppSystemIObject); + } + } + instructions.Add(CilOpCodes.Callvirt, il2CppMethod); + instructions.Add(CilOpCodes.Call, il2CppMethod.ReturnType.GetImplicitConversionTo(newMethod.ReturnType)); + instructions.Add(CilOpCodes.Ret); + + newMethod.PutExtraData(new NativeMethodBody() + { + Instructions = instructions + }); + } + } +} diff --git a/Il2CppInterop.Generator/Operands/EndLabel.cs b/Il2CppInterop.Generator/Operands/EndLabel.cs new file mode 100644 index 00000000..bfcf3f5c --- /dev/null +++ b/Il2CppInterop.Generator/Operands/EndLabel.cs @@ -0,0 +1,9 @@ +namespace Il2CppInterop.Generator.Operands; + +public sealed class EndLabel : ILabel +{ + public static readonly EndLabel Instance = new(); + private EndLabel() + { + } +} diff --git a/Il2CppInterop.Generator/Operands/ExceptionHandler.cs b/Il2CppInterop.Generator/Operands/ExceptionHandler.cs new file mode 100644 index 00000000..0dfcc9f4 --- /dev/null +++ b/Il2CppInterop.Generator/Operands/ExceptionHandler.cs @@ -0,0 +1,15 @@ +using AsmResolver.DotNet.Code.Cil; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Operands; + +public sealed record class ExceptionHandler +{ + public CilExceptionHandlerType HandlerType { get; set; } + public ILabel? TryStart { get; set; } + public ILabel? TryEnd { get; set; } + public ILabel? HandlerStart { get; set; } + public ILabel? HandlerEnd { get; set; } + public ILabel? FilterStart { get; set; } + public TypeAnalysisContext? ExceptionType { get; set; } +} diff --git a/Il2CppInterop.Generator/Operands/ILabel.cs b/Il2CppInterop.Generator/Operands/ILabel.cs new file mode 100644 index 00000000..74699d15 --- /dev/null +++ b/Il2CppInterop.Generator/Operands/ILabel.cs @@ -0,0 +1,5 @@ +namespace Il2CppInterop.Generator.Operands; + +public interface ILabel +{ +} diff --git a/Il2CppInterop.Generator/Operands/Instruction.cs b/Il2CppInterop.Generator/Operands/Instruction.cs new file mode 100644 index 00000000..1bfeb5e0 --- /dev/null +++ b/Il2CppInterop.Generator/Operands/Instruction.cs @@ -0,0 +1,102 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Operands; + +public sealed class Instruction : ILabel +{ + public CilOpCode Code { get; set; } + public object? Operand { get; set; } + + public Instruction() + { + } + + public Instruction(CilOpCode code) + { + Code = code; + } + + public Instruction(CilOpCode code, object? operand) + { + Code = code; + Operand = operand; + } + + public override string ToString() => $"{Code} {Operand}"; + + public int GetPopCount(MethodAnalysisContext owner) + { + return Code.StackBehaviourPop switch + { + CilStackBehaviour.Pop0 => 0, + CilStackBehaviour.Pop1 or CilStackBehaviour.PopI or CilStackBehaviour.PopRef => 1, + CilStackBehaviour.Pop1_Pop1 or CilStackBehaviour.PopI_Pop1 or CilStackBehaviour.PopI_PopI or CilStackBehaviour.PopI_PopI8 or CilStackBehaviour.PopI_PopR4 or CilStackBehaviour.PopI_PopR8 or CilStackBehaviour.PopRef_Pop1 or CilStackBehaviour.PopRef_PopI => 2, + CilStackBehaviour.PopRef_PopI_Pop1 or CilStackBehaviour.PopI_PopI_PopI or CilStackBehaviour.PopRef_PopI_PopI or CilStackBehaviour.PopRef_PopI_PopI8 or CilStackBehaviour.PopRef_PopI_PopR4 or CilStackBehaviour.PopRef_PopI_PopR8 or CilStackBehaviour.PopRef_PopI_PopRef => 3, + CilStackBehaviour.VarPop => Code.Code switch + { + CilCode.Ret => IsVoid(owner.ReturnType) ? 0 : 1, + CilCode.Call or CilCode.Callvirt => Operand switch + { + MethodAnalysisContext method => method.Parameters.Count + (method.IsStatic ? 0 : 1), + MultiDimensionalArrayMethod multiArrayMethod => multiArrayMethod.MethodType switch + { + MultiDimensionalArrayMethodType.Get => multiArrayMethod.Rank, + MultiDimensionalArrayMethodType.Set => multiArrayMethod.Rank + 1, + MultiDimensionalArrayMethodType.Address => multiArrayMethod.Rank, + _ => throw new NotImplementedException($"Unhandled multidimensional array method type for {Code.Code}"), + } + 1, + _ => throw new NotImplementedException($"Unhandled operand type for {Code.Code}"), + }, + CilCode.Newobj => Operand switch + { + MethodAnalysisContext method => method.Parameters.Count, + MultiDimensionalArrayMethod multiArrayMethod => multiArrayMethod.MethodType switch + { + MultiDimensionalArrayMethodType.Constructor => multiArrayMethod.Rank, + _ => throw new NotImplementedException($"Unhandled multidimensional array method type for {Code.Code}"), + }, + _ => throw new NotImplementedException($"Unhandled operand type for {Code.Code}"), + }, + _ => throw new NotImplementedException($"Unhandled var pop count for {Code.Code}"), + }, + CilStackBehaviour.PopAll => -1, // Special case for 'pop all' + _ => throw new NotImplementedException($"Unhandled pop count for {Code.StackBehaviourPop}"), + }; + } + + public int GetPushCount(MethodAnalysisContext owner) + { + return Code.StackBehaviourPush switch + { + CilStackBehaviour.Push0 => 0, + CilStackBehaviour.Push1 or CilStackBehaviour.PushI or CilStackBehaviour.PushI8 or CilStackBehaviour.PushR4 or CilStackBehaviour.PushR8 or CilStackBehaviour.PushRef => 1, + CilStackBehaviour.Push1_Push1 => 2, + CilStackBehaviour.VarPush => Code.Code switch + { + CilCode.Call or CilCode.Callvirt => Operand switch + { + MethodAnalysisContext method => IsVoid(method.ReturnType) ? 0 : 1, + MultiDimensionalArrayMethod multiArrayMethod => multiArrayMethod.MethodType switch + { + MultiDimensionalArrayMethodType.Get or MultiDimensionalArrayMethodType.Address => 1, + MultiDimensionalArrayMethodType.Set or MultiDimensionalArrayMethodType.Constructor => 0, + _ => throw new NotImplementedException($"Unhandled multidimensional array method type for {Code.Code}"), + }, + _ => throw new NotImplementedException($"Unhandled operand type for {Code.Code}"), + }, + _ => throw new NotImplementedException($"Unhandled var push count for {Code.Code}"), + }, + _ => throw new NotImplementedException($"Unhandled push count for {Code.StackBehaviourPush}"), + }; + } + + private static bool IsVoid(TypeAnalysisContext type) + { + if (type.Name is not "Void") + { + return false; + } + + return type == type.AppContext.SystemTypes.SystemVoidType || type == type.AppContext.Il2CppMscorlib.GetTypeByFullName("Il2CppSystem.Void"); + } +} diff --git a/Il2CppInterop.Generator/Operands/LocalVariable.cs b/Il2CppInterop.Generator/Operands/LocalVariable.cs new file mode 100644 index 00000000..4e13a78d --- /dev/null +++ b/Il2CppInterop.Generator/Operands/LocalVariable.cs @@ -0,0 +1,19 @@ +using System.Diagnostics.CodeAnalysis; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Operands; + +public sealed class LocalVariable +{ + public required TypeAnalysisContext Type { get; init; } + + public LocalVariable() + { + } + + [SetsRequiredMembers] + public LocalVariable(TypeAnalysisContext type) + { + Type = type; + } +} diff --git a/Il2CppInterop.Generator/Operands/MultiDimensionalArrayMethod.cs b/Il2CppInterop.Generator/Operands/MultiDimensionalArrayMethod.cs new file mode 100644 index 00000000..2a075c7d --- /dev/null +++ b/Il2CppInterop.Generator/Operands/MultiDimensionalArrayMethod.cs @@ -0,0 +1,35 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Utils.AsmResolver; + +namespace Il2CppInterop.Generator.Operands; + +public sealed record class MultiDimensionalArrayMethod(ArrayTypeAnalysisContext ArrayType, MultiDimensionalArrayMethodType MethodType) +{ + public int Rank => ArrayType.Rank; + + public IMethodDescriptor ToMethodDescriptor(ModuleDefinition module) + { + var arrayTypeSignature = ArrayType.ToTypeSignature(module); + var elementTypeSignature = arrayTypeSignature.BaseType; + var indexParameters = Enumerable.Repeat(module.CorLibTypeFactory.Int32, ArrayType.Rank); + var methodSignature = MethodType switch + { + MultiDimensionalArrayMethodType.Get => MethodSignature.CreateInstance(elementTypeSignature, indexParameters), + MultiDimensionalArrayMethodType.Set => MethodSignature.CreateInstance(module.CorLibTypeFactory.Void, indexParameters.Append(elementTypeSignature)), + MultiDimensionalArrayMethodType.Constructor => MethodSignature.CreateInstance(module.CorLibTypeFactory.Void, indexParameters), + MultiDimensionalArrayMethodType.Address => MethodSignature.CreateInstance(elementTypeSignature.MakeByReferenceType(), indexParameters), + _ => throw new InvalidOperationException($"Unknown {nameof(MultiDimensionalArrayMethodType)}: {MethodType}"), + }; + var methodName = MethodType switch + { + MultiDimensionalArrayMethodType.Get => "Get", + MultiDimensionalArrayMethodType.Set => "Set", + MultiDimensionalArrayMethodType.Constructor => ".ctor", + MultiDimensionalArrayMethodType.Address => "Address", + _ => null, + }; + return new MemberReference(arrayTypeSignature.ToTypeDefOrRef(), methodName, methodSignature); + } +} diff --git a/Il2CppInterop.Generator/Operands/MultiDimensionalArrayMethodType.cs b/Il2CppInterop.Generator/Operands/MultiDimensionalArrayMethodType.cs new file mode 100644 index 00000000..6d3c47da --- /dev/null +++ b/Il2CppInterop.Generator/Operands/MultiDimensionalArrayMethodType.cs @@ -0,0 +1,21 @@ +namespace Il2CppInterop.Generator.Operands; + +public enum MultiDimensionalArrayMethodType +{ + /// + /// call instance bool bool[0..., 0...]::Get(int32, int32) + /// + Get, + /// + /// call instance void bool[0..., 0...]::Set(int32, int32, bool) + /// + Set, + /// + /// newobj instance void bool[0..., 0...]::.ctor(int32, int32) + /// + Constructor, + /// + /// call instance bool& bool[0..., 0...]::Address(int32, int32) + /// + Address, +} diff --git a/Il2CppInterop.Generator/Operands/This.cs b/Il2CppInterop.Generator/Operands/This.cs new file mode 100644 index 00000000..1aced9d7 --- /dev/null +++ b/Il2CppInterop.Generator/Operands/This.cs @@ -0,0 +1,9 @@ +namespace Il2CppInterop.Generator.Operands; + +public sealed record class This +{ + public static This Instance { get; } = new This(); + private This() + { + } +} diff --git a/Il2CppInterop.Generator/OriginalMethodBody.cs b/Il2CppInterop.Generator/OriginalMethodBody.cs new file mode 100644 index 00000000..1d0c026d --- /dev/null +++ b/Il2CppInterop.Generator/OriginalMethodBody.cs @@ -0,0 +1,157 @@ +using System.Diagnostics.CodeAnalysis; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Collections; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class OriginalMethodBody : MethodBodyBase +{ + public static bool MaybeStoreOriginalMethodBody(MethodDefinition originalMethod, MethodAnalysisContext methodContext, RuntimeContext runtimeContext) + { + if (TryResolveOriginalMethodBody(originalMethod, methodContext, runtimeContext, out var originalMethodBody)) + { + methodContext.PutExtraData(originalMethodBody); + return true; + } + else + { + return false; + } + } + + private static bool TryResolveOriginalMethodBody(MethodDefinition method, MethodAnalysisContext methodContext, RuntimeContext runtimeContext, [NotNullWhen(true)] out OriginalMethodBody? originalMethodBody) + { + var body = method.CilMethodBody; + if (body is null) + { + originalMethodBody = null; + return false; + } + + var resolver = new ContextResolver(methodContext, runtimeContext); + + var originalInstructions = body.Instructions; + originalInstructions.ExpandMacros(); + + var newInstructions = new Instruction[originalInstructions.Count]; + for (var i = 0; i < newInstructions.Length; i++) + { + newInstructions[i] = new Instruction + { + Code = originalInstructions[i].OpCode, + }; + } + + var newLocalVariables = new LocalVariable[body.LocalVariables.Count]; + for (var i = 0; i < newLocalVariables.Length; i++) + { + var localVariable = body.LocalVariables[i]; + var localVariableType = resolver.Resolve(localVariable.VariableType); + if (localVariableType is null) + { + originalMethodBody = default; + return false; + } + newLocalVariables[i] = new LocalVariable + { + Type = localVariableType, + }; + } + + var newExceptionHandlers = new ExceptionHandler[body.ExceptionHandlers.Count]; + for (var i = 0; i < newExceptionHandlers.Length; i++) + { + var exceptionHandler = body.ExceptionHandlers[i]; + var handlerType = exceptionHandler.HandlerType; + + var tryStart = ResolveLabel(newInstructions, originalInstructions, exceptionHandler.TryStart); + var tryEnd = ResolveLabel(newInstructions, originalInstructions, exceptionHandler.TryEnd); + var handlerStart = ResolveLabel(newInstructions, originalInstructions, exceptionHandler.HandlerStart); + var handlerEnd = ResolveLabel(newInstructions, originalInstructions, exceptionHandler.HandlerEnd); + var filterStart = ResolveLabel(newInstructions, originalInstructions, exceptionHandler.FilterStart); + TypeAnalysisContext? exceptionType; + if (exceptionHandler.ExceptionType is null) + { + exceptionType = null; + } + else + { + exceptionType = resolver.Resolve(exceptionHandler.ExceptionType); + if (exceptionType is null) + { + originalMethodBody = default; + return false; + } + } + newExceptionHandlers[i] = new ExceptionHandler + { + HandlerType = handlerType, + TryStart = tryStart, + TryEnd = tryEnd, + HandlerStart = handlerStart, + HandlerEnd = handlerEnd, + FilterStart = filterStart, + ExceptionType = exceptionType, + }; + } + + for (var i = 0; i < originalInstructions.Count; i++) + { + var instruction = originalInstructions[i]; + var operand = instruction.Operand; + var resolved = operand switch + { + null or byte or sbyte or short or ushort or int or uint or long or ulong or float or double or bool or char => operand, + string @string => @string, + AsmResolver.Utf8String utf8String => utf8String.ToString(), + CilLocalVariable localVariable => newLocalVariables[localVariable.Index], + Parameter parameter => parameter == method.Parameters.ThisParameter ? This.Instance : methodContext.Parameters[parameter.Index], + ITypeDescriptor typeDescriptor => resolver.Resolve(typeDescriptor), + IFieldDescriptor { Signature: not null } fieldDescriptor => resolver.Resolve(fieldDescriptor), + IMethodDescriptor { Signature: not null } methodDescriptor => resolver.Resolve(methodDescriptor), + ICilLabel label => ResolveLabel(newInstructions, originalInstructions, label), + IReadOnlyList labels => ResolveOperand(labels, originalInstructions, newInstructions), + StandAloneSignature => null,// Not currently supported + _ => null, + }; + if (resolved is null && operand is not null) + { + originalMethodBody = default; + return false; + } + newInstructions[i].Operand = resolved; + } + + originalMethodBody = new OriginalMethodBody + { + Instructions = newInstructions, + LocalVariables = newLocalVariables, + ExceptionHandlers = newExceptionHandlers, + }; + return true; + } + + private static ILabel[] ResolveOperand(IReadOnlyList labels, CilInstructionCollection originalInstructions, Instruction[] newInstructions) + { + var resolved = new ILabel[labels.Count]; + for (var i = 0; i < labels.Count; i++) + { + resolved[i] = ResolveLabel(newInstructions, originalInstructions, labels[i]); + } + return resolved; + } + + [return: NotNullIfNotNull(nameof(label))] + private static ILabel? ResolveLabel(Instruction[] newInstructions, CilInstructionCollection originalInstructions, ICilLabel? label) => label switch + { + null => null, + CilInstructionLabel instructionLabel => instructionLabel.Instruction is not null + ? newInstructions[originalInstructions.IndexOf(instructionLabel.Instruction)] + : throw new ArgumentException("Instruction label must reference an instruction", nameof(label)), + _ when label.GetType().Name == "CilEndLabel" => EndLabel.Instance, + _ => throw new ArgumentException($"Label is an unsupported type: {label.GetType()}", nameof(label)), + }; +} diff --git a/Il2CppInterop.Generator/OverrideRenamingProcessingLayer.cs b/Il2CppInterop.Generator/OverrideRenamingProcessingLayer.cs new file mode 100644 index 00000000..6ec7af97 --- /dev/null +++ b/Il2CppInterop.Generator/OverrideRenamingProcessingLayer.cs @@ -0,0 +1,40 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +/// +/// Poorly implemented obfuscation can cause method names to differ between base and derived methods, which causes issues for virtual method dispatch if not corrected. +/// +public class OverrideRenamingProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Override Name Changes"; + public override string Id => "overridenamechanges"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + RenameMembers(type); + } + } + } + private static void RenameMembers(TypeAnalysisContext type) + { + foreach (var method in type.Methods) + { + if (method.IsInjected) + continue; + var baseMethod = method.UltimateBaseMethod; + if (baseMethod is not null && baseMethod.DefaultName != method.DefaultName) + { + method.Name = baseMethod.Name; + } + } + } +} diff --git a/Il2CppInterop.Generator/Passes/Pass05CreateRenameGroups.cs b/Il2CppInterop.Generator/Passes/Pass05CreateRenameGroups.cs deleted file mode 100644 index e48edbc3..00000000 --- a/Il2CppInterop.Generator/Passes/Pass05CreateRenameGroups.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System.Text; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass05CreateRenameGroups -{ - private static readonly string[] ClassAccessNames = - {"Private", "Public", "NPublic", "NPrivate", "NProtected", "NInternal", "NFamAndAssem", "NFamOrAssem"}; - - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var originalType in assemblyContext.OriginalAssembly.ManifestModule!.TopLevelTypes) - ProcessType(context, originalType, false); - - var typesToRemove = context.RenameGroups.Where(it => it.Value.Count > 1).ToList(); - foreach (var keyValuePair in typesToRemove) - { - context.RenameGroups.Remove(keyValuePair.Key); - foreach (var typeDefinition in keyValuePair.Value) - context.RenamedTypes.Remove(typeDefinition); - } - - foreach (var contextRenamedType in context.RenamedTypes) - context.PreviousRenamedTypes[contextRenamedType.Key] = contextRenamedType.Value; - - foreach (var assemblyContext in context.Assemblies) - foreach (var originalType in assemblyContext.OriginalAssembly.ManifestModule!.TopLevelTypes) - ProcessType(context, originalType, true); - } - - private static void ProcessType(RewriteGlobalContext context, TypeDefinition originalType, - bool allowExtraHeuristics) - { - foreach (var nestedType in originalType.NestedTypes) - ProcessType(context, nestedType, allowExtraHeuristics); - - if (context.RenamedTypes.ContainsKey(originalType)) return; - - var unobfuscatedName = GetUnobfuscatedNameBase(context, originalType, allowExtraHeuristics); - if (unobfuscatedName == null) return; - - context.RenameGroups - .GetOrCreate( - ((object?)originalType.DeclaringType ?? originalType.Namespace, unobfuscatedName, - originalType.GenericParameters.Count), _ => new List()).Add(originalType); - context.RenamedTypes[originalType] = unobfuscatedName; - } - - private static string? GetUnobfuscatedNameBase(RewriteGlobalContext context, TypeDefinition typeDefinition, - bool allowExtraHeuristics) - { - var options = context.Options; - if (options.PassthroughNames || !typeDefinition.Name.IsObfuscated(context.Options)) return null; - - var inheritanceDepth = 0; - var firstUnobfuscatedType = typeDefinition.BaseType; - while (firstUnobfuscatedType != null && firstUnobfuscatedType.Name.IsObfuscated(context.Options)) - { - firstUnobfuscatedType = firstUnobfuscatedType.Resolve()?.BaseType?.Resolve(); - inheritanceDepth++; - } - - var unobfuscatedInterfacesList = typeDefinition.Interfaces.Select(it => it.Interface!) - .Where(it => !it!.Name.IsObfuscated(context.Options)); - var accessName = ClassAccessNames[(int)(typeDefinition.Attributes & TypeAttributes.VisibilityMask)]; - - var classifier = typeDefinition.IsInterface ? "Interface" : typeDefinition.IsValueType() ? "Struct" : "Class"; - var compilerGenertaedString = typeDefinition.Name.StartsWith("<") ? "CompilerGenerated" : ""; - var abstractString = typeDefinition.IsAbstract ? "Abstract" : ""; - var sealedString = typeDefinition.IsSealed ? "Sealed" : ""; - var specialNameString = typeDefinition.IsSpecialName ? "SpecialName" : ""; - - var nameBuilder = new StringBuilder(); - nameBuilder.Append(firstUnobfuscatedType?.ToTypeSignature().GenericNameToStrings(context)?.ConcatAll() ?? classifier); - if (inheritanceDepth > 0) - nameBuilder.Append(inheritanceDepth); - nameBuilder.Append(compilerGenertaedString); - nameBuilder.Append(accessName); - nameBuilder.Append(abstractString); - nameBuilder.Append(sealedString); - nameBuilder.Append(specialNameString); - foreach (var interfaceRef in unobfuscatedInterfacesList) - nameBuilder.Append(interfaceRef.ToTypeSignature().GenericNameToStrings(context).ConcatAll()); - - var uniqContext = new UniquificationContext(options); - foreach (var fieldDef in typeDefinition.Fields) - { - if (!typeDefinition.IsEnum) - uniqContext.Push(fieldDef.Signature!.FieldType.GenericNameToStrings(context)); - - uniqContext.Push(fieldDef.Name!); - - if (uniqContext.CheckFull()) break; - } - - if (typeDefinition.IsEnum) - uniqContext.Push(typeDefinition.Fields.Count + "v"); - - foreach (var propertyDef in typeDefinition.Properties) - { - uniqContext.Push(propertyDef.Signature!.ReturnType.GenericNameToStrings(context)); - uniqContext.Push(propertyDef.Name!); - - if (uniqContext.CheckFull()) break; - } - - if (firstUnobfuscatedType?.Name == "MulticastDelegate") - { - var invokeMethod = typeDefinition.Methods.SingleOrDefault(it => it.Name == "Invoke"); - if (invokeMethod != null) - { - uniqContext.Push(invokeMethod.Signature!.ReturnType.GenericNameToStrings(context)); - - foreach (var parameterDef in invokeMethod.Parameters) - { - uniqContext.Push(parameterDef.ParameterType.GenericNameToStrings(context)); - if (uniqContext.CheckFull()) break; - } - } - } - - if (typeDefinition.IsInterface || - allowExtraHeuristics) // method order on non-interface types appears to be unstable - foreach (var methodDefinition in typeDefinition.Methods) - { - uniqContext.Push(methodDefinition.Name!); - uniqContext.Push(methodDefinition.Signature!.ReturnType.GenericNameToStrings(context)); - - foreach (var parameter in methodDefinition.Parameters) - { - uniqContext.Push(parameter.Name); - uniqContext.Push(parameter.ParameterType.GenericNameToStrings(context)); - - if (uniqContext.CheckFull()) break; - } - - if (uniqContext.CheckFull()) break; - } - - nameBuilder.Append(uniqContext.GetTop()); - - return nameBuilder.ToString(); - } - - private static string ConcatAll(this List strings) - { - return string.Concat(strings); - } - - private static string NameOrRename(this TypeSignature typeRef, RewriteGlobalContext context) - { - var resolved = typeRef.Resolve(); - if (resolved != null && context.PreviousRenamedTypes.TryGetValue(resolved, out var rename)) - return (rename.StableHash() % (ulong)Math.Pow(10, context.Options.TypeDeobfuscationCharsPerUniquifier)) - .ToString(); - - return typeRef.Name!; - } - - private static List GenericNameToStrings(this TypeSignature typeRef, RewriteGlobalContext context) - { - if (typeRef is SzArrayTypeSignature szArrayType) - return szArrayType.BaseType.GenericNameToStrings(context); - - if (typeRef is ArrayTypeSignature arrayType) - return arrayType.BaseType.GenericNameToStrings(context); - - if (typeRef is GenericInstanceTypeSignature genericInstance) - { - var baseTypeName = genericInstance.GenericType.ToTypeSignature().NameOrRename(context); - var indexOfBacktick = baseTypeName.IndexOf('`'); - if (indexOfBacktick >= 0) - baseTypeName = baseTypeName.Substring(0, indexOfBacktick); - - var entries = new List(); - - entries.Add(baseTypeName); - entries.Add(genericInstance.TypeArguments.Count.ToString()); - foreach (var genericArgument in genericInstance.TypeArguments) - entries.AddRange(genericArgument.GenericNameToStrings(context)); - return entries; - } - - if (typeRef.NameOrRename(context).IsObfuscated(context.Options)) - return new List { "Obf" }; - - return new List { typeRef.NameOrRename(context) }; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass10CreateTypedefs.cs b/Il2CppInterop.Generator/Passes/Pass10CreateTypedefs.cs deleted file mode 100644 index f3c277d0..00000000 --- a/Il2CppInterop.Generator/Passes/Pass10CreateTypedefs.cs +++ /dev/null @@ -1,118 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass10CreateTypedefs -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var type in assemblyContext.OriginalAssembly.ManifestModule!.TopLevelTypes) - if (!IsCpp2ILInjectedType(type) && type.Name != "") - ProcessType(type, assemblyContext, null); - - static bool IsCpp2ILInjectedType(TypeDefinition type) => type.Namespace?.Value.StartsWith("Cpp2ILInjected", StringComparison.Ordinal) ?? false; - } - - private static void ProcessType(TypeDefinition type, AssemblyRewriteContext assemblyContext, - TypeDefinition? parentType) - { - var convertedTypeName = GetConvertedTypeName(assemblyContext.GlobalContext, type, parentType); - var newType = - new TypeDefinition( - convertedTypeName.Namespace ?? GetNamespace(type, assemblyContext), - convertedTypeName.Name, AdjustAttributes(type.Attributes)); - newType.IsSequentialLayout = false; - - if (type.IsSealed && type.IsAbstract) // is static - newType.IsSealed = newType.IsAbstract = true; - - if (parentType == null) - { - assemblyContext.NewAssembly.ManifestModule!.TopLevelTypes.Add(newType); - } - else - { - parentType.NestedTypes.Add(newType); - } - - foreach (var typeNestedType in type.NestedTypes) - ProcessType(typeNestedType, assemblyContext, newType); - - assemblyContext.RegisterTypeRewrite(new TypeRewriteContext(assemblyContext, type, newType)); - - static string? GetNamespace(TypeDefinition type, AssemblyRewriteContext assemblyContext) - { - if (type.Name?.Value is "" || type.DeclaringType is not null) - return type.Namespace; - else - return type.Namespace.UnSystemify(assemblyContext.GlobalContext.Options); - } - } - - internal static (string? Namespace, string Name) GetConvertedTypeName( - RewriteGlobalContext assemblyContextGlobalContext, TypeDefinition type, TypeDefinition? enclosingType) - { - if (assemblyContextGlobalContext.Options.PassthroughNames) - return (null, type.Name!); - - if (type.Name.IsObfuscated(assemblyContextGlobalContext.Options)) - { - var newNameBase = assemblyContextGlobalContext.RenamedTypes[type]; - var genericParametersCount = type.GenericParameters.Count; - var renameGroup = - assemblyContextGlobalContext.RenameGroups[ - ((object?)type.DeclaringType ?? type.Namespace, newNameBase, genericParametersCount)]; - var genericSuffix = genericParametersCount == 0 ? "" : "`" + genericParametersCount; - var convertedTypeName = newNameBase + - (renameGroup.Count == 1 ? "Unique" : renameGroup.IndexOf(type).ToString()) + - genericSuffix; - - var fullName = enclosingType == null - ? type.Namespace - : enclosingType.GetNamespacePrefix() + "." + enclosingType.Name; - - if (assemblyContextGlobalContext.Options.RenameMap.TryGetValue(fullName + "." + convertedTypeName, - out var newName)) - { - if (type.DeclaringModule!.TopLevelTypes.Any(t => t.FullName == newName)) - { - Logger.Instance.LogWarning("[Rename map issue] {NewName} already exists in {ModuleName} (mapped from {MappedNamespace}.{MappedType})", - newName, type.DeclaringModule.Name, fullName, convertedTypeName); - newName += "_Duplicate"; - } - - var lastDotPosition = newName.LastIndexOf("."); - if (lastDotPosition >= 0) - { - var ns = newName.Substring(0, lastDotPosition); - var name = newName.Substring(lastDotPosition + 1); - return (ns, name); - } - - convertedTypeName = newName; - } - - return (null, convertedTypeName); - } - - return (null, type.Name.MakeValidInSource()); - } - - private static TypeAttributes AdjustAttributes(TypeAttributes typeAttributes) - { - typeAttributes |= TypeAttributes.BeforeFieldInit; - typeAttributes &= ~(TypeAttributes.Abstract | TypeAttributes.Interface); - - var visibility = typeAttributes & TypeAttributes.VisibilityMask; - if (visibility == 0 || visibility == TypeAttributes.Public) - return typeAttributes | TypeAttributes.Public; - - return (typeAttributes & ~TypeAttributes.VisibilityMask) | TypeAttributes.NestedPublic; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs b/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs deleted file mode 100644 index b50cd2e8..00000000 --- a/Il2CppInterop.Generator/Passes/Pass11ComputeTypeSpecifics.cs +++ /dev/null @@ -1,53 +0,0 @@ -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass11ComputeTypeSpecifics -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - ComputeSpecifics(typeContext); - } - - private static void ComputeSpecifics(TypeRewriteContext typeContext) - { - if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.NotComputed) return; - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.Computing; - - foreach (var originalField in typeContext.OriginalType.Fields) - { - // Sometimes il2cpp metadata has invalid field offsets for some reason (https://github.com/SamboyCoding/Cpp2IL/issues/167) - if (originalField.ExtractFieldOffset() >= 0x8000000) - { - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; - return; - } - - if (originalField.IsStatic) continue; - - var fieldType = originalField.Signature!.FieldType; - if (fieldType.IsPrimitive() || fieldType is PointerTypeSignature) - continue; - if (fieldType.FullName == "System.String" || fieldType.FullName == "System.Object" - || fieldType is ArrayBaseTypeSignature or ByReferenceTypeSignature or GenericParameterSignature or GenericInstanceTypeSignature) - { - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; - return; - } - - var fieldTypeContext = typeContext.AssemblyContext.GlobalContext.GetNewTypeForOriginal(fieldType.Resolve()!); - ComputeSpecifics(fieldTypeContext); - if (fieldTypeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct) - { - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.NonBlittableStruct; - return; - } - } - - typeContext.ComputedTypeSpecifics = TypeRewriteContext.TypeSpecifics.BlittableStruct; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs b/Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs deleted file mode 100644 index 39b9f5b1..00000000 --- a/Il2CppInterop.Generator/Passes/Pass12FillTypedefs.cs +++ /dev/null @@ -1,35 +0,0 @@ -using AsmResolver.DotNet; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass12FillTypedefs -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - { - foreach (var originalParameter in typeContext.OriginalType.GenericParameters) - { - var newParameter = new GenericParameter(originalParameter.Name.MakeValidInSource(), - originalParameter.Attributes.StripValueTypeConstraint()); - typeContext.NewType.GenericParameters.Add(newParameter); - } - - if (typeContext.OriginalType.IsEnum) - typeContext.NewType.BaseType = assemblyContext.Imports.Module.Enum().ToTypeDefOrRef(); - else if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct) - typeContext.NewType.BaseType = assemblyContext.Imports.Module.ValueType().ToTypeDefOrRef(); - } - - // Second pass is explicitly done after first to account for rewriting of generic base types - value-typeness is important there - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - if (!typeContext.OriginalType.IsEnum && typeContext.ComputedTypeSpecifics != - TypeRewriteContext.TypeSpecifics.BlittableStruct) - typeContext.NewType.BaseType = assemblyContext.RewriteTypeRef(typeContext.OriginalType.BaseType!); - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs b/Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs deleted file mode 100644 index 58451af2..00000000 --- a/Il2CppInterop.Generator/Passes/Pass13FillGenericConstraints.cs +++ /dev/null @@ -1,40 +0,0 @@ -using AsmResolver.DotNet; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass13FillGenericConstraints -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - { - foreach (var typeContext in assemblyContext.Types) - { - for (var i = 0; i < typeContext.OriginalType.GenericParameters.Count; i++) - { - var originalParameter = typeContext.OriginalType.GenericParameters[i]; - var newParameter = typeContext.NewType.GenericParameters[i]; - foreach (var originalConstraint in originalParameter.Constraints) - { - if (originalConstraint.IsSystemValueType() || originalConstraint.IsInterface()) - continue; - - if (originalConstraint.IsSystemEnum()) - { - newParameter.Constraints.Add(new GenericParameterConstraint( - typeContext.AssemblyContext.Imports.Module.Enum().ToTypeDefOrRef())); - continue; - } - - newParameter.Constraints.Add( - new GenericParameterConstraint( - assemblyContext.RewriteTypeRef(originalConstraint.Constraint!))); - } - } - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass15GenerateMemberContexts.cs b/Il2CppInterop.Generator/Passes/Pass15GenerateMemberContexts.cs deleted file mode 100644 index 49fcb6ff..00000000 --- a/Il2CppInterop.Generator/Passes/Pass15GenerateMemberContexts.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Il2CppInterop.Generator.Contexts; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass15GenerateMemberContexts -{ - public static bool HasObfuscatedMethods; - - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - typeContext.AddMembers(); - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs b/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs deleted file mode 100644 index f16a3541..00000000 --- a/Il2CppInterop.Generator/Passes/Pass16ScanMethodRefs.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Collections.Concurrent; -using System.IO.MemoryMappedFiles; -using System.Text; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass16ScanMethodRefs -{ - internal static HashSet NonDeadMethods = new(); - internal static IDictionary> MapOfCallers = new Dictionary>(); - - public static void DoPass(RewriteGlobalContext context, GeneratorOptions options) - { - if (string.IsNullOrEmpty(options.GameAssemblyPath)) - { - Pass15GenerateMemberContexts.HasObfuscatedMethods = false; - return; - } - - using var mappedFile = MemoryMappedFile.CreateFromFile(options.GameAssemblyPath, FileMode.Open, null, 0, - MemoryMappedFileAccess.Read); - using var accessor = mappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); - - nint gameAssemblyPtr; - - unsafe - { - byte* fileStartPtr = null; - accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref fileStartPtr); - gameAssemblyPtr = (nint)fileStartPtr; - } - - context.HasGcWbarrierFieldWrite = - FindByteSequence(gameAssemblyPtr, accessor.Capacity, "il2cpp_gc_wbarrier_set_field"); - - if (!Pass15GenerateMemberContexts.HasObfuscatedMethods) return; - - var methodToCallersMap = new ConcurrentDictionary>(); - var methodToCalleesMap = new ConcurrentDictionary>(); - - context.MethodStartAddresses.Sort(); - - // Scan xrefs - context.Assemblies.SelectMany(it => it.Types).SelectMany(it => it.Methods).AsParallel().ForAll( - originalTypeMethod => - { - var address = originalTypeMethod.FileOffset; - if (address == 0) return; - - if (!options.NoXrefCache) - { - var pair = XrefScanMetadataGenerationUtil.FindMetadataInitForMethod(originalTypeMethod, gameAssemblyPtr); - originalTypeMethod.MetadataInitFlagRva = pair.FlagRva; - originalTypeMethod.MetadataInitTokenRva = pair.TokenRva; - } - - var nextMethodStart = context.MethodStartAddresses.BinarySearch(address + 1); - if (nextMethodStart < 0) nextMethodStart = ~nextMethodStart; - var length = nextMethodStart >= context.MethodStartAddresses.Count - ? 1024 * 1024 - : context.MethodStartAddresses[nextMethodStart] - address; - foreach (var callTargetGlobal in XrefScanner.XrefScanImpl( - XrefScanner.DecoderForAddress(IntPtr.Add(gameAssemblyPtr, (int)address), (int)length), - true)) - { - var callTarget = callTargetGlobal.RelativeToBase(gameAssemblyPtr + (nint)originalTypeMethod.FileOffset - (nint)originalTypeMethod.Rva); - if (callTarget.Type == XrefType.Method) - { - var targetRelative = callTarget.Pointer; - methodToCallersMap.GetOrAdd(targetRelative, _ => new List()).AddLocked( - new XrefInstance(XrefType.Method, (nint)originalTypeMethod.Rva, callTarget.FoundAt)); - methodToCalleesMap.GetOrAdd(originalTypeMethod.Rva, _ => new List()) - .AddLocked(targetRelative); - } - - if (!options.NoXrefCache) - originalTypeMethod.XrefScanResults.Add(callTarget); - } - }); - - MapOfCallers = methodToCallersMap; - - void MarkMethodAlive(long address) - { - if (!NonDeadMethods.Add(address)) return; - if (!methodToCalleesMap.TryGetValue(address, out var calleeList)) return; - - foreach (var callee in calleeList) - MarkMethodAlive(callee); - } - - // Now decided which of them are possible dead code - foreach (var assemblyRewriteContext in context.Assemblies) - foreach (var typeRewriteContext in assemblyRewriteContext.Types) - foreach (var methodRewriteContext in typeRewriteContext.Methods) - { - if (methodRewriteContext.FileOffset == 0) continue; - - var originalMethod = methodRewriteContext.OriginalMethod; - if (!originalMethod.Name.IsObfuscated(options) || originalMethod.IsVirtual) - MarkMethodAlive(methodRewriteContext.Rva); - } - } - - private static unsafe bool FindByteSequence(nint basePtr, long length, string str) - { - var bytes = (byte*)basePtr; - var sequence = Encoding.UTF8.GetBytes(str); - for (var i = 0L; i < length; i++) - { - for (var j = 0; j < sequence.Length; j++) - if (bytes[i + j] != sequence[j]) - goto next; - - return true; - - next:; - } - - return false; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass18FinalizeMethodContexts.cs b/Il2CppInterop.Generator/Passes/Pass18FinalizeMethodContexts.cs deleted file mode 100644 index 319ba1d1..00000000 --- a/Il2CppInterop.Generator/Passes/Pass18FinalizeMethodContexts.cs +++ /dev/null @@ -1,67 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Utils; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass18FinalizeMethodContexts -{ - public static int TotalPotentiallyDeadMethods; - - public static void DoPass(RewriteGlobalContext context) - { - var pdmNested0Caller = 0; - var pdmNestedNZCaller = 0; - var pdmTop0Caller = 0; - var pdmTopNZCaller = 0; - - foreach (var assemblyContext in context.Assemblies) - { - foreach (var typeContext in assemblyContext.Types) - { - foreach (var methodContext in typeContext.Methods) - { - methodContext.CtorPhase2(); - - if (Pass15GenerateMemberContexts.HasObfuscatedMethods) - { - var callerCount = 0; - if (Pass16ScanMethodRefs.MapOfCallers.TryGetValue(methodContext.Rva, out var callers)) - callerCount = callers.Count; - - methodContext.NewMethod.CustomAttributes.Add( - new CustomAttribute((ICustomAttributeType)assemblyContext.Imports.CallerCountAttributector.Value, new CustomAttributeSignature(new CustomAttributeArgument(assemblyContext.Imports.Module.Int(), callerCount)))); - - if (!Pass15GenerateMemberContexts.HasObfuscatedMethods) continue; - if (methodContext.UnmangledName?.Contains("_PDM_") is not true) continue; - TotalPotentiallyDeadMethods++; - - var hasZeroCallers = callerCount == 0; - if (methodContext.DeclaringType.OriginalType.IsNested) - { - if (hasZeroCallers) - pdmNested0Caller++; - else - pdmNestedNZCaller++; - } - else - { - if (hasZeroCallers) - pdmTop0Caller++; - else - pdmTopNZCaller++; - } - } - } - } - } - - if (Pass15GenerateMemberContexts.HasObfuscatedMethods) - { - Logger.Instance.LogTrace("Dead method statistics: 0t={Top0Caller} mt={TopNZCaller} 0n={Nested0Caller} mn={NestedNZCaller}", pdmTop0Caller, pdmTopNZCaller, pdmNested0Caller, pdmNestedNZCaller); - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs b/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs deleted file mode 100644 index 709dd202..00000000 --- a/Il2CppInterop.Generator/Passes/Pass19CopyMethodParameters.cs +++ /dev/null @@ -1,49 +0,0 @@ -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass19CopyMethodParameters -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - { - foreach (var typeContext in assemblyContext.Types) - { - foreach (var methodRewriteContext in typeContext.Methods) - { - var originalMethod = methodRewriteContext.OriginalMethod; - var newMethod = methodRewriteContext.NewMethod; - - foreach (var originalMethodParameter in originalMethod.Parameters) - { - var newName = originalMethodParameter.Name.IsObfuscated(context.Options) - ? $"param_{originalMethodParameter.Sequence}" - : originalMethodParameter.Name; - - var newParameter = newMethod.AddParameter( - assemblyContext.RewriteTypeRef(originalMethodParameter.ParameterType), - newName, - originalMethodParameter.GetOrCreateDefinition().Attributes & ~ParameterAttributes.HasFieldMarshal); - - if (originalMethodParameter.IsParamsArray()) - { - newParameter.Definition!.Constant = null; - newParameter.Definition.IsOptional = true; - } - else - { - newParameter.Definition!.Constant = originalMethodParameter.Definition!.Constant; - } - } - - var paramsMethod = context.CreateParamsMethod(originalMethod, newMethod, assemblyContext.Imports, - type => assemblyContext.RewriteTypeRef(type)); - if (paramsMethod != null) typeContext.NewType.Methods.Add(paramsMethod); - } - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs b/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs deleted file mode 100644 index f71c60ae..00000000 --- a/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs +++ /dev/null @@ -1,180 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass20GenerateStaticConstructors -{ - private static int ourTokenlessMethods; - - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - GenerateStaticProxy(assemblyContext, typeContext); - - Logger.Instance.LogTrace("Tokenless method count: {TokenlessMethodCount}", ourTokenlessMethods); - } - - private static void GenerateStaticProxy(AssemblyRewriteContext assemblyContext, TypeRewriteContext typeContext) - { - var oldType = typeContext.OriginalType; - var newType = typeContext.NewType; - if (newType.IsEnum) return; - - var staticCtorMethod = newType.GetOrCreateStaticConstructor(); - - var ctorBuilder = staticCtorMethod.CilMethodBody!.Instructions; - ctorBuilder.Clear(); - - if (newType.IsNested) - { - ctorBuilder.Add(OpCodes.Ldsfld, - assemblyContext.GlobalContext.GetNewTypeForOriginal(oldType.DeclaringType!).ClassPointerFieldRef); - ctorBuilder.Add(OpCodes.Ldstr, oldType.Name ?? ""); - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_GetIl2CppNestedType.Value); - } - else - { - ctorBuilder.Add(OpCodes.Ldstr, oldType.DeclaringModule?.Name ?? ""); - ctorBuilder.Add(OpCodes.Ldstr, oldType.Namespace ?? ""); - ctorBuilder.Add(OpCodes.Ldstr, oldType.Name ?? ""); - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_GetIl2CppClass.Value); - } - - if (oldType.HasGenericParameters()) - { - var il2CppTypeTypeRewriteContext = assemblyContext.GlobalContext.GetAssemblyByName("mscorlib") - .GetTypeByName("System.Type"); - var il2CppSystemTypeRef = newType.DeclaringModule!.DefaultImporter.ImportType(il2CppTypeTypeRewriteContext.NewType); - - var il2CppTypeHandleTypeRewriteContext = assemblyContext.GlobalContext.GetAssemblyByName("mscorlib") - .GetTypeByName("System.RuntimeTypeHandle"); - var il2CppSystemTypeHandleRef = newType.DeclaringModule.DefaultImporter.ImportType(il2CppTypeHandleTypeRewriteContext.NewType); - - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_class_get_type.Value); - ctorBuilder.Add(OpCodes.Call, - new MemberReference(il2CppSystemTypeRef, "internal_from_handle", MethodSignature.CreateStatic(il2CppSystemTypeRef.ToTypeSignature(), assemblyContext.Imports.Module.IntPtr()))); - - ctorBuilder.Add(OpCodes.Ldc_I4, oldType.GenericParameters.Count); - - ctorBuilder.Add(OpCodes.Newarr, il2CppSystemTypeRef); - - for (var i = 0; i < oldType.GenericParameters.Count; i++) - { - ctorBuilder.Add(OpCodes.Dup); - ctorBuilder.Add(OpCodes.Ldc_I4, i); - - var param = oldType.GenericParameters[i]; - var storeRef = assemblyContext.Imports.Il2CppClassPointerStore - .MakeGenericInstanceType(new GenericParameterSignature(GenericParameterType.Type, param.Number)); - var fieldRef = new MemberReference(storeRef.ToTypeDefOrRef(), "NativeClassPtr", new FieldSignature(assemblyContext.Imports.Module.IntPtr())); - ctorBuilder.Add(OpCodes.Ldsfld, fieldRef); - - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_class_get_type.Value); - - ctorBuilder.Add(OpCodes.Call, - new MemberReference(il2CppSystemTypeRef, "internal_from_handle", MethodSignature.CreateStatic(il2CppSystemTypeRef.ToTypeSignature(), assemblyContext.Imports.Module.IntPtr()))); - ctorBuilder.Add(OpCodes.Stelem_Ref); - } - - var il2CppTypeArray = assemblyContext.Imports.Il2CppReferenceArray.MakeGenericInstanceType(il2CppSystemTypeRef.ToTypeSignature()); - ctorBuilder.Add(OpCodes.Newobj, - new MemberReference(il2CppTypeArray.ToTypeDefOrRef(), ".ctor", MethodSignature.CreateInstance(assemblyContext.Imports.Module.Void(), new GenericParameterSignature(GenericParameterType.Type, 0).MakeSzArrayType()))); - ctorBuilder.Add(OpCodes.Call, - ReferenceCreator.CreateInstanceMethodReference(nameof(Type.MakeGenericType), il2CppSystemTypeRef.ToTypeSignature(), il2CppSystemTypeRef, il2CppTypeArray)); - - ctorBuilder.Add(OpCodes.Call, - ReferenceCreator.CreateInstanceMethodReference(typeof(Type).GetProperty(nameof(Type.TypeHandle))!.GetMethod!.Name, - il2CppSystemTypeHandleRef.ToTypeSignature(), il2CppSystemTypeRef)); - ctorBuilder.Add(OpCodes.Ldfld, - ReferenceCreator.CreateFieldReference("value", assemblyContext.Imports.Module.IntPtr(), il2CppSystemTypeHandleRef)); - - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_class_from_type.Value); - } - - ctorBuilder.Add(OpCodes.Stsfld, typeContext.ClassPointerFieldRef); - - if (oldType.IsBeforeFieldInit) - { - ctorBuilder.Add(OpCodes.Ldsfld, typeContext.ClassPointerFieldRef); - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_runtime_class_init.Value); - } - - if (oldType.IsEnum) - { - ctorBuilder.Add(OpCodes.Ret); - return; - } - - foreach (var field in typeContext.Fields) - { - ctorBuilder.Add(OpCodes.Ldsfld, typeContext.ClassPointerFieldRef); - ctorBuilder.Add(OpCodes.Ldstr, field.OriginalField.Name!); - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_GetIl2CppField.Value); - ctorBuilder.Add(OpCodes.Stsfld, field.PointerField); - } - - foreach (var method in typeContext.Methods) - { - ctorBuilder.Add(OpCodes.Ldsfld, typeContext.ClassPointerFieldRef); - - var token = method.OriginalMethod.ExtractToken(); - if (token == 0) - { - ourTokenlessMethods++; - - ctorBuilder.Add( - method.OriginalMethod.GenericParameters.Count > 0 ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - ctorBuilder.Add(OpCodes.Ldstr, method.OriginalMethod.Name!); - ctorBuilder.EmitLoadTypeNameString(assemblyContext.Imports, method.OriginalMethod, - method.OriginalMethod.Signature!.ReturnType, method.NewMethod.Signature!.ReturnType); - ctorBuilder.Add(OpCodes.Ldc_I4, method.OriginalMethod.Parameters.Count); - ctorBuilder.Add(OpCodes.Newarr, assemblyContext.Imports.Module.String().ToTypeDefOrRef()); - - for (var i = 0; i < method.OriginalMethod.Parameters.Count; i++) - { - ctorBuilder.Add(OpCodes.Dup); - ctorBuilder.Add(OpCodes.Ldc_I4, i); - ctorBuilder.EmitLoadTypeNameString(assemblyContext.Imports, method.OriginalMethod, - method.OriginalMethod.Parameters[i].ParameterType, - method.NewMethod.Parameters[i].ParameterType); - ctorBuilder.Add(OpCodes.Stelem_Ref); - } - - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_GetIl2CppMethod.Value); - } - else - { - ctorBuilder.Add(OpCodes.Ldc_I4, (int)token); - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_GetIl2CppMethodByToken.Value); - } - - ctorBuilder.Add(OpCodes.Stsfld, method.NonGenericMethodInfoPointerField); - } - - ctorBuilder.Add(OpCodes.Ret); - } - - private static void EmitLoadTypeNameString(this ILProcessor ctorBuilder, RuntimeAssemblyReferences imports, - MethodDefinition originalMethod, TypeSignature originalTypeReference, TypeSignature newTypeReference) - { - if (originalMethod.HasGenericParameters() || originalTypeReference.FullName == "System.Void") - { - ctorBuilder.Add(OpCodes.Ldstr, originalTypeReference.FullName); - } - else - { - ctorBuilder.Add(newTypeReference is ByReferenceTypeSignature ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - ctorBuilder.Add(OpCodes.Call, - imports.Module.DefaultImporter.ImportMethod( - imports.IL2CPP_RenderTypeName.Value - .MakeGenericInstanceMethod(newTypeReference is ByReferenceTypeSignature ? newTypeReference.GetElementType() : newTypeReference))); - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs b/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs deleted file mode 100644 index c17c9ab9..00000000 --- a/Il2CppInterop.Generator/Passes/Pass21GenerateValueTypeFields.cs +++ /dev/null @@ -1,63 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass21GenerateValueTypeFields -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - { - var il2CppTypeTypeRewriteContext = assemblyContext.GlobalContext.GetAssemblyByName("mscorlib") - .GetTypeByName("System.Object"); - var il2CppSystemTypeRef = - assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(il2CppTypeTypeRewriteContext.NewType).ToTypeSignature(); - - foreach (var typeContext in assemblyContext.Types) - { - if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct || - typeContext.OriginalType.IsEnum) continue; - - try - { - var newType = typeContext.NewType; - newType.Attributes = (newType.Attributes & ~TypeAttributes.LayoutMask) | - TypeAttributes.ExplicitLayout; - - ILGeneratorEx.GenerateBoxMethod(assemblyContext.Imports, newType, typeContext.ClassPointerFieldRef, - il2CppSystemTypeRef); - - foreach (var fieldContext in typeContext.Fields) - { - var field = fieldContext.OriginalField; - if (field.IsStatic) continue; - - var newField = new FieldDefinition(fieldContext.UnmangledName, field.Attributes.ForcePublic(), - !field.Signature!.FieldType.IsValueType() - ? assemblyContext.Imports.Module.IntPtr() - : assemblyContext.RewriteTypeRef(field.Signature.FieldType)); - - newField.FieldOffset = field.ExtractFieldOffset(); - - // Special case: bools in Il2Cpp are bytes - if (newField.Signature!.FieldType.FullName == "System.Boolean") - newField.MarshalDescriptor = new SimpleMarshalDescriptor(NativeType.U1); - - newType.Fields.Add(newField); - } - } - catch (Exception ex) - { - throw new Exception( - $"Failed to generate value type fields for type {typeContext.OriginalType.FullName} in assembly {typeContext.AssemblyContext.OriginalAssembly.Name}", - ex); - } - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs b/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs deleted file mode 100644 index 7d103866..00000000 --- a/Il2CppInterop.Generator/Passes/Pass22GenerateEnums.cs +++ /dev/null @@ -1,59 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass22GenerateEnums -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - { - if (!typeContext.OriginalType.IsEnum) continue; - - var type = typeContext.OriginalType; - var newType = typeContext.NewType; - - if (type.Namespace.NameShouldBePrefixed(context.Options)) - { - newType.CustomAttributes.Add(new CustomAttribute( - (ICustomAttributeType)assemblyContext.Imports.OriginalNameAttributector.Value, - new CustomAttributeSignature( - new CustomAttributeArgument(assemblyContext.Imports.Module.String(), type.DeclaringModule?.Name ?? ""), - new CustomAttributeArgument(assemblyContext.Imports.Module.String(), type.Namespace ?? ""), - new CustomAttributeArgument(assemblyContext.Imports.Module.String(), type.Name ?? "")))); - } - - if (type.CustomAttributes.Any(it => it.Constructor?.DeclaringType?.FullName == "System.FlagsAttribute")) - newType.CustomAttributes.Add(new CustomAttribute(assemblyContext.Imports.Module.FlagsAttributeCtor())); - - foreach (var fieldDefinition in type.Fields) - { - var fieldName = fieldDefinition.Name!; - if (!context.Options.PassthroughNames && fieldName.IsObfuscated(context.Options)) - fieldName = GetUnmangledName(fieldDefinition); - - if (context.Options.RenameMap.TryGetValue( - typeContext.NewType.GetNamespacePrefix() + "." + typeContext.NewType.Name + "::" + fieldName, - out var newName)) - fieldName = newName; - - var newDef = new FieldDefinition(fieldName, fieldDefinition.Attributes | FieldAttributes.HasDefault, - assemblyContext.RewriteTypeRef(fieldDefinition.Signature!.FieldType)); - newType.Fields.Add(newDef); - - newDef.Constant = fieldDefinition.Constant; - } - } - } - - public static string GetUnmangledName(FieldDefinition field) - { - return "EnumValue" + field.Constant; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs b/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs deleted file mode 100644 index a71dba02..00000000 --- a/Il2CppInterop.Generator/Passes/Pass23GeneratePointerConstructors.cs +++ /dev/null @@ -1,39 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass23GeneratePointerConstructors -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - { - if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct || - typeContext.OriginalType.IsEnum) continue; - - var newType = typeContext.NewType; - - var nativeCtor = new MethodDefinition(".ctor", - MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RuntimeSpecialName | - MethodAttributes.HideBySig, MethodSignature.CreateInstance(assemblyContext.Imports.Module.Void())); - - nativeCtor.AddParameter(assemblyContext.Imports.Module.IntPtr(), "pointer"); - - nativeCtor.CilMethodBody = new(); - var ctorBody = nativeCtor.CilMethodBody.Instructions; - newType.Methods.Add(nativeCtor); - - ctorBody.Add(OpCodes.Ldarg_0); - ctorBody.Add(OpCodes.Ldarg_1); - ctorBody.Add(OpCodes.Call, - new MemberReference(newType.BaseType, ".ctor", MethodSignature.CreateInstance(assemblyContext.Imports.Module.Void(), assemblyContext.Imports.Module.IntPtr()))); - ctorBody.Add(OpCodes.Ret); - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass25GenerateNonBlittableValueTypeDefaultCtors.cs b/Il2CppInterop.Generator/Passes/Pass25GenerateNonBlittableValueTypeDefaultCtors.cs deleted file mode 100644 index 1dbbf53e..00000000 --- a/Il2CppInterop.Generator/Passes/Pass25GenerateNonBlittableValueTypeDefaultCtors.cs +++ /dev/null @@ -1,38 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass25GenerateNonBlittableValueTypeDefaultCtors -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - { - if (typeContext.ComputedTypeSpecifics != - TypeRewriteContext.TypeSpecifics.NonBlittableStruct) continue; - - var emptyCtor = new MethodDefinition(".ctor", - MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RuntimeSpecialName | - MethodAttributes.HideBySig, MethodSignature.CreateInstance(assemblyContext.Imports.Module.Void())); - - typeContext.NewType.Methods.Add(emptyCtor); - - emptyCtor.CilMethodBody = new(); - - // NOTE(Kas): This used to stackalloc data of the valuetype's size and box it into an object - // but it seems like it caused issues on certain games. If more issues arise - revert this. - var bodyBuilder = emptyCtor.CilMethodBody.Instructions; - bodyBuilder.Add(OpCodes.Ldarg_0); - bodyBuilder.Add(OpCodes.Ldsfld, typeContext.ClassPointerFieldRef); - bodyBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_object_new.Value); - bodyBuilder.Add(OpCodes.Call, - new MemberReference(typeContext.NewType.BaseType, ".ctor", MethodSignature.CreateInstance(assemblyContext.Imports.Module.Void(), assemblyContext.Imports.Module.IntPtr()))); - bodyBuilder.Add(OpCodes.Ret); - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass30GenerateGenericMethodStoreConstructors.cs b/Il2CppInterop.Generator/Passes/Pass30GenerateGenericMethodStoreConstructors.cs deleted file mode 100644 index 57a589b7..00000000 --- a/Il2CppInterop.Generator/Passes/Pass30GenerateGenericMethodStoreConstructors.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Reflection; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass30GenerateGenericMethodStoreConstructors -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - { - foreach (var typeContext in assemblyContext.Types) - { - foreach (var methodContext in typeContext.Methods) - { - var oldMethod = methodContext.OriginalMethod; - - var storeType = methodContext.GenericInstantiationsStore; - if (storeType != null) - { - var cctor = storeType.GetOrCreateStaticConstructor(); - - var ctorBuilder = cctor.CilMethodBody!.Instructions; - ctorBuilder.Clear(); - - var il2CppTypeTypeRewriteContext = assemblyContext.GlobalContext - .GetAssemblyByName("mscorlib").GetTypeByName("System.Type"); - var il2CppSystemTypeRef = - assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(il2CppTypeTypeRewriteContext.NewType); - - var il2CppMethodInfoTypeRewriteContext = assemblyContext.GlobalContext - .GetAssemblyByName("mscorlib").GetTypeByName("System.Reflection.MethodInfo"); - var il2CppSystemReflectionMethodInfoRef = - assemblyContext.NewAssembly.ManifestModule.DefaultImporter.ImportType(il2CppMethodInfoTypeRewriteContext.NewType); - - ctorBuilder.Add(OpCodes.Ldsfld, methodContext.NonGenericMethodInfoPointerField); - ctorBuilder.Add(OpCodes.Ldsfld, typeContext.ClassPointerFieldRef); - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_method_get_object.Value); - ctorBuilder.Add(OpCodes.Newobj, - new MemberReference(il2CppSystemReflectionMethodInfoRef, ".ctor", - MethodSignature.CreateInstance(assemblyContext.Imports.Module.Void(), assemblyContext.Imports.Module.IntPtr()))); - - ctorBuilder.Add(OpCodes.Ldc_I4, oldMethod.GenericParameters.Count); - - ctorBuilder.Add(OpCodes.Newarr, il2CppSystemTypeRef); - - for (var i = 0; i < oldMethod.GenericParameters.Count; i++) - { - ctorBuilder.Add(OpCodes.Dup); - ctorBuilder.Add(OpCodes.Ldc_I4, i); - - var param = storeType.GenericParameters[i]; - var storeRef = assemblyContext.Imports.Il2CppClassPointerStore.MakeGenericInstanceType(new GenericParameterSignature(GenericParameterType.Type, param.Number)); - var fieldRef = new MemberReference( - storeRef.ToTypeDefOrRef(), - "NativeClassPtr", - new FieldSignature(assemblyContext.Imports.Module.IntPtr())); - ctorBuilder.Add(OpCodes.Ldsfld, fieldRef); - - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_class_get_type.Value); - - ctorBuilder.Add(OpCodes.Call, - new MemberReference(il2CppSystemTypeRef, "internal_from_handle", - MethodSignature.CreateStatic(il2CppSystemTypeRef.ToTypeSignature(), assemblyContext.Imports.Module.IntPtr()))); - ctorBuilder.Add(OpCodes.Stelem_Ref); - } - - var il2CppTypeArray = assemblyContext.Imports.Il2CppReferenceArray.MakeGenericInstanceType(il2CppSystemTypeRef.ToTypeSignature()); - ctorBuilder.Add(OpCodes.Newobj, - ReferenceCreator.CreateInstanceMethodReference(".ctor", assemblyContext.Imports.Module.Void(), il2CppTypeArray.ToTypeDefOrRef(), new GenericParameterSignature(GenericParameterType.Type, 0).MakeSzArrayType())); - ctorBuilder.Add(OpCodes.Call, - ReferenceCreator.CreateInstanceMethodReference(nameof(MethodInfo.MakeGenericMethod), il2CppSystemReflectionMethodInfoRef.ToTypeSignature(), - il2CppSystemReflectionMethodInfoRef, il2CppTypeArray)); - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_Il2CppObjectBaseToPtrNotNull.Value); - - ctorBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_il2cpp_method_get_from_reflection.Value); - ctorBuilder.Add(OpCodes.Stsfld, - ReferenceCreator.CreateFieldReference("Pointer", assemblyContext.Imports.Module.IntPtr(), - methodContext.GenericInstantiationsStoreSelfSubstRef)); - - ctorBuilder.Add(OpCodes.Ret); - } - } - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs b/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs deleted file mode 100644 index b100ebc0..00000000 --- a/Il2CppInterop.Generator/Passes/Pass40GenerateFieldAccessors.cs +++ /dev/null @@ -1,38 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass40GenerateFieldAccessors -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - { - foreach (var typeContext in assemblyContext.Types) - { - foreach (var fieldContext in typeContext.Fields) - { - if (typeContext.ComputedTypeSpecifics == TypeRewriteContext.TypeSpecifics.BlittableStruct && - !fieldContext.OriginalField.IsStatic) continue; - - var field = fieldContext.OriginalField; - var unmangleFieldName = fieldContext.UnmangledName; - - var propertyType = assemblyContext.RewriteTypeRef(fieldContext.OriginalField.Signature!.FieldType); - var signature = field.IsStatic - ? PropertySignature.CreateStatic(propertyType) - : PropertySignature.CreateInstance(propertyType); - var property = new PropertyDefinition(unmangleFieldName, PropertyAttributes.None, signature); - typeContext.NewType.Properties.Add(property); - - FieldAccessorGenerator.MakeGetter(field, fieldContext, property, assemblyContext.Imports); - FieldAccessorGenerator.MakeSetter(field, fieldContext, property, assemblyContext.Imports); - } - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs b/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs deleted file mode 100644 index eff085a7..00000000 --- a/Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs +++ /dev/null @@ -1,259 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Cil; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass50GenerateMethods -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - { - foreach (var typeContext in assemblyContext.Types) - { - foreach (var methodRewriteContext in typeContext.Methods) - { - var originalMethod = methodRewriteContext.OriginalMethod; - var newMethod = methodRewriteContext.NewMethod; - var imports = assemblyContext.Imports; - - var bodyBuilder = newMethod.CilMethodBody!.Instructions; - var exceptionLocal = new CilLocalVariable(imports.Module.IntPtr()); - var argArray = new CilLocalVariable(imports.Module.IntPtr().MakePointerType()); - var resultVar = new CilLocalVariable(imports.Module.IntPtr()); - var valueTypeLocal = new CilLocalVariable(newMethod.Signature!.ReturnType); - newMethod.CilMethodBody.LocalVariables.Add(exceptionLocal); - newMethod.CilMethodBody.LocalVariables.Add(argArray); - newMethod.CilMethodBody.LocalVariables.Add(resultVar); - - if (valueTypeLocal.VariableType.FullName != "System.Void") - newMethod.CilMethodBody.LocalVariables.Add(valueTypeLocal); - - // Pre-initialize any present params - // TODO: This doesn't account for params T[] (i.e. generic element type) yet; may emit incorrect IL - // TODO: Do we really need a loop here? C# allows only one params array. - // On the other hand, CreateParamsMethod accommodates multiple ParamArrayAttribute as well - CilInstructionLabel? nextInstruction = null; - for (var paramIndex = 0; paramIndex < originalMethod.Parameters.Count; paramIndex++) - { - var newParameter = newMethod.Parameters[paramIndex]; - var originalParameter = originalMethod.Parameters[paramIndex]; - if (!originalParameter.IsParamsArray()) - continue; - - var originalElementType = ((ArrayBaseTypeSignature)originalParameter.ParameterType).BaseType; - - if (nextInstruction != null) - nextInstruction.Instruction = bodyBuilder.Add(OpCodes.Nop); - nextInstruction = new(); - - bodyBuilder.Add(OpCodes.Ldarg, newParameter); - bodyBuilder.Add(OpCodes.Brtrue, nextInstruction); - - bodyBuilder.Add(OpCodes.Ldc_I4_0); - bodyBuilder.Add(OpCodes.Conv_I8); - bodyBuilder.Add(OpCodes.Newobj, imports.Module.DefaultImporter.ImportMethod(originalElementType.FullName switch - { - "System.String" => imports.Il2CppStringArrayctor_size.Value, - _ when originalElementType.IsValueType() => imports.Il2CppStructArrayctor_size.Get(((GenericInstanceTypeSignature)newParameter.ParameterType).TypeArguments[0]), - _ => imports.Il2CppRefrenceArrayctor_size.Get(((GenericInstanceTypeSignature)newParameter.ParameterType).TypeArguments[0]) - })); - bodyBuilder.Add(OpCodes.Starg, newParameter); - } - - if (nextInstruction != null) - nextInstruction.Instruction = bodyBuilder.Add(OpCodes.Nop); - - if (typeContext.ComputedTypeSpecifics != TypeRewriteContext.TypeSpecifics.BlittableStruct) - { - if (originalMethod.IsConstructor) - { - bodyBuilder.Add(OpCodes.Ldarg_0); - bodyBuilder.Add(OpCodes.Ldsfld, typeContext.ClassPointerFieldRef); - bodyBuilder.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_new.Value); - bodyBuilder.Add(OpCodes.Call, - ReferenceCreator.CreateInstanceMethodReference(".ctor", imports.Module.Void(), typeContext.SelfSubstitutedRef, imports.Module.IntPtr())); - } - else if (!originalMethod.IsStatic) - { - bodyBuilder.Add(OpCodes.Ldarg_0); - bodyBuilder.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtrNotNull.Value); - bodyBuilder.Add(OpCodes.Pop); - } - } - - if (originalMethod.Parameters.Count == 0) - { - bodyBuilder.Add(OpCodes.Ldc_I4_0); - bodyBuilder.Add(OpCodes.Conv_U); - } - else - { - bodyBuilder.Add(OpCodes.Ldc_I4, originalMethod.Parameters.Count); - bodyBuilder.Add(OpCodes.Conv_U); - bodyBuilder.Add(OpCodes.Sizeof, imports.Module.IntPtr().ToTypeDefOrRef()); - bodyBuilder.Add(OpCodes.Mul_Ovf_Un); - bodyBuilder.Add(OpCodes.Localloc); - } - - bodyBuilder.Add(OpCodes.Stloc, argArray); - - var argOffset = originalMethod.IsStatic ? 0 : 1; - - var byRefParams = new List<(int, CilLocalVariable)>(); - - for (var i = 0; i < newMethod.Parameters.Count; i++) - { - bodyBuilder.Add(OpCodes.Ldloc, argArray); - if (i > 0) - { - bodyBuilder.Add(OpCodes.Ldc_I4, i); - bodyBuilder.Add(OpCodes.Conv_U); - bodyBuilder.Add(OpCodes.Sizeof, imports.Module.IntPtr().ToTypeDefOrRef()); - bodyBuilder.Add(OpCodes.Mul_Ovf_Un); - bodyBuilder.Add(OpCodes.Add); - } - - var newParam = newMethod.Parameters[i]; - // NOTE(Kas): out parameters of value type are passed directly as a pointer to the il2cpp method - // since we don't need to perform any additional copies - if (newParam.Definition!.IsOut && !newParam.ParameterType.GetElementType().IsValueType()) - { - var elementType = newParam.ParameterType.GetElementType(); - - // Storage for the output Il2CppObjectBase pointer, it's - // unused if there's a generic value type parameter - var outVar = new CilLocalVariable(imports.Module.IntPtr()); - bodyBuilder.Owner.LocalVariables.Add(outVar); - - if (elementType is GenericParameterSignature) - { - bodyBuilder.Add(OpCodes.Ldtoken, elementType.ToTypeDefOrRef()); - bodyBuilder.Add(OpCodes.Call, imports.Module.TypeGetTypeFromHandle()); - bodyBuilder.Add(OpCodes.Callvirt, imports.Module.TypeGetIsValueType()); - - var valueTypeBlock = new CilInstructionLabel(); - var continueBlock = new CilInstructionLabel(); - - bodyBuilder.Add(OpCodes.Brtrue, valueTypeBlock); - - // The generic parameter is an Il2CppObjectBase => set the output storage to a nullptr - bodyBuilder.Add(OpCodes.Ldc_I4, 0); - bodyBuilder.Add(OpCodes.Stloc, outVar); - bodyBuilder.Add(OpCodes.Ldloca, outVar); - bodyBuilder.Add(OpCodes.Conv_I); - - bodyBuilder.Add(OpCodes.Br_S, continueBlock); - - // Instruction block that handles generic value types, we only need to return a reference - // to the output argument since it is already allocated for us - valueTypeBlock.Instruction = bodyBuilder.Add(OpCodes.Nop); - bodyBuilder.AddLoadArgument(argOffset + i); - - continueBlock.Instruction = bodyBuilder.Add(OpCodes.Nop); - } - else - { - bodyBuilder.Add(OpCodes.Ldc_I4, 0); - bodyBuilder.Add(OpCodes.Stloc, outVar); - bodyBuilder.Add(OpCodes.Ldloca, outVar); - bodyBuilder.Add(OpCodes.Conv_I); - } - byRefParams.Add((i, outVar)); - } - else - { - bodyBuilder.EmitObjectToPointer(originalMethod.Parameters[i].ParameterType, newParam.ParameterType, - methodRewriteContext.DeclaringType, argOffset + i, false, true, true, false, out var refVar); - if (refVar != null) - byRefParams.Add((i, refVar)); - } - bodyBuilder.Add(OpCodes.Stind_I); - - } - - if (!originalMethod.DeclaringType!.IsSealed && !originalMethod.IsFinal && - ((originalMethod.IsVirtual && !originalMethod.DeclaringType.IsValueType()) || originalMethod.IsAbstract)) - { - bodyBuilder.Add(OpCodes.Ldarg_0); - bodyBuilder.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtr.Value); - if (methodRewriteContext.GenericInstantiationsStoreSelfSubstRef != null) - bodyBuilder.Add(OpCodes.Ldsfld, - ReferenceCreator.CreateFieldReference("Pointer", imports.Module.IntPtr(), - methodRewriteContext.GenericInstantiationsStoreSelfSubstMethodRef)); - else - bodyBuilder.Add(OpCodes.Ldsfld, methodRewriteContext.NonGenericMethodInfoPointerField); - bodyBuilder.Add(OpCodes.Call, imports.IL2CPP_il2cpp_object_get_virtual_method.Value); - } - else if (methodRewriteContext.GenericInstantiationsStoreSelfSubstRef != null) - { - bodyBuilder.Add(OpCodes.Ldsfld, - ReferenceCreator.CreateFieldReference("Pointer", imports.Module.IntPtr(), - methodRewriteContext.GenericInstantiationsStoreSelfSubstMethodRef)); - } - else - { - bodyBuilder.Add(OpCodes.Ldsfld, methodRewriteContext.NonGenericMethodInfoPointerField); - } - - if (originalMethod.IsStatic) - bodyBuilder.Add(OpCodes.Ldc_I4_0); - else - bodyBuilder.EmitObjectToPointer(originalMethod.DeclaringType.ToTypeSignature(), newMethod.DeclaringType!.ToTypeSignature(), typeContext, 0, - true, false, true, true, out _); - - bodyBuilder.Add(OpCodes.Ldloc, argArray); - bodyBuilder.Add(OpCodes.Ldloca, exceptionLocal); - bodyBuilder.Add(OpCodes.Call, imports.IL2CPP_il2cpp_runtime_invoke.Value); - bodyBuilder.Add(OpCodes.Stloc, resultVar); - - bodyBuilder.Add(OpCodes.Ldloc, exceptionLocal); - bodyBuilder.Add(OpCodes.Call, imports.Il2CppException_RaiseExceptionIfNecessary.Value); - - foreach (var byRefParam in byRefParams) - { - var paramIndex = byRefParam.Item1; - var paramVariable = byRefParam.Item2; - var methodParam = newMethod.Parameters[paramIndex]; - - if (methodParam.Definition!.IsOut && methodParam.ParameterType.GetElementType() is GenericParameterSignature) - { - bodyBuilder.Add(OpCodes.Ldtoken, methodParam.ParameterType.GetElementType().ToTypeDefOrRef()); - bodyBuilder.Add(OpCodes.Call, imports.Module.TypeGetTypeFromHandle()); - bodyBuilder.Add(OpCodes.Callvirt, imports.Module.TypeGetIsValueType()); - - var continueBlock = new CilInstructionLabel(); - - bodyBuilder.Add(OpCodes.Brtrue, continueBlock); - - // The generic parameter is an Il2CppObjectBase => update the reference appropriately - bodyBuilder.EmitUpdateRef(newMethod.Parameters[paramIndex], paramIndex + argOffset, paramVariable, - imports); - - bodyBuilder.Add(OpCodes.Br_S, continueBlock); - - // There is no need to handle generic value types, they are already passed by reference - - continueBlock.Instruction = bodyBuilder.Add(OpCodes.Nop); - } - else - { - bodyBuilder.EmitUpdateRef(newMethod.Parameters[paramIndex], paramIndex + argOffset, paramVariable, - imports); - } - } - - bodyBuilder.EmitPointerToObject(originalMethod.Signature!.ReturnType, newMethod.Signature.ReturnType, typeContext, - resultVar, false, true); - - bodyBuilder.Add(OpCodes.Ret); - } - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass60AddImplicitConversions.cs b/Il2CppInterop.Generator/Passes/Pass60AddImplicitConversions.cs deleted file mode 100644 index 500fbc21..00000000 --- a/Il2CppInterop.Generator/Passes/Pass60AddImplicitConversions.cs +++ /dev/null @@ -1,215 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass60AddImplicitConversions -{ - private const MethodAttributes OperatorAttributes = MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; - - public static void DoPass(RewriteGlobalContext context) - { - var assemblyContext = context.GetAssemblyByName("mscorlib"); - var typeContext = assemblyContext.GetTypeByName("System.String"); - var objectTypeContext = assemblyContext.GetTypeByName("System.Object"); - - var methodFromMonoString = new MethodDefinition("op_Implicit", OperatorAttributes, - MethodSignature.CreateStatic(typeContext.NewType.ToTypeSignature(), assemblyContext.Imports.Module.String())); - typeContext.NewType.Methods.Add(methodFromMonoString); - methodFromMonoString.CilMethodBody = new CilMethodBody(); - var fromBuilder = methodFromMonoString.CilMethodBody.Instructions; - - var createIl2CppStringNop = new CilInstructionLabel(); - - fromBuilder.Add(OpCodes.Ldarg_0); - fromBuilder.Add(OpCodes.Dup); - fromBuilder.Add(OpCodes.Brtrue_S, createIl2CppStringNop); - fromBuilder.Add(OpCodes.Ret); - - createIl2CppStringNop.Instruction = fromBuilder.Add(OpCodes.Nop); - - fromBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_ManagedStringToIl2Cpp.Value); - fromBuilder.Add(OpCodes.Newobj, - ReferenceCreator.CreateInstanceMethodReference(".ctor", assemblyContext.Imports.Module.Void(), typeContext.NewType, assemblyContext.Imports.Module.IntPtr())); - fromBuilder.Add(OpCodes.Ret); - - var methodToObject = new MethodDefinition("op_Implicit", OperatorAttributes, MethodSignature.CreateStatic(objectTypeContext.NewType.ToTypeSignature())); - methodToObject.AddParameter(assemblyContext.Imports.Module.String()); - objectTypeContext.NewType.Methods.Add(methodToObject); - methodToObject.CilMethodBody = new CilMethodBody(); - var toObjectBuilder = methodToObject.CilMethodBody.Instructions; - toObjectBuilder.Add(OpCodes.Ldarg_0); - toObjectBuilder.Add(OpCodes.Call, methodFromMonoString); - toObjectBuilder.Add(OpCodes.Ret); - - var methodToMonoString = new MethodDefinition("op_Implicit", OperatorAttributes, MethodSignature.CreateStatic(assemblyContext.Imports.Module.String())); - methodToMonoString.AddParameter(typeContext.NewType.ToTypeSignature()); - typeContext.NewType.Methods.Add(methodToMonoString); - methodToMonoString.CilMethodBody = new CilMethodBody(); - var toBuilder = methodToMonoString.CilMethodBody.Instructions; - - var createStringNop = new CilInstructionLabel(); - - toBuilder.Add(OpCodes.Ldarg_0); - toBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_Il2CppObjectBaseToPtr.Value); - toBuilder.Add(OpCodes.Dup); - toBuilder.Add(OpCodes.Brtrue_S, createStringNop); - toBuilder.Add(OpCodes.Pop); - toBuilder.Add(OpCodes.Ldnull); - toBuilder.Add(OpCodes.Ret); - - createStringNop.Instruction = toBuilder.Add(OpCodes.Nop); - toBuilder.Add(OpCodes.Call, assemblyContext.Imports.IL2CPP_Il2CppStringToManaged.Value); - toBuilder.Add(OpCodes.Ret); - - AddDelegateConversions(context); - - TypeSignature[] primitiveTypes = - [ - assemblyContext.Imports.Module.SByte(), - assemblyContext.Imports.Module.Byte(), - - assemblyContext.Imports.Module.Short(), - assemblyContext.Imports.Module.UShort(), - - assemblyContext.Imports.Module.Int(), - assemblyContext.Imports.Module.UInt(), - - assemblyContext.Imports.Module.Long(), - assemblyContext.Imports.Module.ULong(), - - assemblyContext.Imports.Module.Float(), - assemblyContext.Imports.Module.Double(), - - assemblyContext.Imports.Module.Char(), - assemblyContext.Imports.Module.Bool(), - ]; - - foreach (var systemType in primitiveTypes) - { - var il2CppSystemType = assemblyContext.GetTypeByName(systemType.FullName).NewType; - - var method = new MethodDefinition("op_Implicit", OperatorAttributes, MethodSignature.CreateStatic(objectTypeContext.NewType.ToTypeSignature())); - method.AddParameter(systemType, "value"); - - method.CilMethodBody = new CilMethodBody(); - var il = method.CilMethodBody.Instructions; - - var structLocal = new CilLocalVariable(il2CppSystemType.ToTypeSignature()); - method.CilMethodBody.LocalVariables.Add(structLocal); - - il.Add(OpCodes.Ldloca, structLocal); - il.Add(OpCodes.Initobj, il2CppSystemType); - - il.Add(OpCodes.Ldloca, structLocal); - il.Add(OpCodes.Ldarg_0); - il.Add(OpCodes.Stfld, il2CppSystemType.Fields.Single(f => f.Name == "m_value")); - - il.Add(OpCodes.Ldloca_S, structLocal); - il.Add(OpCodes.Call, il2CppSystemType.Methods.Single(m => m.Name == "BoxIl2CppObject")); - il.Add(OpCodes.Ret); - - objectTypeContext.NewType.Methods.Add(method); - } - } - - private static void AddDelegateConversions(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - { - foreach (var typeContext in assemblyContext.Types) - { - if (typeContext.OriginalType.BaseType?.FullName != "System.MulticastDelegate") - continue; - - var invokeMethod = typeContext.NewType.Methods.Single(it => it.Name == "Invoke"); - if (invokeMethod.Parameters.Count > 8) - continue; // mscorlib only contains delegates of up to 8 parameters - - // Don't generate implicit conversions for pointers and byrefs, as they can't be specified in generics - if (invokeMethod.Parameters.Any(it => it.ParameterType is PointerTypeSignature or ByReferenceTypeSignature)) - continue; - - var implicitMethod = new MethodDefinition("op_Implicit", OperatorAttributes, MethodSignature.CreateStatic(typeContext.SelfSubstitutedRef.ToTypeSignature())); - typeContext.NewType.Methods.Add(implicitMethod); - implicitMethod.CilMethodBody = new CilMethodBody(); - - var hasReturn = invokeMethod.Signature!.ReturnType.FullName != "System.Void"; - var hasParameters = invokeMethod.Parameters.Count > 0; - - TypeSignature monoDelegateType; - if (!hasReturn && !hasParameters) - monoDelegateType = typeContext.NewType.DeclaringModule!.Action(); - else if (!hasReturn) - monoDelegateType = typeContext.NewType.DeclaringModule!.Action(invokeMethod.Parameters.Count); - else - monoDelegateType = typeContext.NewType.DeclaringModule!.Func(invokeMethod.Parameters.Count); - - GenericInstanceTypeSignature? genericInstanceType = null; - if (hasParameters) - { - genericInstanceType = new GenericInstanceTypeSignature(monoDelegateType.ToTypeDefOrRef(), false); - foreach (var t in invokeMethod.Parameters) - genericInstanceType.TypeArguments.Add(t.ParameterType); - } - - if (hasReturn) - { - genericInstanceType ??= new GenericInstanceTypeSignature(monoDelegateType.ToTypeDefOrRef(), false); - genericInstanceType.TypeArguments.Add(invokeMethod.Signature.ReturnType); - } - - implicitMethod.AddParameter(genericInstanceType != null - ? typeContext.NewType.DeclaringModule!.DefaultImporter.ImportTypeSignature(genericInstanceType) - : monoDelegateType); - - var bodyBuilder = implicitMethod.CilMethodBody.Instructions; - - bodyBuilder.Add(OpCodes.Ldarg_0); - var delegateSupportTypeRef = typeContext.AssemblyContext.Imports.DelegateSupport; - var genericConvertSignature = MethodSignature.CreateStatic(new GenericParameterSignature(GenericParameterType.Method, 0), 1, assemblyContext.Imports.Module.Delegate()); - var genericConvertRef = new MemberReference(delegateSupportTypeRef.ToTypeDefOrRef(), "ConvertDelegate", genericConvertSignature); - var convertMethodRef = genericConvertRef.MakeGenericInstanceMethod(typeContext.SelfSubstitutedRef.ToTypeSignature()); - bodyBuilder.Add(OpCodes.Call, typeContext.NewType.DeclaringModule!.DefaultImporter.ImportMethod(convertMethodRef)); - bodyBuilder.Add(OpCodes.Ret); - - // public static T operator+(T lhs, T rhs) => Il2CppSystem.Delegate.Combine(lhs, rhs).Cast(); - var addMethod = new MethodDefinition("op_Addition", OperatorAttributes, MethodSignature.CreateStatic(typeContext.SelfSubstitutedRef.ToTypeSignature())); - typeContext.NewType.Methods.Add(addMethod); - addMethod.AddParameter(typeContext.SelfSubstitutedRef.ToTypeSignature()); - addMethod.AddParameter(typeContext.SelfSubstitutedRef.ToTypeSignature()); - addMethod.CilMethodBody = new CilMethodBody(); - var addBody = addMethod.CilMethodBody.Instructions; - addBody.Add(OpCodes.Ldarg_0); - addBody.Add(OpCodes.Ldarg_1); - addBody.Add(OpCodes.Call, assemblyContext.Imports.Il2CppSystemDelegateCombine.Value); - addBody.Add(OpCodes.Call, - assemblyContext.Imports.Module.DefaultImporter.ImportMethod(assemblyContext.Imports.Il2CppObjectBase_Cast.Value.MakeGenericInstanceMethod(typeContext.SelfSubstitutedRef.ToTypeSignature()))); - addBody.Add(OpCodes.Ret); - - // public static T operator-(T lhs, T rhs) => Il2CppSystem.Delegate.Remove(lhs, rhs)?.Cast(); - var subtractMethod = new MethodDefinition("op_Subtraction", OperatorAttributes, MethodSignature.CreateStatic(typeContext.SelfSubstitutedRef.ToTypeSignature())); - typeContext.NewType.Methods.Add(subtractMethod); - subtractMethod.AddParameter(typeContext.SelfSubstitutedRef.ToTypeSignature()); - subtractMethod.AddParameter(typeContext.SelfSubstitutedRef.ToTypeSignature()); - subtractMethod.CilMethodBody = new CilMethodBody(); - var subtractBody = subtractMethod.CilMethodBody.Instructions; - subtractBody.Add(OpCodes.Ldarg_0); - subtractBody.Add(OpCodes.Ldarg_1); - subtractBody.Add(OpCodes.Call, assemblyContext.Imports.Il2CppSystemDelegateRemove.Value); - subtractBody.Add(OpCodes.Dup); - var ret = new CilInstructionLabel(); - subtractBody.Add(OpCodes.Brfalse_S, ret); - subtractBody.Add(OpCodes.Call, - assemblyContext.Imports.Module.DefaultImporter.ImportMethod(assemblyContext.Imports.Il2CppObjectBase_Cast.Value.MakeGenericInstanceMethod(typeContext.SelfSubstitutedRef.ToTypeSignature()))); - ret.Instruction = subtractBody.Add(OpCodes.Ret); - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs b/Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs deleted file mode 100644 index 9ed14fa0..00000000 --- a/Il2CppInterop.Generator/Passes/Pass61ImplementAwaiters.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Runtime.CompilerServices; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Cloning; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass61ImplementAwaiters -{ - public static void DoPass(RewriteGlobalContext context) - { - var corlib = context.CorLib; - - var actionUntyped = corlib.GetTypeByName("System.Action"); - - var actionConversion = actionUntyped.NewType.Methods.Single(m => m.Name == "op_Implicit"); - - foreach (var assemblyContext in context.Assemblies) - { - // Use Lazy as a lazy way to not actually import the references until they're needed - - Lazy actionUntypedRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(actionConversion.Parameters[0].ParameterType.ToTypeDefOrRef())!); - Lazy actionConversionRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(actionConversion)); - Lazy notifyCompletionRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(typeof(INotifyCompletion))); - var voidRef = assemblyContext.NewAssembly.ManifestModule!.CorLibTypeFactory.Void; - - foreach (var typeContext in assemblyContext.Types) - { - // Odds are a majority of types won't implement any interfaces. Skip them to save time. - if (typeContext.OriginalType.IsInterface || typeContext.OriginalType.Interfaces.Count == 0) - continue; - - var iNotifyCompletion = typeof(INotifyCompletion); - var interfaceImplementation = typeContext.OriginalType.Interfaces.SingleOrDefault(interfaceImpl => interfaceImpl.Interface?.Namespace == iNotifyCompletion.Namespace && interfaceImpl.Interface?.Name == iNotifyCompletion.Name); - if (interfaceImplementation is null) - continue; - - var allOnCompleted = typeContext.Methods.Where(m => m.OriginalMethod.Name == nameof(INotifyCompletion.OnCompleted)).Select(mc => mc.NewMethod).ToArray(); - - // Conversion spits out an Il2CppSystem.Action, so look for methods that take that (and only that) in & return void, so the stack is balanced - // And use SignatureComparer because otherwise equality checks would fail due to the TypeSignatures being different references - var interopOnCompleted = allOnCompleted.FirstOrDefault(m => !m.IsStatic && m.Parameters.Count == 1 && m.Signature is not null && SignatureComparer.Default.Equals(m.Signature.ReturnType, voidRef) && SignatureComparer.Default.Equals(m.Signature.ParameterTypes[0], actionConversion.Signature?.ReturnType)); - - if (interopOnCompleted is null) - { - var typeName = typeContext.OriginalType.FullName; - var foundMethodCount = allOnCompleted.Length; - Logger.Instance.LogInformation("Type {typeName} was found to implement INotifyCompletion, but no suitable method was found. {foundMethodCount} method(s) were found with the required name.", typeName, foundMethodCount); - continue; - } - - var onCompletedAttr = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual; - var sig = MethodSignature.CreateInstance(voidRef, [actionUntypedRef.Value.ToTypeSignature()]); - - var proxyOnCompleted = new MethodDefinition(nameof(INotifyCompletion.OnCompleted), onCompletedAttr, sig); - var parameter = proxyOnCompleted.Parameters[0].GetOrCreateDefinition(); - parameter.Name = "continuation"; - - var body = proxyOnCompleted.CilMethodBody ??= new(); - - typeContext.NewType.Interfaces.Add(new(notifyCompletionRef.Value)); - typeContext.NewType.Methods.Add(proxyOnCompleted); - - var instructions = body.Instructions; - instructions.Add(CilOpCodes.Ldarg_0); // load "this" - instructions.Add(CilOpCodes.Ldarg_1); // not static, so ldarg1 loads "continuation" - instructions.Add(CilOpCodes.Call, actionConversionRef.Value); - - // The titular jump to the interop method -- it's gotta reference the method on the right type, so we need to handle generic parameters - // Without this, awaiters declared in generic types like UniTask.Awaiter would effectively try to cast themselves to their untyped versions (UniTask<>.Awaiter in this case, which isn't a thing) - var genericParameterCount = typeContext.NewType.GenericParameters.Count; - if (genericParameterCount > 0) - { - var typeArguments = Enumerable.Range(0, genericParameterCount).Select(i => new GenericParameterSignature(GenericParameterType.Type, i)).ToArray(); - var interopOnCompleteGeneric = typeContext.NewType.MakeGenericInstanceType(typeArguments) - .ToTypeDefOrRef() - .CreateMemberReference(interopOnCompleted.Name, interopOnCompleted.Signature); - instructions.Add(CilOpCodes.Call, interopOnCompleteGeneric); - } - else - { - instructions.Add(CilOpCodes.Call, interopOnCompleted); - } - - instructions.Add(CilOpCodes.Ret); - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs b/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs deleted file mode 100644 index e80e7911..00000000 --- a/Il2CppInterop.Generator/Passes/Pass70GenerateProperties.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System.Reflection; -using AsmResolver; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass70GenerateProperties -{ - public static void DoPass(RewriteGlobalContext context) - { - foreach (var assemblyContext in context.Assemblies) - foreach (var typeContext in assemblyContext.Types) - { - var type = typeContext.OriginalType; - var propertyCountsByName = new Dictionary(); - - foreach (var oldProperty in type.Properties) - { - FixPropertyDefinitionParameters(oldProperty); - - var unmangledPropertyName = UnmanglePropertyName(assemblyContext, oldProperty, typeContext.NewType, - propertyCountsByName); - - var propertyType = assemblyContext.RewriteTypeRef(oldProperty.Signature!.ReturnType); - var signature = oldProperty.Signature.HasThis - ? PropertySignature.CreateInstance(propertyType) - : PropertySignature.CreateStatic(propertyType); - foreach (var oldParameter in oldProperty.Signature.ParameterTypes) - signature.ParameterTypes.Add(assemblyContext.RewriteTypeRef(oldParameter)); - - var property = new PropertyDefinition(unmangledPropertyName, oldProperty.Attributes, signature); - - typeContext.NewType.Properties.Add(property); - - property.SetSemanticMethods( - oldProperty.GetMethod is null ? null : typeContext.GetMethodByOldMethod(oldProperty.GetMethod).NewMethod, - oldProperty.SetMethod is null ? null : typeContext.GetMethodByOldMethod(oldProperty.SetMethod).NewMethod); - } - - string? defaultMemberName = null; - if (type.CustomAttributes.FirstOrDefault(IsDefaultMemberAttributeFake) != null) - { - defaultMemberName = "Item"; - } - else - { - var realDefaultMemberAttribute = type.CustomAttributes.FirstOrDefault(IsDefaultMemberAttributeReal); - if (realDefaultMemberAttribute != null) - defaultMemberName = realDefaultMemberAttribute.Signature?.FixedArguments[0].Element?.ToString() ?? "Item"; - } - - if (defaultMemberName != null) - typeContext.NewType.CustomAttributes.Add(new CustomAttribute( - ReferenceCreator.CreateInstanceMethodReference(".ctor", assemblyContext.Imports.Module.Void(), - assemblyContext.Imports.Module.DefaultMemberAttribute().ToTypeDefOrRef(), assemblyContext.Imports.Module.String()), - new CustomAttributeSignature(new CustomAttributeArgument(assemblyContext.Imports.Module.String(), defaultMemberName)))); - } - - static bool IsDefaultMemberAttributeFake(CustomAttribute attribute) - { - return attribute.AttributeType()?.Name == "AttributeAttribute" && attribute.Signature!.NamedArguments.Any(it => - { - // Name support is for backwards compatibility. - return (it.MemberName == "Type" && it.Argument.Element is ITypeDescriptor { Namespace: "System.Reflection", Name: nameof(DefaultMemberAttribute) }) - || (it.MemberName == "Name" && it.Argument.GetElementAsString() == nameof(DefaultMemberAttribute)); - }); - } - - static bool IsDefaultMemberAttributeReal(CustomAttribute attribute) - { - return attribute.AttributeType() is { Namespace.Value: "System.Reflection", Name.Value: nameof(DefaultMemberAttribute) }; - } - } - - private static string UnmanglePropertyName(AssemblyRewriteContext assemblyContext, PropertyDefinition prop, - ITypeDefOrRef declaringType, Dictionary countsByBaseName) - { - if (assemblyContext.GlobalContext.Options.PassthroughNames || - !prop.Name.IsObfuscated(assemblyContext.GlobalContext.Options)) return prop.Name!; - - var baseName = "prop_" + assemblyContext.RewriteTypeRef(prop.Signature!.ReturnType).GetUnmangledName(prop.DeclaringType); - - countsByBaseName.TryGetValue(baseName, out var index); - countsByBaseName[baseName] = index + 1; - - var unmanglePropertyName = baseName + "_" + index; - - if (assemblyContext.GlobalContext.Options.RenameMap.TryGetValue( - declaringType.GetNamespacePrefix() + "." + declaringType.Name + "::" + unmanglePropertyName, out var newNameByType)) - { - unmanglePropertyName = newNameByType; - } - else if (assemblyContext.GlobalContext.Options.RenameMap.TryGetValue( - declaringType.GetNamespacePrefix() + "::" + unmanglePropertyName, out var newName)) - { - unmanglePropertyName = newName; - } - - - return unmanglePropertyName; - } - - private static void FixPropertyDefinitionParameters(PropertyDefinition property) - { - // See: https://github.com/SamboyCoding/Cpp2IL/issues/249 - - if (property.Signature is null or { ParameterTypes.Count: > 0 }) - return; - - var getMethod = property.GetMethod; - if (getMethod?.Signature is not null) - { - if (getMethod.Signature.ParameterTypes.Count > 0) - { - foreach (var parameter in getMethod.Signature.ParameterTypes) - { - property.Signature.ParameterTypes.Add(parameter); - } - } - - return; - } - - var setMethod = property.SetMethod; - if (setMethod?.Signature is not null) - { - if (setMethod.Signature.ParameterTypes.Count > 1) - { - foreach (var parameter in setMethod.Signature.ParameterTypes.Take(setMethod.Signature.ParameterTypes.Count - 1)) - { - property.Signature.ParameterTypes.Add(parameter); - } - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs b/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs deleted file mode 100644 index 739d58a9..00000000 --- a/Il2CppInterop.Generator/Passes/Pass79UnstripTypes.cs +++ /dev/null @@ -1,149 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass79UnstripTypes -{ - public static void DoPass(RewriteGlobalContext context) - { - var typesUnstripped = 0; - - foreach (var unityAssembly in context.UnityAssemblies.Assemblies) - { - var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name); - if (processedAssembly == null) - { - var newAssembly = new AssemblyDefinition(unityAssembly.Name, unityAssembly.Version); - newAssembly.Modules.Add(new ModuleDefinition(unityAssembly.ManifestModule!.Name)); - var newContext = new AssemblyRewriteContext(context, unityAssembly, - newAssembly); - context.AddAssemblyContext(unityAssembly.Name!, newContext); - processedAssembly = newContext; - } - - var imports = processedAssembly.Imports; - - foreach (var unityType in unityAssembly.ManifestModule!.TopLevelTypes) - ProcessType(processedAssembly, unityType, null, imports, ref typesUnstripped); - } - - Logger.Instance.LogTrace("Unstripped {UnstrippedTypeCount} types", typesUnstripped); - } - - private static void ProcessType(AssemblyRewriteContext processedAssembly, TypeDefinition unityType, - TypeDefinition? enclosingNewType, RuntimeAssemblyReferences imports, ref int typesUnstripped) - { - if (unityType.Name == "") - return; - - // Don't unstrip delegates, the il2cpp runtime methods are stripped and we cannot recover them - if (unityType.BaseType != null && unityType.BaseType.FullName == "System.MulticastDelegate") - return; - var newModule = processedAssembly.NewAssembly.ManifestModule!; - var processedType = enclosingNewType == null - ? processedAssembly.TryGetTypeByName(unityType.FullName)?.NewType - : enclosingNewType.NestedTypes.SingleOrDefault(it => it.Name == unityType.Name); - if (unityType.IsEnum) - { - if (processedType != null) return; - - typesUnstripped++; - var clonedType = CloneEnum(unityType, imports); - if (enclosingNewType == null) - { - newModule.TopLevelTypes.Add(clonedType); - } - else - { - enclosingNewType.NestedTypes.Add(clonedType); - } - - processedAssembly.RegisterTypeRewrite(new TypeRewriteContext(processedAssembly, null, clonedType)); - - return; - } - - if (processedType == null && !unityType.IsEnum && !HasNonBlittableFields(unityType) && - !unityType.HasGenericParameters()) // restore all types even if it would be not entirely correct - { - typesUnstripped++; - var clonedType = new TypeDefinition(unityType.Namespace, unityType.Name, ForcePublic(unityType.Attributes), unityType.BaseType == null ? null : newModule.DefaultImporter.ImportType(unityType.BaseType)); - if (enclosingNewType == null) - { - newModule.TopLevelTypes.Add(clonedType); - } - else - { - enclosingNewType.NestedTypes.Add(clonedType); - } - - // Unity assemblies sometimes have struct layouts on classes. - // This gets overlooked on mono but not on coreclr. - if (!clonedType.IsValueType() && (clonedType.IsExplicitLayout || clonedType.IsSequentialLayout)) - { - clonedType.IsExplicitLayout = clonedType.IsSequentialLayout = false; - clonedType.IsAutoLayout = true; - } - - processedAssembly.RegisterTypeRewrite(new TypeRewriteContext(processedAssembly, null, clonedType)); - processedType = clonedType; - } - - foreach (var nestedUnityType in unityType.NestedTypes) - ProcessType(processedAssembly, nestedUnityType, processedType, imports, ref typesUnstripped); - } - - private static TypeDefinition CloneEnum(TypeDefinition sourceEnum, RuntimeAssemblyReferences imports) - { - var newType = new TypeDefinition(sourceEnum.Namespace, sourceEnum.Name, ForcePublic(sourceEnum.Attributes), - imports.Module.Enum().ToTypeDefOrRef()); - foreach (var sourceEnumField in sourceEnum.Fields) - { - TypeSignature fieldType = sourceEnumField.Name == "value__" - ? imports.Module.ImportCorlibReference(sourceEnumField.Signature!.FieldType.FullName) - : newType.ToTypeSignature(); - var newField = new FieldDefinition(sourceEnumField.Name, sourceEnumField.Attributes, new FieldSignature(fieldType)); - newField.Constant = sourceEnumField.Constant; - newType.Fields.Add(newField); - } - - return newType; - } - - private static bool HasNonBlittableFields(TypeDefinition type) - { - if (!type.IsValueType()) return false; - - var typeSignature = type.ToTypeSignature(); - foreach (var fieldDefinition in type.Fields) - { - if (fieldDefinition.IsStatic || SignatureComparer.Default.Equals(fieldDefinition.Signature?.FieldType, typeSignature)) - continue; - - if (!fieldDefinition.Signature!.FieldType.IsValueType()) - return true; - - if (fieldDefinition.Signature.FieldType.Namespace?.StartsWith("System") ?? false && - HasNonBlittableFields(fieldDefinition.Signature.FieldType.Resolve())) - return true; - } - - return false; - } - - private static TypeAttributes ForcePublic(TypeAttributes typeAttributes) - { - var visibility = typeAttributes & TypeAttributes.VisibilityMask; - if (visibility == 0 || visibility == TypeAttributes.Public) - return typeAttributes | TypeAttributes.Public; - - return (typeAttributes & ~TypeAttributes.VisibilityMask) | TypeAttributes.NestedPublic; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass80UnstripFields.cs b/Il2CppInterop.Generator/Passes/Pass80UnstripFields.cs deleted file mode 100644 index 63bc0a9b..00000000 --- a/Il2CppInterop.Generator/Passes/Pass80UnstripFields.cs +++ /dev/null @@ -1,65 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass80UnstripFields -{ - public static void DoPass(RewriteGlobalContext context) - { - var fieldsUnstripped = 0; - var fieldsIgnored = 0; - - foreach (var unityAssembly in context.UnityAssemblies.Assemblies) - { - var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name); - if (processedAssembly == null) continue; - var imports = processedAssembly.Imports; - - foreach (var unityType in unityAssembly.ManifestModule!.TopLevelTypes) - { - var processedType = processedAssembly.TryGetTypeByName(unityType.FullName); - if (processedType == null) continue; - - if (unityType.IsEnum) - continue;// Enum fields do not get stripped. - - foreach (var unityField in unityType.Fields) - { - if (unityField.IsStatic && !unityField.HasConstant()) - continue;// Non-constant static fields might require initialization, which we can't do. - if (unityField.IsInstance() && (processedType.OriginalType is not null || processedType.NewType.IsReferenceType())) - continue;// Instance fields are only supported on newly created value types. - - var processedField = processedType.TryGetFieldByUnityAssemblyField(unityField); - if (processedField != null) continue; - - var fieldType = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(context, unityField.Signature!.FieldType, imports); - if (fieldType == null) - { - Logger.Instance.LogTrace("Field {UnityField} on type {UnityType} has unsupported type {UnityFieldType}, the type will be unusable", unityField.ToString(), unityType.FullName, unityField.Signature.FieldType.ToString()); - fieldsIgnored++; - continue; - } - - var newField = new FieldDefinition(unityField.Name!, - (unityField.Attributes & ~FieldAttributes.FieldAccessMask) | FieldAttributes.Public, fieldType); - - if (unityField.HasConstant()) newField.Constant = unityField.Constant; - - processedType.NewType.Fields.Add(newField); - - fieldsUnstripped++; - } - } - } - - Logger.Instance.LogInformation("Restored {FieldsUnstripped} fields", fieldsUnstripped); - Logger.Instance.LogInformation("Failed to restore {FieldsIgnored} fields", fieldsIgnored); - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs deleted file mode 100644 index 04a285bf..00000000 --- a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs +++ /dev/null @@ -1,291 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass80UnstripMethods -{ - public static void DoPass(RewriteGlobalContext context) - { - var methodsUnstripped = 0; - var methodsIgnored = 0; - - foreach (var unityAssembly in context.UnityAssemblies.Assemblies) - { - var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name); - if (processedAssembly == null) continue; - var imports = processedAssembly.Imports; - - foreach (var unityType in unityAssembly.ManifestModule!.TopLevelTypes) - { - var processedType = processedAssembly.TryGetTypeByName(unityType.FullName); - if (processedType == null) continue; - - foreach (var unityMethod in unityType.Methods) - { - var isICall = (unityMethod.ImplAttributes & MethodImplAttributes.InternalCall) != 0; - if (unityMethod.IsConstructor) continue; - if (unityMethod.IsAbstract) continue; - if (!unityMethod.HasMethodBody && !isICall) continue; // CoreCLR chokes on no-body methods - - var processedMethod = processedType.TryGetMethodByUnityAssemblyMethod(unityMethod); - if (processedMethod != null) continue; - - var returnType = ResolveTypeInNewAssemblies(context, unityMethod.Signature!.ReturnType, imports); - if (returnType == null) - { - Logger.Instance.LogTrace("Method {UnityMethod} has unsupported return type {UnityMethodReturnType}", unityMethod.ToString(), unityMethod.Signature.ReturnType.ToString()); - methodsIgnored++; - continue; - } - - var newAttributes = (unityMethod.Attributes & ~MethodAttributes.MemberAccessMask) | MethodAttributes.Public; - var newMethod = new MethodDefinition(unityMethod.Name, - newAttributes, - MethodSignatureCreator.CreateMethodSignature(newAttributes, returnType, unityMethod.Signature.GenericParameterCount)); - newMethod.CilMethodBody = new(); - var hadBadParameter = false; - foreach (var unityMethodParameter in unityMethod.Parameters) - { - var convertedType = - ResolveTypeInNewAssemblies(context, unityMethodParameter.ParameterType, imports); - if (convertedType == null) - { - hadBadParameter = true; - Logger.Instance.LogTrace("Method {UnityMethod} has unsupported parameter type {UnityMethodParameter}", unityMethod.ToString(), unityMethodParameter.ToString()); - break; - } - - newMethod.AddParameter(convertedType, unityMethodParameter.Name, unityMethodParameter.Definition?.Attributes ?? default); - } - - if (hadBadParameter) - { - methodsIgnored++; - continue; - } - - foreach (var unityMethodGenericParameter in unityMethod.GenericParameters) - { - var newParameter = new GenericParameter(unityMethodGenericParameter.Name.MakeValidInSource()); - newParameter.Attributes = unityMethodGenericParameter.Attributes; - foreach (var genericParameterConstraint in unityMethodGenericParameter.Constraints) - { - if (genericParameterConstraint.IsSystemValueType() || genericParameterConstraint.IsInterface()) - continue; - - if (genericParameterConstraint.IsSystemEnum()) - { - newParameter.Constraints.Add(new GenericParameterConstraint(imports.Module.Enum().ToTypeDefOrRef())); - continue; - } - - var newType = ResolveTypeInNewAssemblies(context, genericParameterConstraint.Constraint?.ToTypeSignature(), - imports); - if (newType != null) - newParameter.Constraints.Add(new GenericParameterConstraint(newType.ToTypeDefOrRef())); - } - - newMethod.GenericParameters.Add(newParameter); - } - - if (isICall) - { - var delegateType = - UnstripGenerator.CreateDelegateTypeForICallMethod(unityMethod, newMethod, imports); - processedType.NewType.NestedTypes.Add(delegateType); - - processedType.NewType.Methods.Add(newMethod); - - var delegateField = UnstripGenerator.GenerateStaticCtorSuffix(processedType.NewType, - delegateType, unityMethod, imports); - UnstripGenerator.GenerateInvokerMethodBody(newMethod, delegateField, delegateType, - processedType, imports); - } - else - { - Pass81FillUnstrippedMethodBodies.PushMethod(unityMethod, newMethod, processedType, imports); - processedType.NewType.Methods.Add(newMethod); - } - - if (unityMethod.IsGetMethod) - { - var property = GetOrCreateProperty(unityMethod, newMethod); - property.GetMethod = newMethod; - } - else if (unityMethod.IsSetMethod) - { - var property = GetOrCreateProperty(unityMethod, newMethod); - property.SetMethod = newMethod; - } - - var paramsMethod = context.CreateParamsMethod(unityMethod, newMethod, imports, - type => ResolveTypeInNewAssemblies(context, type, imports)); - if (paramsMethod != null) processedType.NewType.Methods.Add(paramsMethod); - - methodsUnstripped++; - } - } - } - - Logger.Instance.LogInformation("Restored {UnstrippedMethods} methods", methodsUnstripped); - Logger.Instance.LogInformation("Failed to restore {IgnoredMethods} methods", methodsIgnored); - } - - private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMethod, MethodDefinition newMethod) - { - var unityProperty = - unityMethod.DeclaringType!.Properties.Single( - it => it.SetMethod == unityMethod || it.GetMethod == unityMethod); - var newProperty = newMethod.DeclaringType?.Properties.SingleOrDefault(it => - it.Name == unityProperty.Name && it.Signature!.ParameterTypes.Count == unityProperty.Signature!.ParameterTypes.Count && - it.Signature.ParameterTypes.SequenceEqual(unityProperty.Signature.ParameterTypes, SignatureComparer.Default)); - if (newProperty == null) - { - TypeSignature propertyType; - IEnumerable parameterTypes; - if (unityMethod.IsGetMethod) - { - propertyType = newMethod.Signature!.ReturnType; - parameterTypes = newMethod.Signature.ParameterTypes; - } - else - { - propertyType = newMethod.Signature!.ParameterTypes.Last(); - parameterTypes = newMethod.Signature.ParameterTypes.Take(newMethod.Signature.ParameterTypes.Count - 1); - } - - var propertySignature = unityProperty.Signature!.HasThis - ? PropertySignature.CreateInstance(propertyType, parameterTypes) - : PropertySignature.CreateStatic(propertyType, parameterTypes); - newProperty = new PropertyDefinition(unityProperty.Name, unityProperty.Attributes, propertySignature); - newMethod.DeclaringType!.Properties.Add(newProperty); - } - - return newProperty; - } - - internal static TypeSignature? ResolveTypeInNewAssemblies(RewriteGlobalContext context, TypeSignature? unityType, - RuntimeAssemblyReferences imports, bool useSystemCorlibPrimitives = true) - { - var resolved = ResolveTypeInNewAssembliesRaw(context, unityType, imports, useSystemCorlibPrimitives); - return resolved != null ? imports.Module.DefaultImporter.ImportTypeSignature(resolved) : null; - } - - internal static TypeSignature? ResolveTypeInNewAssembliesRaw(RewriteGlobalContext context, TypeSignature? unityType, - RuntimeAssemblyReferences imports, bool useSystemCorlibPrimitives = true) - { - if (unityType is null) - return null; - - if (unityType is GenericParameterSignature genericParameterSignature) - return new GenericParameterSignature(imports.Module, genericParameterSignature.ParameterType, genericParameterSignature.Index); - - if (unityType is ByReferenceTypeSignature) - { - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports); - return resolvedElementType?.MakeByReferenceType(); - } - - if (unityType is ArrayBaseTypeSignature arrayType) - { - if (arrayType.Rank != 1) return null; - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports); - if (resolvedElementType == null) return null; - if (resolvedElementType.FullName == "System.String") - return imports.Il2CppStringArray; - var genericBase = resolvedElementType switch - { - GenericParameterSignature => imports.Il2CppArrayBase, - { IsValueType: true } => imports.Il2CppStructArray, - _ => imports.Il2CppReferenceArray - }; - return new GenericInstanceTypeSignature(genericBase.ToTypeDefOrRef(), false, resolvedElementType); - } - - if (unityType is PointerTypeSignature) - { - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports); - return resolvedElementType?.MakePointerType(); - } - - if (unityType is PinnedTypeSignature) - { - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports); - return resolvedElementType?.MakePinnedType(); - } - - if (unityType is CustomModifierTypeSignature customModifier) - { - var resolvedElementType = ResolveTypeInNewAssemblies(context, customModifier.BaseType, imports); - var resolvedModifierType = ResolveTypeInNewAssemblies(context, customModifier.ModifierType.ToTypeSignature(), imports); - return resolvedElementType is not null && resolvedModifierType is not null - ? new CustomModifierTypeSignature(resolvedModifierType.ToTypeDefOrRef(), customModifier.IsRequired, resolvedElementType) - : null; - } - - if (unityType is GenericInstanceTypeSignature genericInstance) - { - var baseRef = ResolveTypeInNewAssembliesRaw(context, genericInstance.GenericType.ToTypeSignature(), imports); - if (baseRef == null) return null; - var newInstance = new GenericInstanceTypeSignature(baseRef.ToTypeDefOrRef(), baseRef.IsValueType()); - foreach (var unityGenericArgument in genericInstance.TypeArguments) - { - var resolvedArgument = ResolveTypeInNewAssemblies(context, unityGenericArgument, imports); - if (resolvedArgument == null) return null; - newInstance.TypeArguments.Add(resolvedArgument); - } - - return newInstance; - } - - if (unityType is BoxedTypeSignature) - return null; // Boxed types are not yet supported - - if (unityType is FunctionPointerTypeSignature) - return null; // Function pointers are not yet supported - - if (unityType is SentinelTypeSignature) - return unityType; // SentinelTypeSignature has no state and be reused. - - if (unityType.DeclaringType != null) - { - var enclosingResolvedType = ResolveTypeInNewAssembliesRaw(context, unityType.DeclaringType.ToTypeSignature(), imports); - if (enclosingResolvedType == null) return null; - var resolvedNestedType = enclosingResolvedType.Resolve()!.NestedTypes - .FirstOrDefault(it => it.Name == unityType.Name); - - return resolvedNestedType?.ToTypeSignature(); - } - - var targetAssemblyName = unityType.Scope!.Name!; - if (targetAssemblyName.EndsWith(".dll")) - targetAssemblyName = targetAssemblyName.Substring(0, targetAssemblyName.Length - 4); - - if (useSystemCorlibPrimitives && (unityType.IsPrimitive() || unityType.ElementType is ElementType.String or ElementType.Void)) - return imports.Module.CorLibTypeFactory.FromElementType(unityType.ElementType); - - if (targetAssemblyName == "UnityEngine") - foreach (var assemblyRewriteContext in context.Assemblies) - { - if (!assemblyRewriteContext.NewAssembly.Name.StartsWith("UnityEngine")) - continue; - - var newTypeInAnyUnityAssembly = - assemblyRewriteContext.TryGetTypeByName(unityType.FullName)?.NewType; - if (newTypeInAnyUnityAssembly != null) - return newTypeInAnyUnityAssembly.ToTypeSignature(); - } - - var targetAssembly = context.TryGetAssemblyByName(targetAssemblyName); - var newType = targetAssembly?.TryGetTypeByName(unityType.FullName)?.NewType.ToTypeSignature(); - - return newType; - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs b/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs deleted file mode 100644 index 705e419a..00000000 --- a/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs +++ /dev/null @@ -1,47 +0,0 @@ -using AsmResolver.DotNet; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Utils; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass81FillUnstrippedMethodBodies -{ - private static readonly - List<(MethodDefinition unityMethod, MethodDefinition newMethod, TypeRewriteContext processedType, - RuntimeAssemblyReferences imports)> StuffToProcess = - new(); - - public static void DoPass(RewriteGlobalContext context) - { - var methodsSucceeded = 0; - var methodsFailed = 0; - - foreach (var (unityMethod, newMethod, processedType, imports) in StuffToProcess) - { - var success = UnstripTranslator.TranslateMethod(unityMethod, newMethod, processedType, imports); - if (success == false) - { - methodsFailed++; - UnstripTranslator.ReplaceBodyWithException(newMethod, imports); - } - else - { - methodsSucceeded++; - } - } - - StuffToProcess.Clear(); - StuffToProcess.Capacity = 0; - - Logger.Instance.LogInformation("IL unstrip statistics: {MethodsSucceeded} successful, {MethodsFailed} failed", methodsSucceeded, - methodsFailed); - } - - public static void PushMethod(MethodDefinition unityMethod, MethodDefinition newMethod, - TypeRewriteContext processedType, RuntimeAssemblyReferences imports) - { - StuffToProcess.Add((unityMethod, newMethod, processedType, imports)); - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass89GenerateForwarders.cs b/Il2CppInterop.Generator/Passes/Pass89GenerateForwarders.cs deleted file mode 100644 index 70b77aee..00000000 --- a/Il2CppInterop.Generator/Passes/Pass89GenerateForwarders.cs +++ /dev/null @@ -1,60 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass89GenerateForwarders -{ - public static void DoPass(RewriteGlobalContext context) - { - var targetAssembly = context.TryGetAssemblyByName("UnityEngine"); - if (targetAssembly == null) - { - Logger.Instance.LogInformation("No UnityEngine.dll, will not generate forwarders"); - return; - } - - var targetModule = targetAssembly.NewAssembly.ManifestModule; - - foreach (var assemblyRewriteContext in context.Assemblies) - { - if (!assemblyRewriteContext.NewAssembly.Name.StartsWith("UnityEngine.")) continue; - foreach (var mainModuleType in assemblyRewriteContext.NewAssembly.ManifestModule!.TopLevelTypes) - { - if (mainModuleType.Name == "") - continue; - - var exportedType = new ExportedType(null, mainModuleType.Namespace, mainModuleType.Name) - { - Attributes = TypeAttributes.Forwarder - }; - targetModule!.ExportedTypes.Add(exportedType); - - AddNestedTypes(mainModuleType, exportedType, targetModule); - } - } - } - - private static void AddNestedTypes(TypeDefinition mainModuleType, ExportedType importedType, - ModuleDefinition targetModule) - { - foreach (var nested in mainModuleType.NestedTypes) - { - if ((nested.Attributes & TypeAttributes.VisibilityMask) != TypeAttributes.NestedPublic) continue; - - var nestedImport = targetModule.DefaultImporter.ImportType(nested); - var nestedExport = new ExportedType(importedType, nestedImport.Namespace, nestedImport.Name) - { - Attributes = TypeAttributes.Forwarder - }; - - targetModule.ExportedTypes.Add(nestedExport); - - AddNestedTypes(nested, nestedExport, targetModule); - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass89GenerateMethodXrefCache.cs b/Il2CppInterop.Generator/Passes/Pass89GenerateMethodXrefCache.cs deleted file mode 100644 index 4ee5a9ab..00000000 --- a/Il2CppInterop.Generator/Passes/Pass89GenerateMethodXrefCache.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System.Text; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Common.Attributes; -using Il2CppInterop.Common.Maps; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass89GenerateMethodXrefCache -{ - public static void DoPass(RewriteGlobalContext context, GeneratorOptions options) - { - var data = new List(); - var existingAttributesPerAddress = new Dictionary(); - - if (options.NoXrefCache) - goto skipDataGather; - - foreach (var assemblyRewriteContext in context.Assemblies) - { - if (options.AdditionalAssembliesBlacklist.Contains(assemblyRewriteContext.NewAssembly.Name!)) - continue; - - var imports = assemblyRewriteContext.Imports; - - foreach (var typeRewriteContext in assemblyRewriteContext.Types) - foreach (var methodRewriteContext in typeRewriteContext.Methods) - { - var address = methodRewriteContext.Rva; - - if (existingAttributesPerAddress.TryGetValue(address, out var attribute)) - { - methodRewriteContext.NewMethod.CustomAttributes.Add( - new CustomAttribute((ICustomAttributeType?)imports.CachedScanResultsAttributector.Value, new CustomAttributeSignature([], - [ - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.RefRangeStart), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), attribute.RefRangeStart)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.RefRangeEnd), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), attribute.RefRangeEnd)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.XrefRangeStart), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), attribute.RefRangeStart)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.XrefRangeEnd), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), attribute.RefRangeEnd)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.MetadataInitTokenRva), - imports.Module.Long(), - new CustomAttributeArgument(imports.Module.Long(), attribute.MetadataInitTokenRva)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.MetadataInitFlagRva), - imports.Module.Long(), - new CustomAttributeArgument(imports.Module.Long(), attribute.MetadataInitFlagRva)) - ]))); - continue; - } - - var xrefStart = data.Count; - - foreach (var xrefScanResult in methodRewriteContext.XrefScanResults) - data.Add(MethodXrefScanCache.MethodData.FromXrefInstance(xrefScanResult)); - - var xrefEnd = data.Count; - - var refStart = 0; - var refEnd = 0; - - if (address != 0) - if (Pass16ScanMethodRefs.MapOfCallers.TryGetValue(address, out var callerMap)) - { - refStart = data.Count; - foreach (var xrefInstance in callerMap) - data.Add(MethodXrefScanCache.MethodData.FromXrefInstance(xrefInstance)); - - refEnd = data.Count; - } - - if (xrefEnd != xrefStart || refStart != refEnd) - { - methodRewriteContext.NewMethod.CustomAttributes.Add( - new CustomAttribute((ICustomAttributeType?)imports.CachedScanResultsAttributector.Value, new CustomAttributeSignature([], - [ - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.RefRangeStart), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), refStart)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.RefRangeEnd), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), refEnd)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.XrefRangeStart), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), xrefStart)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.XrefRangeEnd), - imports.Module.Int(), - new CustomAttributeArgument(imports.Module.Int(), xrefEnd)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.MetadataInitTokenRva), - imports.Module.Long(), - new CustomAttributeArgument(imports.Module.Long(), methodRewriteContext.MetadataInitTokenRva)), - new CustomAttributeNamedArgument( - CustomAttributeArgumentMemberType.Field, - nameof(CachedScanResultsAttribute.MetadataInitFlagRva), - imports.Module.Long(), - new CustomAttributeArgument(imports.Module.Long(), methodRewriteContext.MetadataInitFlagRva)) - ]))); - - existingAttributesPerAddress[address] = new CachedScanResultsAttribute - { - RefRangeStart = refStart, - RefRangeEnd = refEnd, - XrefRangeStart = xrefStart, - XrefRangeEnd = xrefEnd, - MetadataInitFlagRva = methodRewriteContext.MetadataInitFlagRva, - MetadataInitTokenRva = methodRewriteContext.MetadataInitTokenRva - }; - } - } - } - - skipDataGather: - - var header = new MethodXrefScanCache.FileHeader - { - Magic = MethodXrefScanCache.Magic, - Version = MethodXrefScanCache.Version, - InitMethodMetadataRva = XrefScanMetadataGenerationUtil.MetadataInitForMethodRva - }; - - using var writer = - new BinaryWriter( - new FileStream(Path.Combine(options.OutputDir, MethodXrefScanCache.FileName), FileMode.Create, - FileAccess.Write), Encoding.UTF8, false); - writer.Write(header); - - foreach (var valueTuple in data) - writer.Write(valueTuple); - - if (options.Verbose) - { - using var plainTextWriter = - new StreamWriter(Path.Combine(options.OutputDir, MethodXrefScanCache.FileName + ".txt")); - for (var i = 0; i < data.Count; i++) - plainTextWriter.WriteLine($"{i}\t{data[i].Type}\t{data[i].Address}\t{data[i].FoundAt}"); - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass90WriteToDisk.cs b/Il2CppInterop.Generator/Passes/Pass90WriteToDisk.cs deleted file mode 100644 index 8bb6ea15..00000000 --- a/Il2CppInterop.Generator/Passes/Pass90WriteToDisk.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Runtime.Versioning; -using AsmResolver; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Builder; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass90WriteToDisk -{ - public static void DoPass(RewriteGlobalContext context, GeneratorOptions options) - { - var targetAttributeConstructor = typeof(TargetFrameworkAttribute).GetConstructor([typeof(string)]); - - foreach (var asmContext in context.Assemblies) - { - var module = asmContext.NewAssembly.ManifestModule!; - - // Add TargetFrameworkAttribute to the assembly. - { - var importedConstructor = (ICustomAttributeType)module.DefaultImporter.ImportMethod(targetAttributeConstructor); - - CustomAttribute targetFrameworkAttribute = new(importedConstructor, new()); - - CustomAttributeArgument fixedArgument = new(module.CorLibTypeFactory.String, module.OriginalTargetRuntime.ToString()); - targetFrameworkAttribute.Signature!.FixedArguments.Add(fixedArgument); - - CustomAttributeNamedArgument namedArgument = new( - CustomAttributeArgumentMemberType.Property, - nameof(TargetFrameworkAttribute.FrameworkDisplayName), - module.CorLibTypeFactory.String, - new(module.CorLibTypeFactory.String, CorlibReferences.TargetFrameworkName)); - targetFrameworkAttribute.Signature.NamedArguments.Add(namedArgument); - - asmContext.NewAssembly.CustomAttributes.Add(targetFrameworkAttribute); - } - - // Rewrite corlib references. - foreach (var reference in module.AssemblyReferences) - { - // System.Private.CoreLib needs rewriting because references can get created during the rewrite process. - // mscorlib and netstandard are included for completeness. - if (reference.Name?.Value is "System.Private.CoreLib" or "mscorlib" or "netstandard") - { - CorlibReferences.RewriteCorlibReference(reference); - continue; - } - } - - // Optimize macros in all methods and assign tokens. - foreach (var type in module.GetAllTypes()) - { - foreach (var method in type.Methods) - { - method.CilMethodBody?.Instructions.OptimizeMacros(); - module.TokenAllocator.AssignNextAvailableToken(method); - } - } - } - - var assembliesToProcess = context.Assemblies - .Where(it => !options.AdditionalAssembliesBlacklist.Contains(it.NewAssembly.Name!)); - - void Processor(AssemblyRewriteContext assemblyContext) - { - assemblyContext.NewAssembly.Write( - Path.Combine(options.OutputDir ?? ".", $"{assemblyContext.NewAssembly.Name}.dll"), new ManagedPEImageBuilder(ThrowErrorListener.Instance)); - } - - if (options.Parallel) - { - Parallel.ForEach(assembliesToProcess, Processor); - } - else - { - foreach (var assemblyRewriteContext in assembliesToProcess) - { - Processor(assemblyRewriteContext); - } - } - } -} diff --git a/Il2CppInterop.Generator/Passes/Pass91GenerateMethodPointerMap.cs b/Il2CppInterop.Generator/Passes/Pass91GenerateMethodPointerMap.cs deleted file mode 100644 index 7d501591..00000000 --- a/Il2CppInterop.Generator/Passes/Pass91GenerateMethodPointerMap.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Reflection; -using System.Text; -using Il2CppInterop.Common.Maps; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Passes; - -public static class Pass91GenerateMethodPointerMap -{ - public static void DoPass(RewriteGlobalContext context, GeneratorOptions options) - { - var data = new List<(long, int, int)>(); - var assemblyList = new List(); - - foreach (var assemblyRewriteContext in context.Assemblies) - { - if (options.AdditionalAssembliesBlacklist.Contains(assemblyRewriteContext.NewAssembly.Name!)) - continue; - - assemblyList.Add(assemblyRewriteContext.NewAssembly.FullName); - - foreach (var typeRewriteContext in assemblyRewriteContext.Types) - foreach (var methodRewriteContext in typeRewriteContext.Methods) - { - var address = methodRewriteContext.Rva; - - if (address != 0) - data.Add((address, methodRewriteContext.NewMethod.MetadataToken.ToInt32(), assemblyList.Count - 1)); - } - } - - data.Sort((a, b) => a.Item1.CompareTo(b.Item1)); - - var header = new MethodAddressToTokenMapFileHeader - { - Magic = MethodAddressToTokenMapBase.Magic, - Version = MethodAddressToTokenMapBase.Version, - NumMethods = data.Count, - NumAssemblies = assemblyList.Count - }; - - using var writer = - new BinaryWriter( - new FileStream( - Path.Combine(options.OutputDir, MethodAddressToTokenMapBase.FileName), - FileMode.Create, FileAccess.Write), Encoding.UTF8, false); - writer.Write(header); - - foreach (var s in assemblyList) - writer.Write(s); - - header.DataOffset = (int)writer.BaseStream.Position; - - foreach (var valueTuple in data) - writer.Write(valueTuple.Item1); - - foreach (var valueTuple in data) - { - writer.Write(valueTuple.Item2); - writer.Write(valueTuple.Item3); - } - - writer.BaseStream.Position = 0; - writer.Write(header); - - if (options.Verbose) - { - using var plainTextWriter = new StreamWriter(Path.Combine(options.OutputDir, - MethodAddressToTokenMapBase.FileName + ".txt")); - for (var i = 0; i < data.Count; i++) - plainTextWriter.WriteLine($"{i}\t{data[i].Item1}\t{data[i].Item2}\t{data[i].Item3}"); - } - } -} diff --git a/Il2CppInterop.Generator/PointerConstructorProcessingLayer.cs b/Il2CppInterop.Generator/PointerConstructorProcessingLayer.cs new file mode 100644 index 00000000..d72f8c61 --- /dev/null +++ b/Il2CppInterop.Generator/PointerConstructorProcessingLayer.cs @@ -0,0 +1,97 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class PointerConstructorProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Pointer Constructor Processor"; + public override string Id => "pointer_constructor_processor"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var objectPointerType = appContext.ResolveTypeOrThrow(typeof(ObjectPointer)); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInjected || type.IsInterface) + continue; + + Debug.Assert(!type.IsStatic, "Static types should have been marked as instance types by now."); + + if (type.IsValueType) + continue; + + // Constructors for formerly static types should be private + var attributes = type.DefaultAttributes.HasFlag(TypeAttributes.Abstract) && type.DefaultAttributes.HasFlag(TypeAttributes.Sealed) + ? MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName + : MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; + + var constructor = new InjectedMethodAnalysisContext( + type, + ".ctor", + appContext.SystemTypes.SystemVoidType, + attributes, + [objectPointerType]) + { + IsInjected = true, + }; + type.Methods.Add(constructor); + + type.PointerConstructor = constructor; + } + } + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + var constructor = type.PointerConstructor; + if (constructor is null) + continue; + + var baseType = type.BaseType; + if (baseType is null) + continue; + + MethodAnalysisContext? baseConstructor; + if (baseType is GenericInstanceTypeAnalysisContext genericInstanceType) + { + var m = genericInstanceType.GenericType.PointerConstructor; + if (m is null) + continue; + baseConstructor = new ConcreteGenericMethodAnalysisContext(m, genericInstanceType.GenericArguments, []); + } + else + { + baseConstructor = baseType.PointerConstructor; + if (baseConstructor is null) + continue; + } + + var methodBody = new NativeMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Ldarg_1), + new Instruction(CilOpCodes.Call, baseConstructor), + new Instruction(CilOpCodes.Ret) + ] + }; + constructor.PutExtraData(methodBody); + } + } + } +} diff --git a/Il2CppInterop.Generator/PrimitiveImplicitConversionProcessingLayer.cs b/Il2CppInterop.Generator/PrimitiveImplicitConversionProcessingLayer.cs new file mode 100644 index 00000000..21f068fd --- /dev/null +++ b/Il2CppInterop.Generator/PrimitiveImplicitConversionProcessingLayer.cs @@ -0,0 +1,155 @@ +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Runtime; + +namespace Il2CppInterop.Generator; + +public class PrimitiveImplicitConversionProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "primitive_implicit_conversions"; + public override string Name => "Primitive Implicit Conversions"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var il2CppMscorlib = appContext.Il2CppMscorlib; + var mscorlib = appContext.Mscorlib; + + // Il2CppSystem.String + { + var il2CppType = il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.String"); + var monoType = mscorlib.GetTypeByFullNameOrThrow("System.String"); + + var objectPointerType = appContext.ResolveTypeOrThrow(typeof(ObjectPointer)); + var objectPointerConversionFromIntPtr = objectPointerType.GetExplicitConversionFrom(appContext.SystemTypes.SystemIntPtrType); + + var generationInternalsType = appContext.ResolveTypeOrThrow(typeof(GenerationInternals)); + var managedStringToIl2Cpp = generationInternalsType.Methods.First(m => m.Name == nameof(GenerationInternals.ManagedStringToIl2Cpp)); + var il2CppStringToManaged = generationInternalsType.Methods.First(m => m.Name == nameof(GenerationInternals.Il2CppStringToManaged)); + + // Il2Cpp -> Mono + { + var implicitConversion = new InjectedMethodAnalysisContext( + il2CppType, + "op_Implicit", + monoType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Static, + [il2CppType]); + implicitConversion.IsInjected = true; + il2CppType.Methods.Add(implicitConversion); + + implicitConversion.PutExtraData(new TranslatedMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, il2CppStringToManaged), // Returns null if the input is null + new Instruction(CilOpCodes.Ret), + ] + }); + } + + // Mono -> Il2Cpp + { + var implicitConversion = new InjectedMethodAnalysisContext( + il2CppType, + "op_Implicit", + il2CppType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Static, + [monoType]); + implicitConversion.IsInjected = true; + il2CppType.Methods.Add(implicitConversion); + + implicitConversion.PutExtraData(new TranslatedMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg_0), + new Instruction(CilOpCodes.Call, managedStringToIl2Cpp), // Returns null if the input is null + new Instruction(CilOpCodes.Ret), + ] + }); + } + } + + ReadOnlySpan<(string, string)> numericPairs = + [ + ("Il2CppSystem.SByte", "System.SByte"), + ("Il2CppSystem.Byte", "System.Byte"), + ("Il2CppSystem.Int16", "System.Int16"), + ("Il2CppSystem.UInt16", "System.UInt16"), + ("Il2CppSystem.Int32", "System.Int32"), + ("Il2CppSystem.UInt32", "System.UInt32"), + ("Il2CppSystem.Int64", "System.Int64"), + ("Il2CppSystem.UInt64", "System.UInt64"), + ("Il2CppSystem.Single", "System.Single"), + ("Il2CppSystem.Double", "System.Double"), + ("Il2CppSystem.Char", "System.Char"), + ("Il2CppSystem.Boolean", "System.Boolean"), + ("Il2CppSystem.IntPtr", "System.IntPtr"), + ("Il2CppSystem.UIntPtr", "System.UIntPtr"), + ]; + + foreach (var (il2CppTypeName, monoTypeName) in numericPairs) + { + var il2CppType = il2CppMscorlib.GetTypeByFullNameOrThrow(il2CppTypeName); + var monoType = mscorlib.GetTypeByFullNameOrThrow(monoTypeName); + var field = il2CppType.Fields.Single(f => !f.IsStatic); + field.OverrideFieldType = monoType; + + if (monoTypeName is "System.Boolean") + { + // Not sure this does anything meaningful + + // Ensure boolean size is 1 byte + il2CppType.Definition!.RawSizes.native_size = 1; + + // The fact that we have to change the layout here might indicate an issue in Cpp2IL. + // It only emits sizes for structs with explicit layout, but not for sequential layout. + il2CppType.Attributes = (il2CppType.Attributes & ~TypeAttributes.LayoutMask) | TypeAttributes.ExplicitLayout; + } + + // Il2Cpp -> Mono + { + var implicitConversion = new InjectedMethodAnalysisContext( + il2CppType, + "op_Implicit", + monoType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Static, + [il2CppType]); + implicitConversion.IsInjected = true; + implicitConversion.PutExtraData(new TranslatedMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarg, implicitConversion.Parameters[0]), + new Instruction(CilOpCodes.Ldfld, field), + new Instruction(CilOpCodes.Ret), + ] + }); + il2CppType.Methods.Add(implicitConversion); + } + + // Mono -> Il2Cpp + { + var implicitConversion = new InjectedMethodAnalysisContext( + il2CppType, + "op_Implicit", + il2CppType, + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Static, + [monoType]); + implicitConversion.IsInjected = true; + implicitConversion.PutExtraData(new TranslatedMethodBody() + { + Instructions = [ + new Instruction(CilOpCodes.Ldarga, implicitConversion.Parameters[0]), + new Instruction(CilOpCodes.Conv_U), + new Instruction(CilOpCodes.Ldobj, il2CppType), + new Instruction(CilOpCodes.Ret), + ] + }); + il2CppType.Methods.Add(implicitConversion); + } + } + } + +} diff --git a/Il2CppInterop.Generator/PublicizerProcessingLayer.cs b/Il2CppInterop.Generator/PublicizerProcessingLayer.cs new file mode 100644 index 00000000..c5d5d6e0 --- /dev/null +++ b/Il2CppInterop.Generator/PublicizerProcessingLayer.cs @@ -0,0 +1,57 @@ +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class PublicizerProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "publicizer"; + public override string Name => "Publicizer"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + { + continue; + } + + foreach (var type in assembly.Types) + { + if (type.IsInjected) + { + continue; + } + + type.Visibility = type.DeclaringType is null ? TypeAttributes.Public : TypeAttributes.NestedPublic; + + foreach (var field in type.Fields) + { + if (!field.IsInjected) + { + field.Visibility = FieldAttributes.Public; + } + } + + foreach (var method in type.Methods) + { + if (method.Visibility != MethodAttributes.Public) + { + if (method.IsStaticConstructor || method.IsInjected) + { + continue; + } + + if (method.ImplementsAnInterfaceMethod) + { + continue; + } + + method.Visibility = MethodAttributes.Public; + } + } + } + } + } +} diff --git a/Il2CppInterop.Generator/ReferenceAssemblyInjectionProcessingLayer.cs b/Il2CppInterop.Generator/ReferenceAssemblyInjectionProcessingLayer.cs new file mode 100644 index 00000000..5fbea1a9 --- /dev/null +++ b/Il2CppInterop.Generator/ReferenceAssemblyInjectionProcessingLayer.cs @@ -0,0 +1,136 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.Exceptions; +using Il2CppInterop.Runtime.Injection; +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace Il2CppInterop.Generator; + +public class ReferenceAssemblyInjectionProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "reference_assembly_injector"; + public override string Name => "Inject required references into the Cpp2IL context system"; + + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "The accessed members are never invoked by this processing layer, only viewed for reference.")] + [SuppressMessage("Trimming", "IL2111:Method with parameters or return value with `DynamicallyAccessedMembersAttribute` is accessed via reflection. Trimmer can't guarantee availability of the requirements of the method.", Justification = "The accessed members are never invoked by this processing layer, only viewed for reference.")] + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + // Types need to be provided twice, so that the linker can find them + + ReadOnlySpan il2CppInteropCommonTypes = + [ + typeof(IL2CPP), + typeof(ObjectPointer), + typeof(Il2CppType), + typeof(Il2CppObjectPool), + + typeof(Il2CppMemberAttribute), + typeof(Il2CppMethodAttribute), + typeof(Il2CppFieldAttribute), + typeof(Il2CppPropertyAttribute), + typeof(Il2CppEventAttribute), + typeof(Il2CppTypeAttribute), + + typeof(IIl2CppType), + typeof(IIl2CppType<>), + ]; + { + var injectedAssembly = CreateTypes(appContext, typeof(IL2CPP).Assembly, il2CppInteropCommonTypes); + + InjectContentFromSourceType(injectedAssembly, typeof(IL2CPP)); + InjectContentFromSourceType(injectedAssembly, typeof(ObjectPointer)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppType)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppObjectPool)); + + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppMemberAttribute)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppMethodAttribute)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppFieldAttribute)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppPropertyAttribute)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppEventAttribute)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppTypeAttribute)); + + InjectContentFromSourceType(injectedAssembly, typeof(IIl2CppType)); + InjectContentFromSourceType(injectedAssembly, typeof(IIl2CppType<>)); + } + + ReadOnlySpan il2CppInteropRuntimeTypes = + [ + typeof(Il2CppArrayBase), + typeof(Il2CppArrayBase<>), + typeof(Il2CppArrayRank1<>), + typeof(Il2CppArrayRank2<>), + typeof(Il2CppArrayRank3<>), + typeof(Il2CppArrayRank4<>), + typeof(Il2CppArrayRank5<>), + + typeof(Il2CppException), + typeof(TypeInjector), + typeof(DelegateSupport), + typeof(RuntimeInvoke), + typeof(FieldAccess), + typeof(Pointer<>), + typeof(ByReference<>), + typeof(ByReference), + typeof(IIl2CppException), + typeof(NativeBoxing), + typeof(GenerationInternals), + ]; + { + var injectedAssembly = CreateTypes(appContext, typeof(Il2CppArrayBase).Assembly, il2CppInteropRuntimeTypes); + + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppArrayBase)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppArrayBase<>)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppArrayRank1<>)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppArrayRank2<>)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppArrayRank3<>)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppArrayRank4<>)); + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppArrayRank5<>)); + + InjectContentFromSourceType(injectedAssembly, typeof(Il2CppException)); + InjectContentFromSourceType(injectedAssembly, typeof(TypeInjector)); + InjectContentFromSourceType(injectedAssembly, typeof(DelegateSupport)); + InjectContentFromSourceType(injectedAssembly, typeof(RuntimeInvoke)); + InjectContentFromSourceType(injectedAssembly, typeof(FieldAccess)); + InjectContentFromSourceType(injectedAssembly, typeof(Pointer<>)); + InjectContentFromSourceType(injectedAssembly, typeof(ByReference<>)); + InjectContentFromSourceType(injectedAssembly, typeof(ByReference)); + InjectContentFromSourceType(injectedAssembly, typeof(IIl2CppException)); + InjectContentFromSourceType(injectedAssembly, typeof(NativeBoxing)); + InjectContentFromSourceType(injectedAssembly, typeof(GenerationInternals)); + } + } + + /// + /// Injects the given assembly and some of its types into the . + /// + /// The + /// The assembly + /// The types to be injected from . Must be in order of inheritance + private static InjectedAssemblyAnalysisContext CreateTypes(ApplicationAnalysisContext appContext, Assembly assembly, ReadOnlySpan types) + { + var injectedAssembly = appContext.InjectAssembly(assembly); + + injectedAssembly.IsReferenceAssembly = true; + + var typeContextArray = new InjectedTypeAnalysisContext[types.Length]; + + for (var i = 0; i < types.Length; i++) + { + typeContextArray[i] = injectedAssembly.InjectType(types[i]); + } + + return injectedAssembly; + } + + private static void InjectContentFromSourceType(AssemblyAnalysisContext assembly, [DynamicallyAccessedMembers(InjectedTypeAnalysisContextExtensions.AccessedMemberTypes)] Type sourceType) + { + var type = (InjectedTypeAnalysisContext)assembly.GetTypeByFullNameOrThrow(sourceType); + type.InjectContentFromSourceType(sourceType); + } +} diff --git a/Il2CppInterop.Generator/ReferenceReplacementProcessingLayer.cs b/Il2CppInterop.Generator/ReferenceReplacementProcessingLayer.cs new file mode 100644 index 00000000..ed7af996 --- /dev/null +++ b/Il2CppInterop.Generator/ReferenceReplacementProcessingLayer.cs @@ -0,0 +1,119 @@ +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Visitors; + +namespace Il2CppInterop.Generator; + +public class ReferenceReplacementProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Id => "reference_replacement"; + public override string Name => "Reference Replacement"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var il2CppMscorlib = appContext.AssembliesByName["Il2Cppmscorlib"]; + var mscorlib = appContext.AssembliesByName["mscorlib"]; + + var monoSystemObject = mscorlib.GetTypeByFullNameOrThrow("System.Object"); + var monoSystemValueType = mscorlib.GetTypeByFullNameOrThrow("System.ValueType"); + var monoSystemVoid = mscorlib.GetTypeByFullNameOrThrow("System.Void"); + + var il2CppSystemObject = il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Object"); + var il2CppSystemVoid = il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Void"); + var il2CppSystemEnum = il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Enum"); + var il2CppSystemValueType = il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.ValueType"); + + var visitor = TypeConversionVisitor.Create(appContext); + + il2CppSystemObject.OverrideBaseType = monoSystemObject; + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + + foreach (var type in assembly.Types) + { + if (type.IsInterface) + { + type.OverrideBaseType = null; + } + else if (type.IsStatic) + { + type.OverrideBaseType = monoSystemObject; + } + else if (type == il2CppSystemObject || type == il2CppSystemEnum || type == il2CppSystemValueType) + { + } + else if (type.BaseType is null || type.BaseType == il2CppSystemObject) + { + type.OverrideBaseType = il2CppSystemObject; + } + else if (type.BaseType == il2CppSystemValueType) + { + if (type is not { Definition.IsValueType: false }) + { + type.OverrideBaseType = monoSystemValueType; + } + } + else if (type.BaseType == il2CppSystemEnum) + { + if (type is not { Definition.IsEnumType: false }) + { + type.OverrideBaseType = monoSystemValueType; + } + } + else + { + type.OverrideBaseType = visitor.Replace(type.BaseType); + } + + visitor.Modify(type.InterfaceContexts); + foreach (var genericParameter in type.GenericParameters) + { + visitor.Modify(genericParameter.ConstraintTypes); + } + + foreach (var field in type.Fields) + { + field.OverrideFieldType = visitor.Replace(field.FieldType); + } + + foreach (var method in type.Methods) + { + if (method.ReturnType == il2CppSystemVoid) + { + // Special case for void return type. + method.OverrideReturnType = monoSystemVoid; + } + else + { + method.OverrideReturnType = visitor.Replace(method.ReturnType); + } + foreach (var parameter in method.Parameters) + { + parameter.OverrideParameterType = visitor.Replace(parameter.ParameterType); + } + foreach (var genericParameter in method.GenericParameters) + { + visitor.Modify(genericParameter.ConstraintTypes); + } + for (var i = 0; i < method.Overrides.Count; i++) + { + method.Overrides[i] = visitor.Replace(method.Overrides[i]); + } + } + + foreach (var property in type.Properties) + { + property.OverridePropertyType = visitor.Replace(property.PropertyType); + } + + foreach (var @event in type.Events) + { + @event.OverrideEventType = visitor.Replace(@event.EventType); + } + } + } + } +} diff --git a/Il2CppInterop.Generator/Runners/DeobfuscationAnalyzer.cs b/Il2CppInterop.Generator/Runners/DeobfuscationAnalyzer.cs deleted file mode 100644 index 2c5b465c..00000000 --- a/Il2CppInterop.Generator/Runners/DeobfuscationAnalyzer.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.MetadataAccess; -using Il2CppInterop.Generator.Passes; -using Il2CppInterop.Generator.Utils; - -namespace Il2CppInterop.Generator.Runners; - -public static class DeobfuscationAnalyzer -{ - public static Il2CppInteropGenerator AddDeobfuscationAnalyzer(this Il2CppInteropGenerator gen) - { - return gen.AddRunner(); - } -} - -internal class DeobfuscationAnalyzerRunner : IRunner -{ - public void Dispose() { } - - public void Run(GeneratorOptions options) - { - RewriteGlobalContext rewriteContext; - IIl2CppMetadataAccess inputAssemblies; - using (new TimingCookie("Reading assemblies")) - { - inputAssemblies = new AssemblyMetadataAccess(options.Source ?? throw new ArgumentException("Source assemblies must be provided.", nameof(options))); - } - - using (new TimingCookie("Creating assembly contexts")) - { - rewriteContext = new RewriteGlobalContext(options, inputAssemblies, NullMetadataAccess.Instance); - } - - for (var chars = 1; chars <= 3; chars++) - for (var uniq = 3; uniq <= 15; uniq++) - { - options.TypeDeobfuscationCharsPerUniquifier = chars; - options.TypeDeobfuscationMaxUniquifiers = uniq; - - rewriteContext.RenamedTypes.Clear(); - rewriteContext.RenameGroups.Clear(); - - Pass05CreateRenameGroups.DoPass(rewriteContext); - - var uniqueTypes = rewriteContext.RenameGroups.Values.Count(it => it.Count == 1); - var nonUniqueTypes = rewriteContext.RenameGroups.Values.Count(it => it.Count > 1); - - // Ensure the output is written to stdout - Console.WriteLine($"Chars=\t{chars}\tMaxU=\t{uniq}\tUniq=\t{uniqueTypes}\tNonUniq=\t{nonUniqueTypes}"); - } - } -} diff --git a/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs b/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs deleted file mode 100644 index e6a5e28b..00000000 --- a/Il2CppInterop.Generator/Runners/DeobfuscationMapGenerator.cs +++ /dev/null @@ -1,391 +0,0 @@ -using System.IO.Compression; -using System.Text; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Il2CppInterop.Generator.MetadataAccess; -using Il2CppInterop.Generator.Passes; -using Il2CppInterop.Generator.Utils; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Runners; - -public static class DeobfuscationMapGenerator -{ - public static Il2CppInteropGenerator AddDeobfuscationMapGenerator(this Il2CppInteropGenerator gen) - { - return gen.AddRunner(); - } -} - -internal class DeobfuscationMapGeneratorRunner : IRunner -{ - public void Run(GeneratorOptions options) - { - if (options.Source == null || !options.Source.Any()) - { - Console.WriteLine("No input specified; use -h for help"); - return; - } - - if (string.IsNullOrEmpty(options.OutputDir)) - { - Console.WriteLine("No target dir specified; use -h for help"); - return; - } - - if (string.IsNullOrEmpty(options.DeobfuscationNewAssembliesPath)) - { - Console.WriteLine("No obfuscated assembly path specified; use -h for help"); - return; - } - - if (!Directory.Exists(options.OutputDir)) - Directory.CreateDirectory(options.OutputDir); - - RewriteGlobalContext rewriteContext; - IIl2CppMetadataAccess inputAssemblies; - using (new TimingCookie("Reading assemblies")) - { - inputAssemblies = - new AssemblyMetadataAccess(Directory.EnumerateFiles(options.DeobfuscationNewAssembliesPath, "*.dll")); - } - - using (new TimingCookie("Creating rewrite assemblies")) - { - rewriteContext = new RewriteGlobalContext(options, inputAssemblies, NullMetadataAccess.Instance); - } - - using (new TimingCookie("Computing renames")) - { - Pass05CreateRenameGroups.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating typedefs")) - { - Pass10CreateTypedefs.DoPass(rewriteContext); - } - - using (new TimingCookie("Computing struct blittability")) - { - Pass11ComputeTypeSpecifics.DoPass(rewriteContext); - } - - using (new TimingCookie("Filling typedefs")) - { - Pass12FillTypedefs.DoPass(rewriteContext); - } - - using (new TimingCookie("Filling generic constraints")) - { - Pass13FillGenericConstraints.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating members")) - { - Pass15GenerateMemberContexts.DoPass(rewriteContext); - } - - - RewriteGlobalContext cleanContext; - IIl2CppMetadataAccess cleanAssemblies; - using (new TimingCookie("Reading clean assemblies")) - { - cleanAssemblies = new AssemblyMetadataAccess(options.Source); - } - - using (new TimingCookie("Creating clean rewrite assemblies")) - { - cleanContext = new RewriteGlobalContext(options, cleanAssemblies, NullMetadataAccess.Instance); - } - - using (new TimingCookie("Computing clean assembly renames")) - { - Pass05CreateRenameGroups.DoPass(cleanContext); - } - - using (new TimingCookie("Creating clean assembly typedefs")) - { - Pass10CreateTypedefs.DoPass(cleanContext); - } - - - var usedNames = new Dictionary(); - - using var fileOutput = new FileStream(options.OutputDir + Path.DirectorySeparatorChar + "RenameMap.csv.gz", - FileMode.Create, FileAccess.Write); - using var gzipStream = new GZipStream(fileOutput, CompressionLevel.Optimal, true); - using var writer = new StreamWriter(gzipStream, Encoding.UTF8, 65536, true); - - void DoEnum(TypeRewriteContext obfuscatedType, TypeRewriteContext cleanType) - { - foreach (var originalTypeField in obfuscatedType.OriginalType.Fields) - { - if (!originalTypeField.Name.IsObfuscated(obfuscatedType.AssemblyContext.GlobalContext.Options)) - continue; - var matchedField = - cleanType.OriginalType.Fields[obfuscatedType.OriginalType.Fields.IndexOf(originalTypeField)]; - - writer.WriteLine(obfuscatedType.NewType.GetNamespacePrefix() + "." + obfuscatedType.NewType.Name + - "::" + Pass22GenerateEnums.GetUnmangledName(originalTypeField) + ";" + - matchedField.Name + ";0"); - } - } - - foreach (var assemblyContext in rewriteContext.Assemblies) - { - if (options.DeobfuscationGenerationAssemblies.Count > 0 && - !options.DeobfuscationGenerationAssemblies.Contains(assemblyContext.NewAssembly.Name!)) - continue; - - var cleanAssembly = cleanContext.GetAssemblyByName(assemblyContext.OriginalAssembly.Name!); - - void DoType(TypeRewriteContext typeContext, TypeRewriteContext? enclosingType) - { - if (!typeContext.OriginalNameWasObfuscated) return; - - var cleanType = FindBestMatchType(typeContext, cleanAssembly, enclosingType); - if (cleanType.Item1 == null) return; - - if (!usedNames.TryGetValue(cleanType.Item1.NewType, out var existing) || - existing.Item2 < cleanType.Item2) - usedNames[cleanType.Item1.NewType] = ( - typeContext.NewType.GetNamespacePrefix() + "." + typeContext.NewType.Name, cleanType.Item2, - typeContext.OriginalType.Namespace != cleanType.Item1.OriginalType.Namespace); - else - return; - - if (typeContext.OriginalType.IsEnum) - DoEnum(typeContext, cleanType.Item1); - - foreach (var originalTypeNestedType in typeContext.OriginalType.NestedTypes) - DoType(typeContext.AssemblyContext.GetContextForOriginalType(originalTypeNestedType), - cleanType.Item1); - } - - foreach (var typeContext in assemblyContext.Types) - { - if (typeContext.NewType.DeclaringType != null) continue; - - DoType(typeContext, null); - } - } - - - foreach (var keyValuePair in usedNames) - writer.WriteLine(keyValuePair.Value.Item1 + ";" + - (keyValuePair.Value.ForceNs ? keyValuePair.Key.Namespace + "." : "") + - keyValuePair.Key.Name + ";" + keyValuePair.Value.Item2); - - Logger.Instance.LogInformation("Done!"); - - rewriteContext.Dispose(); - } - - private static (TypeRewriteContext?, int) FindBestMatchType(TypeRewriteContext obfType, - AssemblyRewriteContext cleanAssembly, TypeRewriteContext? enclosingCleanType) - { - var inheritanceDepthOfOriginal = 0; - var currentBase = obfType.OriginalType.BaseType; - while (true) - { - if (currentBase == null) break; - var currentBaseContext = - obfType.AssemblyContext.GlobalContext.TryGetNewTypeForOriginal(currentBase.Resolve()!); - if (currentBaseContext == null || !currentBaseContext.OriginalNameWasObfuscated) break; - - inheritanceDepthOfOriginal++; - currentBase = currentBaseContext.OriginalType.BaseType; - } - - var bestPenalty = int.MinValue; - TypeRewriteContext? bestMatch = null; - - var source = - enclosingCleanType?.OriginalType.NestedTypes.Select(it => - cleanAssembly.GlobalContext.GetNewTypeForOriginal(it)) ?? - cleanAssembly.Types.Where(it => it.NewType.DeclaringType == null); - - foreach (var candidateCleanType in source) - { - if (obfType.OriginalType.HasMethods() != candidateCleanType.OriginalType.HasMethods()) - continue; - - if (obfType.OriginalType.HasFields() != candidateCleanType.OriginalType.HasFields()) - continue; - - if (obfType.OriginalType.IsEnum) - if (obfType.OriginalType.Fields.Count != candidateCleanType.OriginalType.Fields.Count) - continue; - - var currentPenalty = 0; - - var tryBase = candidateCleanType.OriginalType.BaseType; - var actualBaseDepth = 0; - while (tryBase != null) - { - if (tryBase?.Name == currentBase?.Name && tryBase?.Namespace == currentBase?.Namespace) - break; - - tryBase = tryBase?.Resolve()?.BaseType; - actualBaseDepth++; - } - - if (tryBase == null && currentBase != null) - continue; - - var baseDepthDifference = Math.Abs(actualBaseDepth - inheritanceDepthOfOriginal); - if (baseDepthDifference > 1) continue; // heuristic optimization - currentPenalty -= baseDepthDifference * 50; - - currentPenalty -= - Math.Abs(candidateCleanType.OriginalType.Fields.Count - obfType.OriginalType.Fields.Count) * 5; - - currentPenalty -= Math.Abs(obfType.OriginalType.NestedTypes.Count - - candidateCleanType.OriginalType.NestedTypes.Count) * 10; - - currentPenalty -= - Math.Abs(obfType.OriginalType.Properties.Count - candidateCleanType.OriginalType.Properties.Count) * 5; - - currentPenalty -= - Math.Abs(obfType.OriginalType.Interfaces.Count - candidateCleanType.OriginalType.Interfaces.Count) * 35; - - var options = obfType.AssemblyContext.GlobalContext.Options; - - foreach (var obfuscatedField in obfType.OriginalType.Fields) - { - if (obfuscatedField.Name.IsObfuscated(options)) - { - var bestFieldScore = candidateCleanType.OriginalType.Fields.Max(it => - TypeMatchWeight(obfuscatedField.Signature!.FieldType, it.Signature!.FieldType, options)); - currentPenalty += bestFieldScore * (bestFieldScore < 0 ? 10 : 2); - continue; - } - - if (candidateCleanType.OriginalType.Fields.Any(it => it.Name == obfuscatedField.Name)) - currentPenalty += 10; - } - - foreach (var obfuscatedMethod in obfType.OriginalType.Methods) - { - if (obfuscatedMethod.IsConstructor) continue; - - if (obfuscatedMethod.Name.IsObfuscated(options)) - { - var bestMethodScore = candidateCleanType.OriginalType.Methods.Max(it => - MethodSignatureMatchWeight(obfuscatedMethod, it, options)); - currentPenalty += bestMethodScore * (bestMethodScore < 0 ? 10 : 1); - - continue; - } - - if (candidateCleanType.OriginalType.Methods.Any(it => it.Name == obfuscatedMethod.Name)) - currentPenalty += (obfuscatedMethod.Name?.Length ?? 0) / 10 * 5 + 1; - } - - if (currentPenalty == bestPenalty) - { - bestMatch = null; - } - else if (currentPenalty > bestPenalty) - { - bestPenalty = currentPenalty; - bestMatch = candidateCleanType; - } - } - - // if (bestPenalty < -100) - // bestMatch = null; - - return (bestMatch, bestPenalty); - } - - private static int TypeMatchWeight(TypeSignature a, TypeSignature b, GeneratorOptions options) - { - if (a.GetType() != b.GetType()) - return -1; - - var runningSum = 0; - - void Accumulate(int i) - { - if (i < 0 || runningSum < 0) - runningSum = -1; - else - runningSum += i; - } - - switch (a) - { - case ArrayBaseTypeSignature arr: - if (b is not ArrayBaseTypeSignature brr) - return -1; - return TypeMatchWeight(arr.BaseType, brr.BaseType, options) * 5; - case ByReferenceTypeSignature abr: - if (b is not ByReferenceTypeSignature bbr) - return -1; - return TypeMatchWeight(abr.BaseType, bbr.BaseType, options) * 5; - case GenericInstanceTypeSignature agi: - if (b is not GenericInstanceTypeSignature bgi) - return -1; - if (agi.TypeArguments.Count != bgi.TypeArguments.Count) return -1; - Accumulate(TypeMatchWeight(agi.GenericType.ToTypeSignature(), bgi.GenericType.ToTypeSignature(), options)); - for (var i = 0; i < agi.TypeArguments.Count; i++) - Accumulate(TypeMatchWeight(agi.TypeArguments[i], bgi.TypeArguments[i], options)); - return runningSum * 5; - case GenericParameterSignature: - if (b is not GenericParameterSignature) - return -1; - return 5; - default: - if (a.IsNested()) - { - if (!b.IsNested()) - return -1; - - if (a.Name.IsObfuscated(options)) - return 0; - - var declMatch = TypeMatchWeight(a.DeclaringType!.ToTypeSignature(), b.DeclaringType!.ToTypeSignature(), options); - if (declMatch == -1 || a.Name != b.Name) - return -1; - - return 1; - } - - if (a.Name.IsObfuscated(options)) - return 0; - return a.Name == b.Name && a.Namespace == b.Namespace ? 1 : -1; - } - } - - private static int MethodSignatureMatchWeight(MethodDefinition a, MethodDefinition b, GeneratorOptions options) - { - if (a.Parameters.Count != b.Parameters.Count || a.IsStatic != b.IsStatic || - (a.Attributes & MethodAttributes.MemberAccessMask) != - (b.Attributes & MethodAttributes.MemberAccessMask)) - return -1; - - var runningSum = TypeMatchWeight(a.Signature!.ReturnType, b.Signature!.ReturnType, options); - if (runningSum == -1) - return -1; - - void Accumulate(int i) - { - if (i < 0 || runningSum < 0) - runningSum = -1; - else - runningSum += i; - } - - for (var i = 0; i < a.Parameters.Count; i++) - Accumulate(TypeMatchWeight(a.Parameters[i].ParameterType, b.Parameters[i].ParameterType, options)); - - return runningSum * (a.Parameters.Count + 1); - } - - public void Dispose() { } -} diff --git a/Il2CppInterop.Generator/Runners/IRunner.cs b/Il2CppInterop.Generator/Runners/IRunner.cs deleted file mode 100644 index ad697781..00000000 --- a/Il2CppInterop.Generator/Runners/IRunner.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Il2CppInterop.Generator.Runners; - -internal interface IRunner : IDisposable -{ - void Run(GeneratorOptions options); -} diff --git a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs b/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs deleted file mode 100644 index 72d46862..00000000 --- a/Il2CppInterop.Generator/Runners/InteropAssemblyGenerator.cs +++ /dev/null @@ -1,222 +0,0 @@ -using Il2CppInterop.Common; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.MetadataAccess; -using Il2CppInterop.Generator.Passes; -using Il2CppInterop.Generator.Utils; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Runners; - -public static class InteropAssemblyGenerator -{ - public static Il2CppInteropGenerator AddInteropAssemblyGenerator(this Il2CppInteropGenerator gen) - { - return gen.AddRunner(); - } -} - -internal class InteropAssemblyGeneratorRunner : IRunner -{ - public void Run(GeneratorOptions options) - { - if (options.Source == null || !options.Source.Any()) - { - Console.WriteLine("No input specified; use -h for help"); - return; - } - - if (string.IsNullOrEmpty(options.OutputDir)) - { - Console.WriteLine("No target dir specified; use -h for help"); - return; - } - - if (!Directory.Exists(options.OutputDir)) - Directory.CreateDirectory(options.OutputDir); - - RewriteGlobalContext rewriteContext; - IIl2CppMetadataAccess gameAssemblies; - IMetadataAccess unityAssemblies; - - using (new TimingCookie("Reading assemblies")) - { - gameAssemblies = new AssemblyMetadataAccess(options.Source); - } - - if (!string.IsNullOrEmpty(options.UnityBaseLibsDir)) - using (new TimingCookie("Reading unity assemblies")) - { - unityAssemblies = new AssemblyMetadataAccess(Directory.EnumerateFiles(options.UnityBaseLibsDir, "*.dll")); - } - else - unityAssemblies = NullMetadataAccess.Instance; - - using (new TimingCookie("Creating rewrite assemblies")) - { - rewriteContext = new RewriteGlobalContext(options, gameAssemblies, unityAssemblies); - } - - using (new TimingCookie("Computing renames")) - { - Pass05CreateRenameGroups.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating typedefs")) - { - Pass10CreateTypedefs.DoPass(rewriteContext); - } - - using (new TimingCookie("Computing struct blittability")) - { - Pass11ComputeTypeSpecifics.DoPass(rewriteContext); - } - - using (new TimingCookie("Filling typedefs")) - { - Pass12FillTypedefs.DoPass(rewriteContext); - } - - using (new TimingCookie("Filling generic constraints")) - { - Pass13FillGenericConstraints.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating members")) - { - Pass15GenerateMemberContexts.DoPass(rewriteContext); - } - - using (new TimingCookie("Scanning method cross-references")) - { - Pass16ScanMethodRefs.DoPass(rewriteContext, options); - } - - using (new TimingCookie("Finalizing method declarations")) - { - Pass18FinalizeMethodContexts.DoPass(rewriteContext); - } - - Logger.Instance.LogInformation("{DeadMethodsCount} total potentially dead methods", Pass18FinalizeMethodContexts.TotalPotentiallyDeadMethods); - - using (new TimingCookie("Filling method parameters")) - { - Pass19CopyMethodParameters.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating static constructors")) - { - Pass20GenerateStaticConstructors.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating value type fields")) - { - Pass21GenerateValueTypeFields.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating enums")) - { - Pass22GenerateEnums.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating IntPtr constructors")) - { - Pass23GeneratePointerConstructors.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating non-blittable struct constructors")) - { - Pass25GenerateNonBlittableValueTypeDefaultCtors.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating generic method static constructors")) - { - Pass30GenerateGenericMethodStoreConstructors.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating field accessors")) - { - Pass40GenerateFieldAccessors.DoPass(rewriteContext); - } - - using (new TimingCookie("Filling methods")) - { - Pass50GenerateMethods.DoPass(rewriteContext); - } - - using (new TimingCookie("Generating implicit conversions")) - { - Pass60AddImplicitConversions.DoPass(rewriteContext); - } - - using (new TimingCookie("Implementing awaiters")) - { - Pass61ImplementAwaiters.DoPass(rewriteContext); - } - - using (new TimingCookie("Creating properties")) - { - Pass70GenerateProperties.DoPass(rewriteContext); - } - - if (options.UnityBaseLibsDir != null) - { - using (new TimingCookie("Unstripping types")) - { - Pass79UnstripTypes.DoPass(rewriteContext); - } - - using (new TimingCookie("Unstripping fields")) - { - Pass80UnstripFields.DoPass(rewriteContext); - } - - using (new TimingCookie("Unstripping methods")) - { - Pass80UnstripMethods.DoPass(rewriteContext); - } - - using (new TimingCookie("Unstripping method bodies")) - { - Pass81FillUnstrippedMethodBodies.DoPass(rewriteContext); - } - } - else - { - Logger.Instance.LogWarning("Not performing unstripping as unity libs are not specified"); - } - - // Breaks .net runtime - //using (new TimingCookie("Generating forwarded types")) - //{ - // Pass89GenerateForwarders.DoPass(rewriteContext); - //} - - using (new TimingCookie("Writing xref cache")) - { - Pass89GenerateMethodXrefCache.DoPass(rewriteContext, options); - } - - using (new TimingCookie("Writing assemblies")) - { - Pass90WriteToDisk.DoPass(rewriteContext, options); - } - - using (new TimingCookie("Writing method pointer map")) - { - Pass91GenerateMethodPointerMap.DoPass(rewriteContext, options); - } - - using (new TimingCookie("Clearing static data")) - { - Pass16ScanMethodRefs.MapOfCallers = new Dictionary>(); - Pass16ScanMethodRefs.NonDeadMethods = []; - } - - Logger.Instance.LogInformation("Done!"); - - rewriteContext.Dispose(); - } - - public void Dispose() { } -} diff --git a/Il2CppInterop.Generator/StackTypes/DoubleStackType.cs b/Il2CppInterop.Generator/StackTypes/DoubleStackType.cs new file mode 100644 index 00000000..1db5959e --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/DoubleStackType.cs @@ -0,0 +1,10 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class DoubleStackType : StackType +{ + public static DoubleStackType Instance { get; } = new(); + private DoubleStackType() + { + } + public override string ToString() => "double"; +} diff --git a/Il2CppInterop.Generator/StackTypes/ExactStackType.cs b/Il2CppInterop.Generator/StackTypes/ExactStackType.cs new file mode 100644 index 00000000..4ff1bad4 --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/ExactStackType.cs @@ -0,0 +1,18 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class ExactStackType(TypeAnalysisContext Type) : StackType +{ + public bool Equals(ExactStackType? other) + { + return TypeAnalysisContextEqualityComparer.Instance.Equals(Type, other?.Type); + } + + public override int GetHashCode() + { + return TypeAnalysisContextEqualityComparer.Instance.GetHashCode(Type); + } + + public override string ToString() => Type.FullName; +} diff --git a/Il2CppInterop.Generator/StackTypes/IncompatibleStackType.cs b/Il2CppInterop.Generator/StackTypes/IncompatibleStackType.cs new file mode 100644 index 00000000..f0e40e81 --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/IncompatibleStackType.cs @@ -0,0 +1,11 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class IncompatibleStackType : StackType +{ + public static IncompatibleStackType Instance { get; } = new(); + private IncompatibleStackType() + { + } + + public override string ToString() => "incompatible"; +} diff --git a/Il2CppInterop.Generator/StackTypes/IntegerStackType.cs b/Il2CppInterop.Generator/StackTypes/IntegerStackType.cs new file mode 100644 index 00000000..2c04cf73 --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/IntegerStackType.cs @@ -0,0 +1,5 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public abstract record class IntegerStackType : StackType +{ +} diff --git a/Il2CppInterop.Generator/StackTypes/IntegerStackType32.cs b/Il2CppInterop.Generator/StackTypes/IntegerStackType32.cs new file mode 100644 index 00000000..41723be8 --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/IntegerStackType32.cs @@ -0,0 +1,11 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class IntegerStackType32 : IntegerStackType +{ + public static IntegerStackType32 Instance { get; } = new(); + private IntegerStackType32() + { + } + + public override string ToString() => "int"; +} diff --git a/Il2CppInterop.Generator/StackTypes/IntegerStackType64.cs b/Il2CppInterop.Generator/StackTypes/IntegerStackType64.cs new file mode 100644 index 00000000..54a8f17c --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/IntegerStackType64.cs @@ -0,0 +1,11 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class IntegerStackType64 : IntegerStackType +{ + public static IntegerStackType64 Instance { get; } = new(); + private IntegerStackType64() + { + } + + public override string ToString() => "long"; +} diff --git a/Il2CppInterop.Generator/StackTypes/IntegerStackTypeNative.cs b/Il2CppInterop.Generator/StackTypes/IntegerStackTypeNative.cs new file mode 100644 index 00000000..53bcb28c --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/IntegerStackTypeNative.cs @@ -0,0 +1,11 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class IntegerStackTypeNative : IntegerStackType +{ + public static IntegerStackTypeNative Instance { get; } = new(); + private IntegerStackTypeNative() + { + } + + public override string ToString() => "nint"; +} diff --git a/Il2CppInterop.Generator/StackTypes/SingleStackType.cs b/Il2CppInterop.Generator/StackTypes/SingleStackType.cs new file mode 100644 index 00000000..05bd90c6 --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/SingleStackType.cs @@ -0,0 +1,10 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class SingleStackType : StackType +{ + public static SingleStackType Instance { get; } = new(); + private SingleStackType() + { + } + public override string ToString() => "float"; +} diff --git a/Il2CppInterop.Generator/StackTypes/StackType.cs b/Il2CppInterop.Generator/StackTypes/StackType.cs new file mode 100644 index 00000000..5db46122 --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/StackType.cs @@ -0,0 +1,48 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public abstract record class StackType +{ + public static StackType Merge(StackType a, StackType b) + { + if (a is IncompatibleStackType || b is IncompatibleStackType) + { + return IncompatibleStackType.Instance; + } + if (a is UnknownStackType) + { + return b; + } + if (b is UnknownStackType) + { + return a; + } + if (EqualityComparer.Default.Equals(a, b)) + { + return a; + } + + // Could be improved, but good enough for now. + return IncompatibleStackType.Instance; + } + + public static StackType MergeForMathOperation(StackType a, StackType b) + { + if (a is IntegerStackType64 && b is IntegerStackType) + { + return IntegerStackType64.Instance; + } + if (b is IntegerStackType64 && a is IntegerStackType) + { + return IntegerStackType64.Instance; + } + if (a is IntegerStackTypeNative && b is IntegerStackType32) + { + return IntegerStackTypeNative.Instance; + } + if (b is IntegerStackTypeNative && a is IntegerStackType32) + { + return IntegerStackTypeNative.Instance; + } + return Merge(a, b); + } +} diff --git a/Il2CppInterop.Generator/StackTypes/UnknownStackType.cs b/Il2CppInterop.Generator/StackTypes/UnknownStackType.cs new file mode 100644 index 00000000..a6e6e3f0 --- /dev/null +++ b/Il2CppInterop.Generator/StackTypes/UnknownStackType.cs @@ -0,0 +1,11 @@ +namespace Il2CppInterop.Generator.StackTypes; + +public sealed record class UnknownStackType : StackType +{ + public static UnknownStackType Instance { get; } = new(); + private UnknownStackType() + { + } + + public override string ToString() => "unknown"; +} diff --git a/Il2CppInterop.Generator/StaticConstructorProcessingLayer.cs b/Il2CppInterop.Generator/StaticConstructorProcessingLayer.cs new file mode 100644 index 00000000..dae14370 --- /dev/null +++ b/Il2CppInterop.Generator/StaticConstructorProcessingLayer.cs @@ -0,0 +1,50 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; + +namespace Il2CppInterop.Generator; + +public class StaticConstructorProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Static Constructor Processor"; + public override string Id => "static_constructor_processor"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + foreach (var type in appContext.AllTypes) + { + var instructions = type.StaticConstructorInstructions; + if (instructions is null or { Count: 0 }) + continue; + + for (var i = type.Methods.Count - 1; i >= 0; i--) + { + if (type.Methods[i].IsStaticConstructor) + { + Debug.Fail($"Type {type.FullName} already has a static constructor defined. It should have been renamed in an earlier processing layer."); + } + } + + // Add a final instruction to return from the static constructor. + instructions.Add(new Instruction(CilOpCodes.Ret)); + + var staticConstructor = new InjectedMethodAnalysisContext( + type, + ".cctor", + type.AppContext.SystemTypes.SystemVoidType, + MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + []) + { + IsInjected = true + }; + type.Methods.Add(staticConstructor); + + var nativeMethodBody = new NativeMethodBody() + { + Instructions = instructions, + }; + staticConstructor.PutExtraData(nativeMethodBody); + } + } +} diff --git a/Il2CppInterop.Generator/SystemInterfaceProcessingLayer.cs b/Il2CppInterop.Generator/SystemInterfaceProcessingLayer.cs new file mode 100644 index 00000000..d767bb36 --- /dev/null +++ b/Il2CppInterop.Generator/SystemInterfaceProcessingLayer.cs @@ -0,0 +1,314 @@ +using System.Diagnostics; +using System.Reflection; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Conversions; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.Visitors; + +namespace Il2CppInterop.Generator; + +/// +/// This processing layer finds matching interfaces in mscorlib and Il2Cppmscorlib and implements +/// the mscorlib interfaces on the Il2Cppmscorlib interfaces, forwarding calls to the existing Il2Cppmscorlib methods. +/// This allows user code to use the normal .NET interfaces and have them work with the Il2Cpp types. +/// +public sealed class SystemInterfaceProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "System Interface Implementations"; + public override string Id => "system_interface_implementations"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + // The reason we inject some new interfaces instead of just adding methods to the existing Il2Cpp interfaces is partially because + // `Il2CppSystem.Runtime.CompilerServices.ICriticalNotifyCompletion` does not always inherit from + // `Il2CppSystem.Runtime.CompilerServices.INotifyCompletion`, even though `System.Runtime.CompilerServices.ICriticalNotifyCompletion` + // always inherits from `System.Runtime.CompilerServices.INotifyCompletion`. + // Without these injected interfaces, types that implement `Il2CppSystem.Runtime.CompilerServices.ICriticalNotifyCompletion` + // cause a TypeLoadException if they do not also implement `Il2CppSystem.Runtime.CompilerServices.INotifyCompletion`. + // The TypeLoadException would come from `System.Runtime.CompilerServices.INotifyCompletion::OnCompleted` not being implemented. + + // Injected interfaces circumvent this issue by: + // * Recreating the entire interface hierarchy of the system interfaces in a new namespace (`Il2CppInterop.SystemInterfaces`) + // * Implementing the system interfaces on the injected interfaces + // * Making the Il2Cpp interfaces inherit from the injected interfaces instead of the system interfaces + + var pairs = FindPairs(appContext); + + // Create interfaces in Il2CppInterop.SystemInterfaces + var systemToInjectedMap = new Dictionary(TypeAnalysisContextEqualityComparer.Instance); + foreach ((_, var systemInterface) in pairs) + { + var injectedInterface = CreateInjectedInterface(systemInterface); + AddPairToMap(systemToInjectedMap, systemInterface, injectedInterface); + } + + // Find any missing base interfaces + { + for (var i = 0; i < pairs.Count; i++) + { + (_, var systemInterface) = pairs[i]; + foreach (var systemBaseInterface in systemInterface.InterfaceContexts.Select(RemoveTypeArgumentsIfPresent)) + { + if (!systemToInjectedMap.ContainsKey(systemBaseInterface)) + { + var injectedBaseInterface = CreateInjectedInterface(systemBaseInterface); + AddPairToMap(systemToInjectedMap, systemBaseInterface, injectedBaseInterface); + pairs.Add((null, systemBaseInterface)); + } + } + } + } + + // Assign inheritance to the injected interfaces + { + var visitor = new TypeReplacementVisitor(systemToInjectedMap); + foreach ((_, var systemInterface) in pairs) + { + var injectedInterface = systemToInjectedMap[systemInterface]; + foreach (var systemBaseInterface in systemInterface.InterfaceContexts) + { + injectedInterface.InterfaceContexts.Add(visitor.Replace(systemBaseInterface)); + } + } + } + + foreach (var (il2CppInterface, systemInterface) in pairs) + { + ImplementInterface(il2CppInterface, systemToInjectedMap[systemInterface], systemInterface); + } + } + + private static TypeAnalysisContext RemoveTypeArgumentsIfPresent(TypeAnalysisContext type) => type is GenericInstanceTypeAnalysisContext genericInstance + ? genericInstance.GenericType + : type; + + private static InjectedTypeAnalysisContext CreateInjectedInterface(TypeAnalysisContext systemInterface) + { + var injectedInterface = systemInterface.AppContext.Il2CppMscorlib.InjectType( + "Il2CppInterop.SystemInterfaces", + systemInterface.Name, + null, + TypeAttributes.NotPublic | TypeAttributes.Interface | TypeAttributes.Abstract); + injectedInterface.CopyGenericParameters(systemInterface, true); + injectedInterface.InterfaceContexts.Add(systemInterface.MaybeMakeGenericInstanceType(injectedInterface.GenericParameters)); + return injectedInterface; + } + + private static void AddPairToMap(Dictionary systemToInjectedMap, TypeAnalysisContext systemInterface, InjectedTypeAnalysisContext injectedInterface) + { + systemToInjectedMap[systemInterface] = injectedInterface; + + // Add generic parameters to the dictionary too + for (var i = 0; i < systemInterface.GenericParameters.Count; i++) + { + systemToInjectedMap[systemInterface.GenericParameters[i]] = injectedInterface.GenericParameters[i]; + } + } + + private static List<(TypeAnalysisContext?, TypeAnalysisContext)> FindPairs(ApplicationAnalysisContext appContext) + { + List<(TypeAnalysisContext?, TypeAnalysisContext)> pairs = []; + foreach (var type in appContext.Il2CppMscorlib.Types) + { + if (type.IsInterface) + { + var systemType = appContext.Mscorlib.GetTypeByFullName(type.DefaultFullName); + if (systemType != null && IsPublic(systemType)) + { + pairs.Add((type, systemType)); + } + } + } + + return pairs; + + static bool IsPublic(TypeAnalysisContext? type) + { + var current = type; + while (current is not null) + { + if (current.Visibility is not TypeAttributes.Public and not TypeAttributes.NestedPublic) + { + return false; + } + current = current.DeclaringType; + } + return true; + } + } + + private static void ImplementInterface(TypeAnalysisContext? il2CppInterface, TypeAnalysisContext injectedInterface, TypeAnalysisContext systemInterface) + { + foreach (var systemMethod in systemInterface.Methods) + { + if (systemMethod.IsStatic) + { + continue; + } + + var attributes = (systemMethod.Attributes & ~(MethodAttributes.Abstract | MethodAttributes.MemberAccessMask)) | MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.NewSlot; + var injectedMethod = new InjectedMethodAnalysisContext(injectedInterface, $"{systemInterface.FullName}.{systemMethod.Name}", systemMethod.ReturnType, attributes, []) + { + IsInjected = true, + }; + injectedInterface.Methods.Add(injectedMethod); + + injectedMethod.CopyGenericParameters(systemMethod, true); + + var visitor = TypeReplacementVisitor.CreateForMethodCopying(systemMethod, injectedMethod); + + injectedMethod.SetDefaultReturnType(visitor.Replace(systemMethod.ReturnType)); + + foreach (var parameter in systemMethod.Parameters) + { + injectedMethod.Parameters.Add(new InjectedParameterAnalysisContext(parameter.Name, visitor.Replace(parameter.ParameterType), parameter.Attributes, parameter.ParameterIndex, injectedMethod)); + } + + injectedMethod.Overrides.Add(systemMethod.MaybeMakeConcreteGeneric(injectedInterface.GenericParameters, injectedMethod.GenericParameters)); + + var il2CppMethod = FindIl2CppMethod(il2CppInterface, systemMethod.Name, injectedMethod, out var returnConversion, out var parameterConversions); + + if (il2CppMethod is null) + { + injectedMethod.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldnull), + new Instruction(CilOpCodes.Throw), + ] + }); + continue; + } + + Debug.Assert(il2CppInterface is not null); + + List instructions = []; + instructions.Add(CilOpCodes.Ldarg_0); + instructions.Add(CilOpCodes.Castclass, il2CppInterface.MaybeMakeGenericInstanceType(injectedInterface.GenericParameters)); + for (var i = 0; i < injectedMethod.Parameters.Count; i++) + { + var parameter = injectedMethod.Parameters[i]; + instructions.Add(CilOpCodes.Ldarg, parameter); + parameterConversions[i].Add(instructions); + } + instructions.Add(CilOpCodes.Callvirt, il2CppMethod.MaybeMakeConcreteGeneric(injectedInterface.GenericParameters, injectedMethod.GenericParameters)); + returnConversion.Add(instructions); + instructions.Add(CilOpCodes.Ret); + + injectedMethod.PutExtraData(new NativeMethodBody() + { + Instructions = instructions + }); + } + + // Make the Il2Cpp interface inherit from the injected interface + if (il2CppInterface is not null) + { + Debug.Assert(il2CppInterface.GenericParameters.Count == injectedInterface.GenericParameters.Count); + il2CppInterface.InterfaceContexts.Add(injectedInterface.MaybeMakeGenericInstanceType(il2CppInterface.GenericParameters)); + } + } + + private static MethodAnalysisContext? FindIl2CppMethod(TypeAnalysisContext? il2CppInterface, string methodName, MethodAnalysisContext injectedMethod, out Conversion returnConversion, out Conversion[] parameterConversions) + { + returnConversion = NullConversion.Instance; + parameterConversions = new Conversion[injectedMethod.Parameters.Count]; + + if (il2CppInterface is null) + { + return null; + } + + // Search in reverse order because the most user-friendly overloads will be last (they always get added to the end of the list). + // This ensures correct behavior for array parameters, which have special handling in the user-friendly overload processing layer. + for (var il2CppMethodIndex = il2CppInterface.Methods.Count - 1; il2CppMethodIndex >= 0; il2CppMethodIndex--) + { + var il2CppMethod = il2CppInterface.Methods[il2CppMethodIndex]; + if (il2CppMethod.Name == methodName && + !il2CppMethod.IsStatic && + il2CppMethod.Parameters.Count == injectedMethod.Parameters.Count && + il2CppMethod.GenericParameters.Count == injectedMethod.GenericParameters.Count && + il2CppMethod.IsVoid == injectedMethod.IsVoid) + { + var visitor = TypeReplacementVisitor.CreateForMethodCopying(il2CppMethod, injectedMethod); + if (!AreTypesEqual(visitor.Replace(il2CppMethod.ReturnType), injectedMethod.ReturnType, out returnConversion)) + { + continue; + } + + var parametersMatch = true; + for (var i = 0; i < il2CppMethod.Parameters.Count; i++) + { + var injectedParameterType = injectedMethod.Parameters[i].ParameterType; + var il2CppParameterType = visitor.Replace(il2CppMethod.Parameters[i].ParameterType); + if (!AreTypesEqual(injectedParameterType, il2CppParameterType, out parameterConversions[i])) + { + parametersMatch = false; + break; + } + } + if (parametersMatch) + { + return il2CppMethod; + } + } + } + return null; + + static bool AreTypesEqual(TypeAnalysisContext from, TypeAnalysisContext to, out Conversion conversion) + { + if (TypeAnalysisContextEqualityComparer.Instance.Equals(from, to)) + { + conversion = NullConversion.Instance; + return true; + } + else if (from.TryGetConversionTo(to, out var conversionMethod) || to.TryGetConversionFrom(from, out conversionMethod)) + { + // An implicit or explicit conversion exists + conversion = new MethodCallConversion(conversionMethod); + return true; + } + else if (to.KnownType is KnownTypeCode.System_Object && !from.IsValueType) + { + // Any reference type can be converted to System.Object + conversion = NullConversion.Instance; + return true; + } + else if (to.KnownType is KnownTypeCode.Il2CppSystem_IObject && from.KnownType is KnownTypeCode.System_Object) + { + // obj as IObject + conversion = new IsInstanceConversion(to); + return true; + } + else if (to.IsInterface && from.IsInterface && to.DefaultFullName == from.DefaultFullName) + { + if (to.Namespace.StartsWith("Il2Cpp", StringComparison.Ordinal)) + { + // System.IX to Il2CppSystem.IX + // Need to cast + conversion = new CastClassConversion(to); + return true; + } + else if (from.Namespace.StartsWith("Il2Cpp", StringComparison.Ordinal)) + { + // Il2CppSystem.IX to System.IX + // Since the Il2Cpp interface implements the System interface, no conversion is needed. + conversion = NullConversion.Instance; + return true; + } + else + { + Debug.Fail($"Unexpected case where two reference types have the same full name but are not considered equal: {from.FullName} and {to.FullName}"); + conversion = NullConversion.Instance; + return false; + } + } + else + { + conversion = NullConversion.Instance; + return false; + } + } + } +} diff --git a/Il2CppInterop.Generator/SystemTypeResolver.cs b/Il2CppInterop.Generator/SystemTypeResolver.cs new file mode 100644 index 00000000..0160dd1a --- /dev/null +++ b/Il2CppInterop.Generator/SystemTypeResolver.cs @@ -0,0 +1,120 @@ +using System.Diagnostics; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public readonly struct SystemTypeResolver +{ + private readonly AssemblyAnalysisContext referencedFrom; + private readonly TypeAnalysisContext? referencingType; + private readonly MethodAnalysisContext? referencingMethod; + + public SystemTypeResolver(AssemblyAnalysisContext referencedFrom) + { + this.referencedFrom = referencedFrom; + } + + public SystemTypeResolver(TypeAnalysisContext referencingType) + { + if (referencingType is ReferencedTypeAnalysisContext) + throw new ArgumentException("Must be a simple type", nameof(referencingType)); + referencedFrom = referencingType.DeclaringAssembly; + this.referencingType = referencingType; + } + + public SystemTypeResolver(MethodAnalysisContext referencingMethod) + { + if (referencingMethod is ConcreteGenericMethodAnalysisContext) + throw new ArgumentException("Must be a simple method", nameof(referencingMethod)); + referencedFrom = referencingMethod.CustomAttributeAssembly; + referencingType = referencingMethod.DeclaringType; + this.referencingMethod = referencingMethod; + } + public TypeAnalysisContext ResolveOrThrow(Type? type, bool allowGenericInstance = true) + { + return Resolve(type, allowGenericInstance) ?? throw new($"Unable to resolve type {type?.FullName}"); + } + + public TypeAnalysisContext? Resolve(Type? type) => Resolve(type, true); + public TypeAnalysisContext? Resolve(Type? type, bool allowGenericInstance) + { + if (type is null) + return null; + + if (type.IsSZArray) + return Resolve(type.GetElementType())?.MakeSzArrayType(); + + if (type.IsByRef) + return Resolve(type.GetElementType())?.MakeByReferenceType(); + + if (type.IsPointer) + return Resolve(type.GetElementType())?.MakePointerType(); + + if (type.IsArray) + return Resolve(type.GetElementType())?.MakeArrayType(type.GetArrayRank()); + + if (type.IsGenericParameter) + { + if (type.IsGenericTypeParameter) + { + return TryGetGenericParameter(referencingType?.GenericParameters, type.GenericParameterPosition); + } + else + { + Debug.Assert(type.IsGenericMethodParameter); + return TryGetGenericParameter(referencingMethod?.GenericParameters, type.GenericParameterPosition); + } + } + + if (type.IsGenericType && allowGenericInstance) + { + var genericArguments = type.GetGenericArguments().Select(Resolve).ToArray(); + if (genericArguments.Any(x => x is null)) + return null; + + var genericType = type.GetGenericTypeDefinition(); + return Resolve(genericType, false)?.MakeGenericInstanceType(genericArguments!); + } + + if (type.IsFunctionPointer) + throw new NotSupportedException($"Function pointers are not supported: {type.Name}"); + + // Custom modifiers might be possible to support, but probably not necessary + + var assemblyName = type.Assembly.GetName().Name!; + var fullName = type.FullName!; + if (assemblyName == "System.Private.CoreLib") + { + return ResolveSystemType(fullName); + } + else + { + return ResolveSimpleType(assemblyName, fullName); + } + } + + private TypeAnalysisContext? ResolveSystemType(string fullName) + { + TypeAnalysisContext? type = null; + foreach (var assemblyName in MscorlibAssemblyInjectionProcessingLayer.InjectedAssemblies) + { + type = ResolveSimpleType(assemblyName, fullName); + if (type is not null) + break; + } + return type; + } + + private TypeAnalysisContext? ResolveSimpleType(string assemblyName, string fullName) + { + var assembly = referencedFrom.AppContext.GetAssemblyByName(assemblyName); + return assembly?.GetTypeByFullName(fullName); + } + + private static GenericParameterTypeAnalysisContext? TryGetGenericParameter(List? genericParameters, int index) + { + if (genericParameters is null || index < 0 || index >= genericParameters.Count) + return null; + return genericParameters[index]; + } +} diff --git a/Il2CppInterop.Generator/TranslatedMethodBody.cs b/Il2CppInterop.Generator/TranslatedMethodBody.cs new file mode 100644 index 00000000..7fc9c1ee --- /dev/null +++ b/Il2CppInterop.Generator/TranslatedMethodBody.cs @@ -0,0 +1,1227 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Common; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.StackTypes; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.Exceptions; +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace Il2CppInterop.Generator; + +public class TranslatedMethodBody : MethodBodyBase +{ + // Notes: + // + // There are subtle flaws in this implementation, in regards to exception handling. + // Certain instructions (eg `castclass` and `callvirt`) can throw exceptions. + // These exceptions will have System types, not Il2Cpp types. + // + // + + public static bool TryTranslateOriginalMethodBody(MethodAnalysisContext methodContext) + { + if (!methodContext.TryGetExtraData(out OriginalMethodBody? originalMethodBody)) + { + return false; + } + + var appContext = methodContext.AppContext; + + var byReference = appContext.ResolveTypeOrThrow(typeof(ByReference<>)); + var byReference_Constructor = byReference.GetMethodByName(".ctor"); + var byReference_ToPointer = byReference.GetMethodByName(nameof(ByReference<>.ToPointer)); + var byReference_GetValue = byReference.GetMethodByName(nameof(ByReference<>.GetValue)); + + var byReferenceStatic = appContext.ResolveTypeOrThrow(typeof(ByReference)); + var byReferenceStatic_GetValue = byReferenceStatic.GetMethodByName(nameof(ByReference.GetValue)); + var byReferenceStatic_SetValue1 = byReferenceStatic.GetMethodByName(nameof(ByReference.SetValue1)); + var byReferenceStatic_SetValue2 = byReferenceStatic.GetMethodByName(nameof(ByReference.SetValue2)); + + var il2CppTypeHelper = appContext.ResolveTypeOrThrow(typeof(Il2CppType)); + var il2CppTypeHelper_SizeOf = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.SizeOf)); + + Debug.Assert(methodContext.UnsafeImplementationMethod is not null); + var implementationMethod = methodContext.UnsafeImplementationMethod!; + + TypeReplacementVisitor replacementVisitor = TypeReplacementVisitor.Combine(TypeConversionVisitor.Create(methodContext.AppContext), TypeReplacementVisitor.CreateForMethodCopying(methodContext, implementationMethod)); + + var dictionary = originalMethodBody.AnalyzeStackTypes(methodContext, replacementVisitor, true); + + var initializeInstructions = new List(); + var localVariableList = new List(originalMethodBody.LocalVariables.Count); + var localVariableDictionary = new Dictionary(originalMethodBody.LocalVariables.Count); + foreach (var originalLocalVariable in originalMethodBody.LocalVariables) + { + var transferredType = replacementVisitor.Replace(originalLocalVariable.Type); // Convert to Il2Cpp types and swap out generic parameters for the correct ones + var localType = byReference.MakeGenericInstanceType([transferredType]); // All locals are byref + var translatedLocalVariable = new LocalVariable + { + Type = localType, + }; + localVariableList.Add(translatedLocalVariable); + localVariableDictionary.Add(originalLocalVariable, translatedLocalVariable); + + initializeInstructions.Add(CilOpCodes.Call, il2CppTypeHelper_SizeOf.MakeGenericInstanceMethod(transferredType)); + initializeInstructions.Add(CilOpCodes.Conv_U); + initializeInstructions.Add(CilOpCodes.Localloc); + initializeInstructions.Add(CilOpCodes.Newobj, byReference_Constructor.MakeConcreteGeneric([transferredType], [])); + initializeInstructions.Add(CilOpCodes.Stloc, translatedLocalVariable); + } + + var instructionDictionary = new Dictionary(originalMethodBody.Instructions.Count); + foreach (var originalInstruction in originalMethodBody.Instructions) + { + instructionDictionary[originalInstruction] = new Instruction(); + } + + var handlerStarts = originalMethodBody.ExceptionHandlers.Select(eh => eh.HandlerStart).OfType().ToHashSet(); + + var translatedInstructions = new List(originalMethodBody.Instructions.Count); + foreach (var originalInstruction in originalMethodBody.Instructions) + { + var translatedInstruction = instructionDictionary[originalInstruction]; + + if (handlerStarts.Contains(originalInstruction)) + { + // Ensure that the start of the exception handler is a nop, + // so that we can insert any necessary instructions at the beginning of the handler. + translatedInstruction.Code = CilOpCodes.Nop; + translatedInstructions.Add(translatedInstruction); + translatedInstruction = new Instruction(); + } + + translatedInstructions.Add(translatedInstruction); + + var originalCode = originalInstruction.Code; + var originalOperand = originalInstruction.Operand; + + if (originalOperand is null) + { + switch (originalCode.Code) + { + case CilCode.Arglist: + return false; + + case CilCode.Ldlen: + // This is Il2CppArrayBase.Length + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext.ResolveTypeOrThrow(typeof(Il2CppArrayBase)).GetMethodByName($"get_{nameof(Il2CppArrayBase.Length)}"); + } + break; + + case CilCode.Ldelem_I1: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemSByteType]); + } + break; + + case CilCode.Ldelem_I2: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemInt16Type]); + } + break; + + case CilCode.Ldelem_I4: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemInt32Type]); + } + break; + + case CilCode.Ldelem_I8: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemInt64Type]); + } + break; + + case CilCode.Ldelem_U1: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemByteType]); + } + break; + + case CilCode.Ldelem_U2: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemUInt16Type]); + } + break; + + case CilCode.Ldelem_U4: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemUInt32Type]); + } + break; + + case CilCode.Ldelem_I: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemIntPtrType]); + } + break; + + case CilCode.Ldelem_R4: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemSingleType]); + } + break; + + case CilCode.Ldelem_R8: + // This is Il2CppArrayBase.LoadElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemDoubleType]); + } + break; + + case CilCode.Stelem_I1: + // This is Il2CppArrayBase.StoreElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemSByteType]); + } + break; + + case CilCode.Stelem_I2: + // This is Il2CppArrayBase.StoreElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemInt16Type]); + } + break; + + case CilCode.Stelem_I4: + // This is Il2CppArrayBase.StoreElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemInt32Type]); + } + break; + + case CilCode.Stelem_I8: + // This is Il2CppArrayBase.StoreElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemInt64Type]); + } + break; + + case CilCode.Stelem_I: + // This is Il2CppArrayBase.StoreElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemIntPtrType]); + } + break; + + case CilCode.Stelem_R4: + // This is Il2CppArrayBase.StoreElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemSingleType]); + } + break; + + case CilCode.Stelem_R8: + // This is Il2CppArrayBase.StoreElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreElementUnsafe)) + .MakeGenericInstanceMethod([methodContext.AppContext.SystemTypes.SystemDoubleType]); + } + break; + + case CilCode.Ldelem_Ref: + // This is Il2CppArrayBase.LoadReferenceElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.LoadReferenceElementUnsafe)); + } + break; + + case CilCode.Stelem_Ref: + // This is Il2CppArrayBase.StoreReferenceElementUnsafe + { + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayBase)) + .GetMethodByName(nameof(Il2CppArrayBase.StoreReferenceElementUnsafe)); + } + break; + + case >= CilCode.Ldind_I1 and < CilCode.Ldind_Ref: + // This is for by ref and pointers + goto default; + + case CilCode.Ldind_Ref: + // This is for by ref and pointers + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = appContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .GetMethodByName(nameof(GenerationInternals.LoadIndirectReference)); + } + break; + + case CilCode.Stind_Ref: + // This is for by ref and pointers + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = appContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .GetMethodByName(nameof(GenerationInternals.StoreIndirectReference)); + } + break; + + case > CilCode.Stind_Ref and <= CilCode.Stind_R8: + // This is for by ref and pointers + goto default; + + case CilCode.Refanytype: + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = appContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .GetMethodByName(nameof(GenerationInternals.RefAnyType)); + } + break; + + case CilCode.Throw: + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = methodContext.AppContext.ResolveTypeOrThrow(typeof(Il2CppException)).GetMethodByName(nameof(Il2CppException.FromNativeObject)); + translatedInstructions.Add(new Instruction(originalCode)); + } + break; + + case CilCode.Ret: + if (MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, methodContext.ReturnType)) + { + translatedInstruction.Code = CilOpCodes.Nop; + + translatedInstructions.Add(new Instruction(originalCode)); + } + else + { + translatedInstruction.Code = originalCode; + } + break; + + case CilCode.Volatile: + // This op code can be ignored. + translatedInstruction.Code = CilOpCodes.Nop; + break; + + default: + // nop, ldnull, ldarg_0, mul, add, etc. + translatedInstruction.Code = originalCode; + break; + } + } + else if (originalOperand is byte or sbyte or short or ushort or int or uint or long or ulong or float or double or bool or char) + { + if (originalCode == CilOpCodes.Unaligned) + { + // This op code can be ignored. + translatedInstruction.Code = CilOpCodes.Nop; + } + else + { + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = originalOperand; + } + } + else if (originalOperand is string) + { + Debug.Assert(originalCode == CilOpCodes.Ldstr); + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = originalOperand; + + MonoIl2CppConversion.AddMonoToIl2CppStringConversion(translatedInstructions, methodContext.AppContext); + } + else if (originalOperand is IReadOnlyList labels) + { + Debug.Assert(originalCode == CilOpCodes.Switch); + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = ResolveLabels(labels, instructionDictionary); + } + else if (originalOperand is ILabel label) + { + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = ResolveLabel(label, instructionDictionary); + } + else if (originalOperand is This) + { + Debug.Assert(originalCode == CilOpCodes.Ldarg); + + var newParameter = implementationMethod.Parameters[0]; + + var parameterType = (GenericInstanceTypeAnalysisContext)newParameter.ParameterType; + var dataType = parameterType.GenericArguments[0]; + + if (dataType.IsValueType) + { + translatedInstruction.Code = CilOpCodes.Ldarg; + translatedInstruction.Operand = newParameter; + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, parameterType); + } + else + { + translatedInstruction.Code = CilOpCodes.Ldarga; + translatedInstruction.Operand = newParameter; + + translatedInstructions.Add(CilOpCodes.Call, byReference_GetValue.MakeConcreteGeneric(parameterType.GenericArguments, [])); + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, dataType); + } + } + else if (originalOperand is ParameterAnalysisContext parameter) + { + var parameterOffset = implementationMethod.Parameters.Count - methodContext.Parameters.Count; + var newParameter = implementationMethod.Parameters[parameterOffset + parameter.ParameterIndex]; + + var parameterType = (GenericInstanceTypeAnalysisContext)newParameter.ParameterType; + var dataType = parameterType.GenericArguments[0]; + + if (originalCode == CilOpCodes.Ldarg) + { + translatedInstruction.Code = CilOpCodes.Ldarga; + translatedInstruction.Operand = newParameter; + + translatedInstructions.Add(CilOpCodes.Call, byReference_GetValue.MakeConcreteGeneric(parameterType.GenericArguments, [])); + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, dataType); + } + else if (originalCode == CilOpCodes.Starg) + { + if (MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, dataType)) + { + translatedInstruction.Code = CilOpCodes.Nop; + + translatedInstructions.Add(CilOpCodes.Ldarg, newParameter); + } + else + { + translatedInstruction.Code = CilOpCodes.Ldarg; + translatedInstruction.Operand = newParameter; + } + translatedInstructions.Add(CilOpCodes.Call, byReferenceStatic_SetValue2.MakeGenericInstanceMethod(parameterType.GenericArguments)); + } + else if (originalCode == CilOpCodes.Ldarga) + { + translatedInstruction.Code = CilOpCodes.Ldarg; + translatedInstruction.Operand = newParameter; + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, parameterType); + } + else + { + Debug.Fail($"Unexpected CIL code: {originalCode}"); + return false; + } + } + else if (originalOperand is LocalVariable localVariable) + { + var translatedLocalVariable = localVariableDictionary[localVariable]; + + var localType = (GenericInstanceTypeAnalysisContext)translatedLocalVariable.Type; + var dataType = localType.GenericArguments[0]; + + if (originalCode == CilOpCodes.Ldloc) + { + translatedInstruction.Code = CilOpCodes.Ldloca; + translatedInstruction.Operand = translatedLocalVariable; + + translatedInstructions.Add(CilOpCodes.Call, byReference_GetValue.MakeConcreteGeneric(localType.GenericArguments, [])); + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, dataType); + } + else if (originalCode == CilOpCodes.Stloc) + { + if (MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, dataType)) + { + translatedInstruction.Code = CilOpCodes.Nop; + + translatedInstructions.Add(CilOpCodes.Ldloc, translatedLocalVariable); + } + else + { + translatedInstruction.Code = CilOpCodes.Ldloc; + translatedInstruction.Operand = translatedLocalVariable; + } + translatedInstructions.Add(CilOpCodes.Call, byReferenceStatic_SetValue2.MakeGenericInstanceMethod(localType.GenericArguments)); + } + else if (originalCode == CilOpCodes.Ldloca) + { + translatedInstruction.Code = CilOpCodes.Ldloc; + translatedInstruction.Operand = translatedLocalVariable; + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, localType); + } + else + { + Debug.Fail($"Unexpected CIL code: {originalCode}"); + return false; + } + } + else if (originalOperand is TypeAnalysisContext type) + { + var translatedType = replacementVisitor.Replace(type); + + switch (originalCode.Code) + { + case CilCode.Castclass or CilCode.Isinst: + { + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = translatedType; + } + break; + case CilCode.Constrained: + return false; + case CilCode.Initobj: + { + // References on the stack are always void*. + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.InitializeObject)).MakeGenericInstanceMethod(translatedType); + } + break; + case CilCode.Cpobj: + { + // References on the stack are always void*. + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.CopyObject)).MakeGenericInstanceMethod(translatedType); + } + break; + case CilCode.Ldobj: + { + // References on the stack are always void*. + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.ReadFromPointer)).MakeGenericInstanceMethod(translatedType); + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, translatedType); + } + break; + case CilCode.Stobj: + { + var storeMethod = il2CppTypeHelper.GetMethodByName(nameof(Il2CppType.StoreObject)).MakeGenericInstanceMethod(translatedType); + if (MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, translatedType)) + { + translatedInstruction.Code = CilOpCodes.Nop; + + translatedInstructions.Add(CilOpCodes.Call, storeMethod); + } + else + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = storeMethod; + } + } + break; + case CilCode.Sizeof: + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = il2CppTypeHelper_SizeOf.MakeGenericInstanceMethod(translatedType); + } + break; + case CilCode.Box: + if (MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, translatedType)) + { + // Conversion happens before boxing + translatedInstruction.Code = CilOpCodes.Nop; + + // Since this is an Il2Cpp primitive type, we can use the normal box instruction with the translated type. + translatedInstructions.Add(originalCode, translatedType); + } + else + { + // This ensures that we properly box Nullable<> + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = appContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .GetMethodByName(nameof(GenerationInternals.Box)) + .MakeGenericInstanceMethod(translatedType); + } + break; + case CilCode.Unbox: + { + // The unbox instruction extracts a pointer to the value type data from the boxed object. + // To unstrip this, add `byte* backingData = stackalloc byte[Il2CppType.SizeOf()];` to the beginning of the method. + // Replace this instruction with the following: + // * unbox.any translatedType + // * ldloc backingData + // * call Il2CppType.WriteToPointer<> + // * ldloc backingData + return false; + } + case CilCode.Unbox_Any: + if (MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, translatedType)) + { + // Since this is an Il2Cpp primitive type, we can use the normal unbox.any instruction with the translated type. + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = translatedType; + } + else + { + // This ensures that we properly unbox Nullable<> + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = appContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .GetMethodByName(nameof(GenerationInternals.Unbox)) + .MakeGenericInstanceMethod(translatedType); + } + break; + case CilCode.Ldtoken: + { + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = translatedType; + + translatedInstructions.Add(new Instruction(CilOpCodes.Call, methodContext.AppContext.SystemTypes.SystemTypeType.GetMethodByName(nameof(Type.GetTypeFromHandle)))); + + var managedTypeToIl2CppTypeMethod = methodContext.AppContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .Methods.First(m => m.Name == nameof(GenerationInternals.ManagedTypeToIl2CppType) && m.Parameters.Count == 1); + + translatedInstructions.Add(new Instruction(CilOpCodes.Call, managedTypeToIl2CppTypeMethod)); + + var getTypeHandleMethod = methodContext.AppContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.Type").GetMethodByName("get_TypeHandle"); + + translatedInstructions.Add(new Instruction(CilOpCodes.Callvirt, getTypeHandleMethod)); + } + break; + case CilCode.Newarr: + { + translatedInstruction.Code = CilOpCodes.Newobj; + translatedInstruction.Operand = appContext.ResolveTypeOrThrow(typeof(Il2CppArrayRank1<>)) + .Methods + .Single(m => m.IsInstanceConstructor && m.Parameters.Count == 1 && m.Parameters[0].ParameterType == appContext.SystemTypes.SystemInt32Type) + .MakeConcreteGeneric([translatedType], []); + } + break; + case CilCode.Ldelem: + { + var genericMethod = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayRank1<>)) + .GetMethodByName("get_Item"); + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = new ConcreteGenericMethodAnalysisContext(genericMethod, [translatedType], []); + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, translatedType); + } + break; + case CilCode.Stelem: + { + translatedInstruction.Code = CilOpCodes.Nop; + + var genericMethod = methodContext.AppContext + .ResolveTypeOrThrow(typeof(Il2CppArrayRank1<>)) + .GetMethodByName("set_Item"); + var concreteGenericMethod = new ConcreteGenericMethodAnalysisContext(genericMethod, [translatedType], []); + MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, translatedType); + + translatedInstructions.Add(new Instruction(CilOpCodes.Callvirt, concreteGenericMethod)); + } + break; + case CilCode.Ldelema: + { + var getElementAddress = appContext.ResolveTypeOrThrow(typeof(Il2CppArrayRank1<>)) + .GetMethodByName(nameof(Il2CppArrayRank1<>.GetElementAddress)) + .MakeConcreteGeneric([translatedType], []); + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = getElementAddress; + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, getElementAddress.ReturnType); + } + break; + case CilCode.Mkrefany: + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = appContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .GetMethodByName(nameof(GenerationInternals.MakeRefAny)) + .MakeGenericInstanceMethod(translatedType); + } + break; + case CilCode.Refanyval: + { + translatedInstruction.Code = CilOpCodes.Call; + translatedInstruction.Operand = appContext + .ResolveTypeOrThrow(typeof(GenerationInternals)) + .GetMethodByName(nameof(GenerationInternals.RefAnyValue)) + .MakeGenericInstanceMethod(translatedType); + } + break; + default: + Debug.Fail($"Unexpected CIL code: {originalCode}"); + return false; + } + } + else if (originalOperand is MethodAnalysisContext method) + { + MethodAnalysisContext baseMethod; + TypeAnalysisContext[] typeGenericArguments; + TypeAnalysisContext[] methodGenericArguments; + if (method is ConcreteGenericMethodAnalysisContext concreteGeneric) + { + baseMethod = concreteGeneric.BaseMethodContext; + if (concreteGeneric.TypeGenericParameters.Count > 0) + { + typeGenericArguments = new TypeAnalysisContext[concreteGeneric.TypeGenericParameters.Count]; + for (var i = 0; i < concreteGeneric.TypeGenericParameters.Count; i++) + { + typeGenericArguments[i] = replacementVisitor.Replace(concreteGeneric.TypeGenericParameters[i]); + } + } + else + { + typeGenericArguments = []; + } + if (concreteGeneric.MethodGenericParameters.Count > 0) + { + methodGenericArguments = new TypeAnalysisContext[concreteGeneric.MethodGenericParameters.Count]; + for (var i = 0; i < concreteGeneric.MethodGenericParameters.Count; i++) + { + methodGenericArguments[i] = replacementVisitor.Replace(concreteGeneric.MethodGenericParameters[i]); + } + } + else + { + methodGenericArguments = []; + } + } + else + { + baseMethod = method; + typeGenericArguments = []; + methodGenericArguments = []; + } + if (originalCode.Code is CilCode.Call or CilCode.Callvirt or CilCode.Newobj) + { + MethodAnalysisContext targetMethod; + CilOpCode targetOpCode; + if (originalCode.Code is CilCode.Call && baseMethod.InterfaceRedirectMethod is not null) + { + if (baseMethod.IsFinal) + { + targetMethod = baseMethod.InterfaceRedirectMethod!; + targetOpCode = CilOpCodes.Callvirt; + } + else if (baseMethod.UnsafeInvokeMethod is not null) + { + targetMethod = baseMethod.UnsafeInvokeMethod!.MaybeMakeConcreteGeneric(typeGenericArguments, methodGenericArguments); + Debug.Assert(targetMethod.IsStatic); + Debug.Assert(method.Parameters.Count == targetMethod.Parameters.Count - 1); + Debug.Assert(baseMethod.InterfaceRedirectMethod!.DeclaringType == targetMethod.Parameters[0].ParameterType); + targetOpCode = CilOpCodes.Call; + } + else + { + Debug.Fail("This should not happen"); + return false; + } + } + else if (originalCode.Code is CilCode.Call && baseMethod.UnsafeInvokeMethod is not null) + { + targetMethod = baseMethod.UnsafeInvokeMethod!.MaybeMakeConcreteGeneric(typeGenericArguments, methodGenericArguments); + Debug.Assert(targetMethod.IsStatic); + Debug.Assert(method.Parameters.Count == targetMethod.Parameters.Count - 1); + targetOpCode = CilOpCodes.Call; + } + else + { + if (originalCode.Code is CilCode.Callvirt && baseMethod.InterfaceRedirectMethod is not null) + { + // Interface redirects are only used for IObject, IValueType, and IEnum + // Which have no generic methods + targetMethod = baseMethod.InterfaceRedirectMethod!; + } + else + { + targetMethod = baseMethod.MaybeMakeConcreteGeneric(typeGenericArguments, methodGenericArguments); + } + targetOpCode = originalCode; + } + + if (targetMethod.Parameters.Count > 0) + { + translatedInstruction.Code = CilOpCodes.Nop; + + var temporaryVariables = new LocalVariable[targetMethod.Parameters.Count]; + for (var i = targetMethod.Parameters.Count - 1; i >= 0; i--) // Order matters + { + var methodParameter = targetMethod.Parameters[i]; + MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, methodParameter.ParameterType); + var temporaryVariable = new LocalVariable + { + Type = methodParameter.ParameterType, + }; + translatedInstructions.Add(CilOpCodes.Stloc, temporaryVariable); + + temporaryVariables[i] = temporaryVariable; + localVariableList.Add(temporaryVariable); + } + + foreach (var temporaryVariable in temporaryVariables) + { + translatedInstructions.Add(CilOpCodes.Ldloc, temporaryVariable); + } + + translatedInstructions.Add(targetOpCode, targetMethod); + } + else + { + translatedInstruction.Code = targetOpCode; + translatedInstruction.Operand = targetMethod; + } + + var returnType = originalCode == CilOpCodes.Newobj ? targetMethod.DeclaringType! : targetMethod.ReturnType; + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, returnType); + } + else if (originalCode == CilOpCodes.Ldtoken) + { + // Not sure this can happen in normal CIL code, but we check for it just in case. + return false; + } + else if (originalCode == CilOpCodes.Ldftn || originalCode == CilOpCodes.Ldvirtftn) + { + return false; + } + else if (originalCode == CilOpCodes.Jmp) + { + // This shouldn't happen in normal CIL code, but we check for it just in case. + return false; + } + else + { + Debug.Fail($"Unexpected CIL code: {originalCode}"); + return false; + } + } + else if (originalOperand is FieldAnalysisContext field) + { + FieldAnalysisContext baseField; + TypeAnalysisContext[] declaringTypeArguments; + if (field is ConcreteGenericFieldAnalysisContext concreteGenericField) + { + baseField = concreteGenericField.BaseFieldContext; + declaringTypeArguments = replacementVisitor.Replace(((GenericInstanceTypeAnalysisContext)field.DeclaringType).GenericArguments).ToArray(); + } + else + { + baseField = field; + declaringTypeArguments = []; + } + + if (originalCode == CilOpCodes.Ldfld || originalCode == CilOpCodes.Ldsfld) + { + // Load field value + + // ldfld can accept either a ref or a value, so we use stack analysis to determine. + bool operandIsReferenceType; + if (originalCode == CilOpCodes.Ldfld && baseField is { DeclaringType.IsValueType: true }) + { + var stack = dictionary[originalInstruction]; + var operandStackType = stack[^1]; + if (operandStackType is ExactStackType { Type: { } objectType }) + { + operandIsReferenceType = IsByReferenceType(objectType); + } + else if (operandStackType is IntegerStackTypeNative) + { + operandIsReferenceType = true; + } + else + { + return false; + } + } + else + { + operandIsReferenceType = true; + } + + var translatedField = baseField.MaybeMakeConcreteGeneric(declaringTypeArguments); + var fieldType = translatedField.FieldType; + + if (baseField.PropertyAccessor is not null) + { + var accessorMethod = baseField.PropertyAccessor!.Getter!.MaybeMakeConcreteGeneric(declaringTypeArguments, []); + fieldType = accessorMethod.ReturnType; + if (operandIsReferenceType) + { + translatedInstruction.Code = originalCode == CilOpCodes.Ldfld && baseField is { DeclaringType.IsValueType: false } ? CilOpCodes.Callvirt : CilOpCodes.Call; + translatedInstruction.Operand = accessorMethod; + } + else + { + Debug.Assert(accessorMethod.DeclaringType is not null); + LocalVariable temporaryLocal = new(accessorMethod.DeclaringType); + localVariableList.Add(temporaryLocal); + + translatedInstruction.Code = CilOpCodes.Stloc; + translatedInstruction.Operand = temporaryLocal; + + translatedInstructions.Add(CilOpCodes.Ldloca, temporaryLocal); + translatedInstructions.Add(CilOpCodes.Call, accessorMethod); + } + } + else if (baseField.DeclaringType.IsIl2CppPrimitive) + { + Debug.Assert(!baseField.IsStatic, "There should be no static fields."); + Debug.Assert(fieldType.DeclaringAssembly == appContext.Mscorlib); + + if (!operandIsReferenceType) + { + Debug.Fail("Operand was unexpectedly not a reference type."); + return false; + } + + translatedInstruction.Code = CilOpCodes.Ldobj; + translatedInstruction.Operand = fieldType; + } + else if (baseField.DeclaringType.Fields.Contains(baseField)) + { + Debug.Assert(!baseField.IsStatic, "There should be no static fields."); + Debug.Assert(baseField is { DeclaringType.IsValueType: true }, "Only value types should have instance fields."); + Debug.Assert(baseField.FieldAddressAccessor is not null); + + if (operandIsReferenceType) + { + translatedInstruction.Code = CilOpCodes.Nop; + MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, byReference.MakeGenericInstanceType([translatedField.DeclaringType])); + translatedInstructions.Add(CilOpCodes.Call, baseField.FieldAddressAccessor!.MaybeMakeConcreteGeneric(declaringTypeArguments, [])); + translatedInstructions.Add(CilOpCodes.Call, byReferenceStatic_GetValue.MakeGenericInstanceMethod(fieldType)); + } + else + { + translatedInstruction.Code = originalCode; + translatedInstruction.Operand = translatedField; + } + } + else + { + Debug.Fail("This should not occur."); + return false; + } + + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, fieldType); + } + else if (originalCode == CilOpCodes.Stfld || originalCode == CilOpCodes.Stsfld) + { + // Store field value + + translatedInstruction.Code = CilOpCodes.Nop; + + var translatedField = baseField.MaybeMakeConcreteGeneric(declaringTypeArguments); + var fieldType = translatedField.FieldType; + + var accessorMethod = baseField.PropertyAccessor?.Setter!.MaybeMakeConcreteGeneric(declaringTypeArguments, []); + + if (accessorMethod is not null) + { + fieldType = accessorMethod.Parameters[^1].ParameterType; + } + + MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, fieldType); + + if (accessorMethod is not null) + { + translatedInstructions.Add(new Instruction(originalCode == CilOpCodes.Stfld && baseField is { DeclaringType.IsValueType: false } ? CilOpCodes.Callvirt : CilOpCodes.Call, accessorMethod)); + } + else if (baseField.DeclaringType.IsIl2CppPrimitive) + { + Debug.Assert(!baseField.IsStatic, "There should be no static fields."); + Debug.Assert(baseField.FieldType.DeclaringAssembly == appContext.Mscorlib); + + Debug.Fail("Unexpected store to a primitive field because the primitive fields are readonly."); + return false; + } + else if (baseField.DeclaringType.Fields.Contains(baseField)) + { + Debug.Assert(!baseField.IsStatic, "There should be no static fields."); + Debug.Assert(baseField is { DeclaringType.IsValueType: true }, "Only value types should have instance fields."); + Debug.Assert(baseField.FieldAddressAccessor is not null); + + var fieldAddressAccessor = baseField.FieldAddressAccessor!.MaybeMakeConcreteGeneric(declaringTypeArguments, []); + Debug.Assert(TypeAnalysisContextEqualityComparer.Instance.Equals(fieldType, ((GenericInstanceTypeAnalysisContext)fieldAddressAccessor.ReturnType).GenericArguments[0])); + + LocalVariable temporaryLocal = new(fieldType); + localVariableList.Add(temporaryLocal); + + translatedInstructions.Add(CilOpCodes.Stloc, temporaryLocal); + MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, byReference.MakeGenericInstanceType([translatedField.DeclaringType])); + translatedInstructions.Add(CilOpCodes.Call, fieldAddressAccessor); + translatedInstructions.Add(CilOpCodes.Ldloc, temporaryLocal); + translatedInstructions.Add(CilOpCodes.Call, byReferenceStatic_SetValue1.MakeGenericInstanceMethod(fieldType)); + } + else + { + Debug.Fail("This should not occur."); + return false; + } + } + else if (originalCode == CilOpCodes.Ldflda) + { + // Load field address + Debug.Assert(!baseField.IsStatic); + Debug.Assert(baseField.FieldAddressAccessor is not null); + + var translatedField = baseField.MaybeMakeConcreteGeneric(declaringTypeArguments); + var fieldAddressAccessor = baseField.FieldAddressAccessor!.MaybeMakeConcreteGeneric(declaringTypeArguments, []); + + translatedInstruction.Code = CilOpCodes.Nop; + if (baseField.DeclaringType is { IsValueType: true }) + { + MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, byReference.MakeGenericInstanceType([translatedField.DeclaringType])); + } + translatedInstructions.Add(CilOpCodes.Call, fieldAddressAccessor); + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, fieldAddressAccessor.ReturnType); + } + else if (originalCode == CilOpCodes.Ldsflda) + { + // Load static field address + // Not implemented yet + return false; + } + else if (originalCode == CilOpCodes.Ldtoken) + { + // This can happen in array initializers that have constant data. + return false; + } + else + { + Debug.Fail($"Unexpected CIL code: {originalCode}"); + return false; + } + } + else if (originalOperand is MultiDimensionalArrayMethod multiDimensionalArrayMethod) + { + var arrayType = multiDimensionalArrayMethod.Rank switch + { + 2 => appContext.ResolveTypeOrThrow(typeof(Il2CppArrayRank2<>)), + 3 => appContext.ResolveTypeOrThrow(typeof(Il2CppArrayRank3<>)), + 4 => appContext.ResolveTypeOrThrow(typeof(Il2CppArrayRank4<>)), + 5 => appContext.ResolveTypeOrThrow(typeof(Il2CppArrayRank5<>)), + _ => null, + }; + if (arrayType is null) + return false; + + switch (multiDimensionalArrayMethod.MethodType) + { + case MultiDimensionalArrayMethodType.Get: + { + var genericMethod = arrayType.Methods.Single(m => m.Name == "get_Item" && m.Parameters.Count == multiDimensionalArrayMethod.Rank); + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = genericMethod.MakeConcreteGeneric([multiDimensionalArrayMethod.ArrayType.ElementType], []); + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, genericMethod.ReturnType); + } + break; + case MultiDimensionalArrayMethodType.Set: + { + var genericMethod = arrayType.Methods.Single(m => m.Name == "set_Item" && m.Parameters.Count == multiDimensionalArrayMethod.Rank + 1); + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = genericMethod.MakeConcreteGeneric([multiDimensionalArrayMethod.ArrayType.ElementType], []); + MonoIl2CppConversion.AddMonoToIl2CppConversion(translatedInstructions, genericMethod.Parameters[^1].ParameterType); + } + break; + case MultiDimensionalArrayMethodType.Address: + { + var genericMethod = arrayType.Methods.Single(m => m.Name == nameof(Il2CppArrayRank2<>.GetElementAddress) && m.Parameters.Count == multiDimensionalArrayMethod.Rank); + translatedInstruction.Code = CilOpCodes.Callvirt; + translatedInstruction.Operand = genericMethod.MakeConcreteGeneric([multiDimensionalArrayMethod.ArrayType.ElementType], []); + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, genericMethod.ReturnType); + } + break; + case MultiDimensionalArrayMethodType.Constructor: + { + var genericMethod = arrayType.Methods.Single(m => + { + return m.IsInstanceConstructor + && m.Parameters.Count == multiDimensionalArrayMethod.Rank + && m.Parameters.All(p => p.ParameterType == appContext.SystemTypes.SystemInt32Type); + }); + translatedInstruction.Code = CilOpCodes.Newobj; + translatedInstruction.Operand = genericMethod.MakeConcreteGeneric([multiDimensionalArrayMethod.ArrayType.ElementType], []); + MonoIl2CppConversion.AddIl2CppToMonoConversion(translatedInstructions, genericMethod.ReturnType); + } + break; + default: + Debug.Fail("Unexpected multidimensional array method type"); + return false; + } + } + else + { + Debug.Fail($"Unexpected operand type: {originalInstruction.Operand?.GetType().Name}"); + return false; + } + } + + var exceptionHandlers = new ExceptionHandler[originalMethodBody.ExceptionHandlers.Count]; + for (var i = 0; i < exceptionHandlers.Length; i++) + { + var originalExceptionHandler = originalMethodBody.ExceptionHandlers[i]; + + var handlerStart = ResolveLabel(originalExceptionHandler.HandlerStart, instructionDictionary); + + TypeAnalysisContext? exceptionType; + if (originalExceptionHandler.ExceptionType is null) + { + exceptionType = null; + } + else if (IsIl2CppPrimitive(originalExceptionHandler.ExceptionType, "Object")) + { + exceptionType = methodContext.AppContext.SystemTypes.SystemObjectType; + } + else + { + if (originalExceptionHandler.ExceptionType is GenericInstanceTypeAnalysisContext genericInstance) + { + exceptionType = genericInstance.GenericType.SystemExceptionType!.MakeGenericInstanceType(replacementVisitor.Replace(genericInstance.GenericArguments)); + } + else + { + exceptionType = originalExceptionHandler.ExceptionType.SystemExceptionType; + Debug.Assert(exceptionType is not null); + } + + // The system exception wrapper contains a reference to the underlying Il2Cpp object. + // We need to load the object reference after entering the exception handler. + + Debug.Assert(handlerStart is Instruction { Code.Code: CilCode.Nop }); + var handlerStartIndex = translatedInstructions.IndexOf((Instruction)handlerStart); + + var loadObjectInstruction = new Instruction(CilOpCodes.Ldfld, methodContext.AppContext.ResolveTypeOrThrow(typeof(Il2CppException)).GetFieldByName(nameof(Il2CppException.Il2cppObject))); + var castInstruction = new Instruction(CilOpCodes.Castclass, originalExceptionHandler.ExceptionType); + translatedInstructions.Insert(handlerStartIndex + 1, loadObjectInstruction); + translatedInstructions.Insert(handlerStartIndex + 2, castInstruction); + } + + var translatedExceptionHandler = new ExceptionHandler + { + HandlerType = originalExceptionHandler.HandlerType, + TryStart = ResolveLabel(originalExceptionHandler.TryStart, instructionDictionary), + TryEnd = ResolveLabel(originalExceptionHandler.TryEnd, instructionDictionary), + HandlerStart = handlerStart, + HandlerEnd = ResolveLabel(originalExceptionHandler.HandlerEnd, instructionDictionary), + FilterStart = ResolveLabel(originalExceptionHandler.FilterStart, instructionDictionary), + ExceptionType = exceptionType, + }; + exceptionHandlers[i] = translatedExceptionHandler; + } + + translatedInstructions.InsertRange(0, initializeInstructions); + + implementationMethod.PutExtraData(new TranslatedMethodBody + { + Instructions = translatedInstructions, + LocalVariables = localVariableList, + ExceptionHandlers = exceptionHandlers, + }); + return true; + + static ILabel[] ResolveLabels(IReadOnlyList labels, Dictionary instructionDictionary) + { + var resolvedLabels = new ILabel[labels.Count]; + for (var i = 0; i < labels.Count; i++) + { + resolvedLabels[i] = ResolveLabel(labels[i], instructionDictionary); + } + return resolvedLabels; + } + + [return: NotNullIfNotNull(nameof(label))] + static ILabel? ResolveLabel(ILabel? label, Dictionary instructionDictionary) + { + if (label is null or EndLabel) + return label; + + return instructionDictionary[(Instruction)label]; + } + } + + private static bool IsIl2CppPrimitive(TypeAnalysisContext type, string name) + { + if (type is ReferencedTypeAnalysisContext) + return false; + + if (type.DeclaringType is not null) + return false; + + if (type.DeclaringAssembly != type.AppContext.Il2CppMscorlib) + return false; + + return type.Namespace == "Il2CppSystem" && type.Name == name; + } +} diff --git a/Il2CppInterop.Generator/TypeAnalysisContextEqualityComparer.cs b/Il2CppInterop.Generator/TypeAnalysisContextEqualityComparer.cs new file mode 100644 index 00000000..9231a67b --- /dev/null +++ b/Il2CppInterop.Generator/TypeAnalysisContextEqualityComparer.cs @@ -0,0 +1,407 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using Cpp2IL.Core.Model.Contexts; +using LibCpp2IL.BinaryStructures; + +namespace Il2CppInterop.Generator; + +public class TypeAnalysisContextEqualityComparer : + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer +{ + public static TypeAnalysisContextEqualityComparer Instance { get; } = new(); + + public bool Equals(TypeAnalysisContext? x, TypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + if (x.Type != y.Type) + return false; + + return x.Type switch + { + Il2CppTypeEnum.IL2CPP_TYPE_ARRAY => Equals(x as ArrayTypeAnalysisContext, y as ArrayTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY => Equals(x as SzArrayTypeAnalysisContext, y as SzArrayTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST => Equals(x as GenericInstanceTypeAnalysisContext, y as GenericInstanceTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_VAR or Il2CppTypeEnum.IL2CPP_TYPE_MVAR => Equals(x as GenericParameterTypeAnalysisContext, y as GenericParameterTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_SENTINEL => Equals(x as SentinelTypeAnalysisContext, y as SentinelTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_PINNED => Equals(x as PinnedTypeAnalysisContext, y as PinnedTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_PTR => Equals(x as PointerTypeAnalysisContext, y as PointerTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_BYREF => Equals(x as ByRefTypeAnalysisContext, y as ByRefTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_BOXED => Equals(x as BoxedTypeAnalysisContext, y as BoxedTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_CMOD_OPT or Il2CppTypeEnum.IL2CPP_TYPE_CMOD_REQD => Equals(x as CustomModifierTypeAnalysisContext, y as CustomModifierTypeAnalysisContext), + Il2CppTypeEnum.IL2CPP_TYPE_FNPTR => false,// Function pointers are not part of the Cpp2IL context system + _ => false,// Type definitions have unique instances + }; + } + public bool Equals(GenericInstanceTypeAnalysisContext? x, GenericInstanceTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.GenericType, y.GenericType) && x.GenericArguments.SequenceEqual(y.GenericArguments, this); + } + public bool Equals(GenericParameterTypeAnalysisContext? x, GenericParameterTypeAnalysisContext? y) + { + return ReferenceEquals(x, y); // Generic parameters have unique instances + } + public bool Equals(SentinelTypeAnalysisContext? x, SentinelTypeAnalysisContext? y) + { + if (x is null || y is null) + return ReferenceEquals(x, y); + + return true; // Sentinel types are always equal + } + public bool Equals(CustomModifierTypeAnalysisContext? x, CustomModifierTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.ElementType, y.ElementType) && Equals(x.ModifierType, y.ModifierType); + } + public bool Equals(PointerTypeAnalysisContext? x, PointerTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.ElementType, y.ElementType); + } + public bool Equals(ByRefTypeAnalysisContext? x, ByRefTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.ElementType, y.ElementType); + } + public bool Equals(BoxedTypeAnalysisContext? x, BoxedTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.ElementType, y.ElementType); + } + public bool Equals(SzArrayTypeAnalysisContext? x, SzArrayTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.ElementType, y.ElementType); + } + public bool Equals(ArrayTypeAnalysisContext? x, ArrayTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.ElementType, y.ElementType) && x.Rank == y.Rank; + } + public bool Equals(PinnedTypeAnalysisContext? x, PinnedTypeAnalysisContext? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + + return Equals(x.ElementType, y.ElementType); + } + + public int GetHashCode(TypeAnalysisContext type) => type.Type switch + { + Il2CppTypeEnum.IL2CPP_TYPE_ARRAY => GetHashCode((ArrayTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY => GetHashCode((SzArrayTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST => GetHashCode((GenericInstanceTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_VAR or Il2CppTypeEnum.IL2CPP_TYPE_MVAR => GetHashCode((GenericParameterTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_SENTINEL => GetHashCode((SentinelTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_PINNED => GetHashCode((PinnedTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_PTR => GetHashCode((PointerTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_BYREF => GetHashCode((ByRefTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_BOXED => GetHashCode((BoxedTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_CMOD_OPT or Il2CppTypeEnum.IL2CPP_TYPE_CMOD_REQD => GetHashCode((CustomModifierTypeAnalysisContext)type), + Il2CppTypeEnum.IL2CPP_TYPE_FNPTR => 0, // Function pointers are not part of the Cpp2IL context system + _ => HashCode.Combine(type.Type, type), // Type definitions have unique instances + }; + + public int GetHashCode(GenericInstanceTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.GenericType, this); + foreach (var genericArgument in type.GenericArguments) + { + hash.Add(genericArgument, this); + } + return hash.ToHashCode(); + } + + public int GetHashCode(GenericParameterTypeAnalysisContext type) + { + return HashCode.Combine(type.Type, type); // Generic parameters have unique instances, so we can use the instance itself as the hash code. + } + + public int GetHashCode(SentinelTypeAnalysisContext type) + { + return HashCode.Combine(type.Type); + } + + public int GetHashCode(CustomModifierTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.ElementType, this); + hash.Add(type.ModifierType, this); + return hash.ToHashCode(); + } + + public int GetHashCode(PointerTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.ElementType, this); + return hash.ToHashCode(); + } + + public int GetHashCode(ByRefTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.ElementType, this); + return hash.ToHashCode(); + } + + public int GetHashCode(BoxedTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.ElementType, this); + return hash.ToHashCode(); + } + + public int GetHashCode(SzArrayTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.ElementType, this); + return hash.ToHashCode(); + } + + public int GetHashCode(ArrayTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.ElementType, this); + hash.Add(type.Rank); + return hash.ToHashCode(); + } + + public int GetHashCode(PinnedTypeAnalysisContext type) + { + HashCode hash = new HashCode(); + hash.Add(type.Type); + hash.Add(type.ElementType, this); + return hash.ToHashCode(); + } + + public bool Equals(TypeAnalysisContext? x, TypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + + return x.Type switch + { + Il2CppTypeEnum.IL2CPP_TYPE_ARRAY => Equals(x as ArrayTypeAnalysisContext, y as ArrayTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY => Equals(x as SzArrayTypeAnalysisContext, y as SzArrayTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST => Equals(x as GenericInstanceTypeAnalysisContext, y as GenericInstanceTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_VAR or Il2CppTypeEnum.IL2CPP_TYPE_MVAR => Equals(x as GenericParameterTypeAnalysisContext, y as GenericParameterSignature), + Il2CppTypeEnum.IL2CPP_TYPE_SENTINEL => Equals(x as SentinelTypeAnalysisContext, y as SentinelTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_PINNED => Equals(x as PinnedTypeAnalysisContext, y as PinnedTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_PTR => Equals(x as PointerTypeAnalysisContext, y as PointerTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_BYREF => Equals(x as ByRefTypeAnalysisContext, y as ByReferenceTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_BOXED => Equals(x as BoxedTypeAnalysisContext, y as BoxedTypeSignature), + Il2CppTypeEnum.IL2CPP_TYPE_CMOD_OPT or Il2CppTypeEnum.IL2CPP_TYPE_CMOD_REQD => Equals(x as CustomModifierTypeAnalysisContext, y as CustomModifierTypeSignature), + _ => SimpleTypeEquals(x, y), + }; + + bool SimpleTypeEquals(TypeAnalysisContext x, TypeSignature y) + { + if (x.Name != y.Name || x.Namespace != y.Namespace) + return false; + + if (x.DeclaringType is not null) + return Equals(x.DeclaringType, y.DeclaringType); + else if (y.DeclaringType is not null) + return false; + + return true; + } + } + + public bool Equals(TypeAnalysisContext? x, ITypeDescriptor? y) + { + return y switch + { + null => x is null, + TypeSignature typeSignature => Equals(x, typeSignature), + ITypeDefOrRef typeDefOrRef => Equals(x, typeDefOrRef), + _ => false, + }; + } + + public bool Equals(TypeAnalysisContext? x, ITypeDefOrRef? y) + { + if (y is TypeSpecification typeSpecification) + return Equals(x, typeSpecification.Signature); + if (x is null) + return y is null; + if (y is null) + return false; + if (x is ReferencedTypeAnalysisContext) + return false; + + return SimpleTypeEquals(x, y); + + bool SimpleTypeEquals(TypeAnalysisContext x, ITypeDefOrRef y) + { + if (x.Name != y.Name || x.Namespace != y.Namespace) + return false; + + if (x.DeclaringType is not null) + return Equals(x.DeclaringType, y.DeclaringType); + else if (y.DeclaringType is not null) + return false; + + return true; + } + } + + public bool Equals(GenericInstanceTypeAnalysisContext? x, GenericInstanceTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + if (!Equals(x.GenericType, y.GenericType)) + return false; + if (x.GenericArguments.Count != y.TypeArguments.Count) + return false; + for (var i = 0; i < x.GenericArguments.Count; i++) + { + if (!Equals(x.GenericArguments[i], y.TypeArguments[i])) + return false; + } + return true; + } + + public bool Equals(GenericParameterTypeAnalysisContext? x, GenericParameterSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return x.Index == y.Index && Equals(x.Type, y.ParameterType); + } + + private static bool Equals(Il2CppTypeEnum x, GenericParameterType y) => (x, y) switch + { + (Il2CppTypeEnum.IL2CPP_TYPE_VAR, GenericParameterType.Type) => true, + (Il2CppTypeEnum.IL2CPP_TYPE_MVAR, GenericParameterType.Method) => true, + _ => false, + }; + + public bool Equals(SentinelTypeAnalysisContext? x, SentinelTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return true; + } + + public bool Equals(CustomModifierTypeAnalysisContext? x, CustomModifierTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return (x.Required == y.IsRequired) && Equals(x.ElementType, y.BaseType) && Equals(x.ModifierType, y.ModifierType); + } + + public bool Equals(PointerTypeAnalysisContext? x, PointerTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return Equals(x.ElementType, y.BaseType); + } + + public bool Equals(ByRefTypeAnalysisContext? x, ByReferenceTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return Equals(x.ElementType, y.BaseType); + } + + public bool Equals(BoxedTypeAnalysisContext? x, BoxedTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return Equals(x.ElementType, y.BaseType); + } + + public bool Equals(SzArrayTypeAnalysisContext? x, SzArrayTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return Equals(x.ElementType, y.BaseType); + } + + public bool Equals(ArrayTypeAnalysisContext? x, ArrayTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return x.Rank == y.Rank && Equals(x.ElementType, y.BaseType); + } + + public bool Equals(PinnedTypeAnalysisContext? x, PinnedTypeSignature? y) + { + if (x is null) + return y is null; + if (y is null) + return false; + return Equals(x.ElementType, y.BaseType); + } +} diff --git a/Il2CppInterop.Generator/UnstripBaseProcessingLayer.cs b/Il2CppInterop.Generator/UnstripBaseProcessingLayer.cs new file mode 100644 index 00000000..b04ba5e9 --- /dev/null +++ b/Il2CppInterop.Generator/UnstripBaseProcessingLayer.cs @@ -0,0 +1,732 @@ +using System.Diagnostics.CodeAnalysis; +using AsmResolver.DotNet; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Cpp2IL.Core.Model.CustomAttributes; +using Il2CppInterop.Generator.Operands; +using LibCpp2IL.BinaryStructures; + +namespace Il2CppInterop.Generator; + +public abstract class UnstripBaseProcessingLayer : Cpp2IlProcessingLayer +{ + public const string AssembliesKey = "unstrip-assemblies"; + public const string DirectoryKey = "unstrip-directory"; + + protected static void InjectAssemblies(ApplicationAnalysisContext appContext, IReadOnlyList assemblyList, bool storeMethodBodies, bool allowGenericTypes) + { + // Inject assemblies + var assemblyDictionary = new Dictionary(assemblyList.Count); + RuntimeContext? runtimeContext = null; + foreach (var assembly in assemblyList) + { + if (IsEmpty(assembly.ManifestModule!)) + continue; // Skip empty assemblies + + var assemblyRuntimeContext = assembly.RuntimeContext ?? throw new InvalidOperationException("Assembly is missing runtime context"); + if (runtimeContext is null) + { + runtimeContext = assemblyRuntimeContext; + } + else if (runtimeContext != assemblyRuntimeContext) + { + throw new InvalidOperationException("All assemblies must have the same runtime context"); + } + + if (!appContext.AssembliesByName.TryGetValue(assembly.Name!, out var assemblyContext)) + { + assemblyContext = appContext.InjectAssembly(assembly.Name!, assembly.Version, (uint)assembly.HashAlgorithm, (uint)assembly.Attributes, assembly.Culture, null, assembly.PublicKey); + assemblyContext.IsUnstripped = true; + } + assemblyDictionary.Add(assembly.ManifestModule!, assemblyContext); + } + + if (runtimeContext is null) + { + return; // No assemblies were injected, so we can skip the rest of the process + } + + // Inject types + var injectedTypes = new List<(TypeDefinition, InjectedTypeAnalysisContext)>(); + var existingTypes = new List<(TypeDefinition, TypeAnalysisContext)>(); + if (allowGenericTypes) + { + foreach ((var module, var assemblyContext) in assemblyDictionary) + { + foreach (var type in module.TopLevelTypes) + { + var typeContext = assemblyContext.GetTypeByFullName(type.FullName); + if (typeContext is null) + { + typeContext = assemblyContext.InjectType((string?)type.Namespace ?? "", (string?)type.Name ?? "", null, (System.Reflection.TypeAttributes)type.Attributes); + foreach (var genericParameter in type.GenericParameters) + { + var genericParameterContext = new GenericParameterTypeAnalysisContext( + genericParameter.Name!, + genericParameter.Number, + Il2CppTypeEnum.IL2CPP_TYPE_VAR, + (System.Reflection.GenericParameterAttributes)genericParameter.Attributes, + typeContext); + typeContext.GenericParameters.Add(genericParameterContext); + } + injectedTypes.Add((type, (InjectedTypeAnalysisContext)typeContext)); + typeContext.IsUnstripped = true; + } + else + { + existingTypes.Add((type, typeContext)); + } + InjectNestedTypes(type, typeContext, injectedTypes, existingTypes); + } + } + } + else + { + var nonExistingTypes = new Queue(); + var existingTypesDictionary = new Dictionary(runtimeContext.SignatureComparer); + foreach ((var module, var assemblyContext) in assemblyDictionary) + { + foreach (var type in module.TopLevelTypes) + { + var typeContext = assemblyContext.GetTypeByFullName(type.FullName); + if (typeContext is null) + { + nonExistingTypes.Enqueue(type); + //AddNestedTypes(type, nonExistingTypes); + } + else + { + existingTypesDictionary.Add(type, typeContext); + //AddNestedTypes(type, typeContext, nonExistingTypes, existingTypesDictionary); + } + } + } + + var invalidTypes = new HashSet(runtimeContext.SignatureComparer); + var injectedTypesDictionary = new Dictionary(runtimeContext.SignatureComparer); + while (nonExistingTypes.TryDequeue(out var nonExistingType)) + { + if (nonExistingType.GenericParameters.Count > 0) + { + invalidTypes.Add(nonExistingType); + continue; + } + if (ContainsAnyAbstractGenericMethods(nonExistingType, runtimeContext)) + { + invalidTypes.Add(nonExistingType); + continue; + } + + var baseType = nonExistingType.BaseType; + if (baseType is not null) + { + var baseTypeResolved = baseType.TryResolve(runtimeContext); + if (baseTypeResolved is null) + { + invalidTypes.Add(nonExistingType); + continue; + } + if (baseTypeResolved.GenericParameters.Count > 0) + { + invalidTypes.Add(nonExistingType); + continue; + } + if (baseTypeResolved.DeclaringType is not null) + { + // We don't currently support nested types + invalidTypes.Add(nonExistingType); + continue; + } + if (invalidTypes.Contains(baseTypeResolved)) + { + invalidTypes.Add(nonExistingType); + continue; + } + if (ContainsAnyAbstractGenericMethods(baseTypeResolved, runtimeContext)) + { + invalidTypes.Add(nonExistingType); + continue; + } + if (!existingTypesDictionary.ContainsKey(baseTypeResolved) && !injectedTypesDictionary.ContainsKey(baseTypeResolved)) + { + nonExistingTypes.Enqueue(nonExistingType); + continue; + } + } + + var shouldSkipToNextType = false; + foreach (var interfaceType in nonExistingType.Interfaces.Select(i => i.Interface)) + { + var interfaceTypeResolved = interfaceType?.TryResolve(runtimeContext); + if (interfaceTypeResolved is null) + { + invalidTypes.Add(nonExistingType); + shouldSkipToNextType = true; + break; + } + if (interfaceTypeResolved.GenericParameters.Count > 0) + { + invalidTypes.Add(nonExistingType); + shouldSkipToNextType = true; + break; + } + if (interfaceTypeResolved.DeclaringType is not null) + { + // We don't currently support nested types + invalidTypes.Add(nonExistingType); + shouldSkipToNextType = true; + break; + } + if (invalidTypes.Contains(interfaceTypeResolved)) + { + invalidTypes.Add(nonExistingType); + shouldSkipToNextType = true; + break; + } + if (ContainsAnyAbstractGenericMethods(interfaceTypeResolved, runtimeContext)) + { + invalidTypes.Add(nonExistingType); + shouldSkipToNextType = true; + break; + } + if (!existingTypesDictionary.ContainsKey(interfaceTypeResolved) && !injectedTypesDictionary.ContainsKey(interfaceTypeResolved)) + { + nonExistingTypes.Enqueue(nonExistingType); + shouldSkipToNextType = true; + break; + } + } + if (shouldSkipToNextType) + { + continue; + } + + if (nonExistingType.DeclaringType is null) + { + var typeContext = assemblyDictionary[nonExistingType.DeclaringModule!].InjectType((string?)nonExistingType.Namespace ?? "", (string?)nonExistingType.Name ?? "", null, (System.Reflection.TypeAttributes)nonExistingType.Attributes); + injectedTypesDictionary.Add(nonExistingType, typeContext); + typeContext.IsUnstripped = true; + } + else if (invalidTypes.Contains(nonExistingType.DeclaringType)) + { + invalidTypes.Add(nonExistingType); + } + else if (nonExistingType.DeclaringType.GenericParameters.Count > 0) + { + invalidTypes.Add(nonExistingType); + } + else if (existingTypesDictionary.TryGetValue(nonExistingType.DeclaringType, out var declaringTypeContext) || injectedTypesDictionary.TryGetValue(nonExistingType.DeclaringType, out declaringTypeContext)) + { + var typeContext = declaringTypeContext.InjectNestedType((string?)nonExistingType.Name ?? "", null, (System.Reflection.TypeAttributes)nonExistingType.Attributes); + injectedTypesDictionary.Add(nonExistingType, typeContext); + typeContext.IsUnstripped = true; + } + else + { + nonExistingTypes.Enqueue(nonExistingType); + } + } + + injectedTypes.Capacity = injectedTypesDictionary.Count; + existingTypes.Capacity = existingTypesDictionary.Count; + injectedTypes.AddRange(injectedTypesDictionary.Select(kv => (kv.Key, (InjectedTypeAnalysisContext)kv.Value))); + existingTypes.AddRange(existingTypesDictionary.Select(kv => (kv.Key, kv.Value))); + } + + // Set up type hierarchy + foreach (var (type, typeContext) in injectedTypes) + { + var resolver = new ContextResolver(typeContext, runtimeContext); + if (type.BaseType is not null) + { + if (resolver.TryResolve(type.BaseType, out var baseTypeContext)) + { + typeContext.SetDefaultBaseType(baseTypeContext); + } + else + { + throw new InvalidOperationException($"Unable to resolve base type {type.BaseType} for type {type.FullName}"); + } + } + + foreach (var interfaceType in type.Interfaces.Select(t => t.Interface)) + { + if (resolver.TryResolve(interfaceType, out var interfaceTypeContext)) + { + typeContext.InterfaceContexts.Add(interfaceTypeContext); + } + else + { + throw new InvalidOperationException($"Unable to resolve interface type {interfaceType} for type {type.FullName}"); + } + } + + for (var i = 0; i < type.GenericParameters.Count; i++) + { + var genericParameter = type.GenericParameters[i]; + var genericParameterContext = typeContext.GenericParameters[i]; + foreach (var constraint in genericParameter.Constraints) + { + if (resolver.TryResolve(constraint.Constraint, out var constraintType)) + { + genericParameterContext.ConstraintTypes.Add(constraintType); + } + else + { + throw new InvalidOperationException($"Unable to resolve generic parameter constraint {constraint.Constraint} for type {type.FullName}"); + } + } + } + } + + // Inject members + List<(InjectedMethodAnalysisContext, IMethodDefOrRef)> methodsNeedingOverrides = new(); + List<(MethodAnalysisContext, MethodDefinition)> methodsNeedingBodies = new(); + foreach (var (type, typeContext) in injectedTypes) + { + CopyCustomAttributes(type, typeContext, typeContext.DeclaringAssembly, runtimeContext); + + for (var i = 0; i < type.GenericParameters.Count; i++) + { + CopyCustomAttributes(type.GenericParameters[i], typeContext.GenericParameters[i], typeContext.DeclaringAssembly, runtimeContext); + } + + foreach (var field in type.Fields) + { + TryInjectField(field, typeContext, runtimeContext); + } + + Dictionary methodLookup = new(); + foreach (var method in type.Methods) + { + if (method.IsPInvokeImpl) + { + // No p/invoke methods + continue; + } + + if (TryInjectMethod(method, typeContext, runtimeContext, out var methodContext)) + { + methodLookup.Add(method, methodContext); + methodsNeedingBodies.Add((methodContext, method)); + } + } + + foreach (var property in type.Properties) + { + TryInjectProperty(property, typeContext, runtimeContext, methodLookup); + } + + foreach (var @event in type.Events) + { + TryInjectEvent(@event, typeContext, runtimeContext, methodLookup); + } + + foreach (var implementation in type.MethodImplementations) + { + if (implementation.Body is not MethodDefinition method || implementation.Declaration is null || !methodLookup.TryGetValue(method, out var methodContext)) + continue; + + methodsNeedingOverrides.Add(((InjectedMethodAnalysisContext)methodContext, implementation.Declaration)); + } + } + foreach (var (type, typeContext) in existingTypes) + { + // We shouldn't inject static fields. + // Even though it's not like instance fields where state gets changed, + // we can't guarantee that the static fields will be initialized properly. + // However, we can still inject constant fields. + foreach (var field in type.Fields) + { + if (!field.IsStatic) + continue; // Instance fields cannot be unstripped + + if (typeContext.Fields.Any(f => f.Name == field.Name)) + continue; // Already present + + if (field.Constant?.InterpretData() is null) + continue; // Skip fields without a constant value + + TryInjectField(field, typeContext, runtimeContext); + } + + Dictionary methodLookup = new(); + foreach (var method in type.Methods) + { + var existingMethodContext = new ContextResolver(typeContext, runtimeContext).ResolveInType(method); + if (existingMethodContext is not null) + { + methodLookup.Add(method, existingMethodContext); + if (method.GenericParameters.Count > 0 || type.GenericParameters.Count > 0) + { + // We inject the method if it's generic because we want to support generic instantiations + // that were not present in the original game. We abstain from non-generic methods because + // of performance, memory usage, output size, and risk of breaking the game. + // + // Counterargument: injecting as much as possible allows for more transpilers to be written. + + methodsNeedingBodies.Add((existingMethodContext, method)); + } + continue; + } + + if (method.IsPInvokeImpl) + { + // No p/invoke methods + continue; + } + + if (!TryInjectMethod(method, typeContext, runtimeContext, out var methodContext)) + continue; + + methodLookup.Add(method, methodContext); + if (method.IsAbstract) + { + methodContext.Attributes &= ~System.Reflection.MethodAttributes.Abstract; // We convert abstract methods to virtual methods + + methodContext.PutExtraData(new NativeMethodBody() + { + Instructions = + [ + new Instruction(CilOpCodes.Ldnull), + new Instruction(CilOpCodes.Throw), + ] + }); + } + else + { + methodsNeedingBodies.Add((methodContext, method)); + } + + foreach (var implementation in type.MethodImplementations) + { + if (implementation.Body != method || implementation.Declaration is null) + continue; + methodsNeedingOverrides.Add((methodContext, implementation.Declaration)); + } + } + + foreach (var property in type.Properties) + { + if (typeContext.Properties.Any(p => p.Name == property.Name)) + continue; // Already present + + TryInjectProperty(property, typeContext, runtimeContext, methodLookup); + } + + foreach (var @event in type.Events) + { + if (typeContext.Events.Any(e => e.Name == @event.Name)) + continue; // Already present + + TryInjectEvent(@event, typeContext, runtimeContext, methodLookup); + } + } + + // Assign method overrides + foreach (var (methodContext, declaration) in methodsNeedingOverrides) + { + var declarationContext = new ContextResolver(methodContext.DeclaringType!, runtimeContext).Resolve(declaration); + if (declarationContext is not null) + { + methodContext.Overrides.Add((MethodAnalysisContext)declarationContext); + } + } + + // Assign method bodies + if (storeMethodBodies) + { + var successfulCount = 0; + foreach (var (methodContext, methodDefinition) in methodsNeedingBodies) + { + var successful = OriginalMethodBody.MaybeStoreOriginalMethodBody(methodDefinition, methodContext, runtimeContext); + if (successful) + { + successfulCount++; + } + } + + // Report how many method bodies were successfully stored. + Logger.InfoNewline($"Recovered the original method body for {successfulCount}/{methodsNeedingBodies.Count} attempts.", nameof(UnstripBaseProcessingLayer)); + } + } + + private static bool ContainsAnyAbstractGenericMethods(TypeDefinition type, RuntimeContext runtimeContext) + { + while (type.IsAbstract) + { + if (type.Methods.Any(m => m.IsAbstract && m.GenericParameters.Count > 0)) + return true; + if (type.BaseType is null) + break; + var baseTypeResolved = type.BaseType.TryResolve(runtimeContext); + if (baseTypeResolved is null) + return true; // If we can't resolve the base type, we have to assume it contains abstract generic methods + type = baseTypeResolved; + } + return false; + } + + private static void AddNestedTypes(TypeDefinition declaringType, TypeAnalysisContext declaringTypeContext, Queue nonExistingTypes, Dictionary existingTypes) + { + foreach (var nestedType in declaringType.NestedTypes) + { + var nestedTypeContext = declaringTypeContext.NestedTypes.FirstOrDefault(t => t.Name == nestedType.Name); + if (nestedTypeContext is null) + { + nonExistingTypes.Enqueue(nestedType); + AddNestedTypes(nestedType, nonExistingTypes); + } + else + { + existingTypes.Add(nestedType, nestedTypeContext); + AddNestedTypes(nestedType, nestedTypeContext, nonExistingTypes, existingTypes); + } + } + } + + private static void AddNestedTypes(TypeDefinition declaringType, Queue nonExistingTypes) + { + foreach (var nestedType in declaringType.NestedTypes) + { + nonExistingTypes.Enqueue(nestedType); + AddNestedTypes(nestedType, nonExistingTypes); + } + } + + private static void InjectNestedTypes(TypeDefinition declaringType, TypeAnalysisContext declaringTypeContext, List<(TypeDefinition, InjectedTypeAnalysisContext)> injectedTypes, List<(TypeDefinition, TypeAnalysisContext)> existingTypes) + { + foreach (var nestedType in declaringType.NestedTypes) + { + var nestedTypeContext = declaringTypeContext.NestedTypes.FirstOrDefault(t => t.Name == nestedType.Name); + if (nestedTypeContext is null) + { + nestedTypeContext = declaringTypeContext.InjectNestedType((string?)nestedType.Name ?? "", null, (System.Reflection.TypeAttributes)nestedType.Attributes); + foreach (var genericParameter in nestedType.GenericParameters) + { + var genericParameterContext = new GenericParameterTypeAnalysisContext( + genericParameter.Name!, + genericParameter.Number, + Il2CppTypeEnum.IL2CPP_TYPE_VAR, + (System.Reflection.GenericParameterAttributes)genericParameter.Attributes, + nestedTypeContext); + nestedTypeContext.GenericParameters.Add(genericParameterContext); + } + injectedTypes.Add((nestedType, (InjectedTypeAnalysisContext)nestedTypeContext)); + + nestedTypeContext.IsUnstripped = true; + } + else + { + existingTypes.Add((nestedType, nestedTypeContext)); + } + InjectNestedTypes(nestedType, nestedTypeContext, injectedTypes, existingTypes); + } + } + + private static bool TryInjectField(FieldDefinition field, TypeAnalysisContext typeContext, RuntimeContext runtimeContext) + { + if (field.Name is not null && new ContextResolver(typeContext, runtimeContext).TryResolve(field.Signature?.FieldType, out var fieldTypeContext)) + { + var fieldContext = new InjectedFieldAnalysisContext( + field.Name!, + fieldTypeContext, + (System.Reflection.FieldAttributes)field.Attributes, + typeContext); + typeContext.Fields.Add(fieldContext); + + fieldContext.OverrideConstantValue = field.Constant?.InterpretData(); + + CopyCustomAttributes(field, fieldContext, typeContext.DeclaringAssembly, runtimeContext); + + fieldContext.IsUnstripped = true; + + return true; + } + + return false; + } + + private static bool TryInjectMethod(MethodDefinition method, TypeAnalysisContext typeContext, RuntimeContext runtimeContext, [NotNullWhen(true)] out InjectedMethodAnalysisContext? methodContext) + { + // Due to an unfortunate reality of resolving types, we need to create a method and add it to our target type before we can resolve its signature. + // This is because the method signature can reference the method's generic parameters, so we need to create the method first. + methodContext = new InjectedMethodAnalysisContext( + typeContext, + method.Name!, + typeContext.AppContext.SystemTypes.SystemObjectType, + (System.Reflection.MethodAttributes)method.Attributes, + Enumerable.Repeat(typeContext.AppContext.SystemTypes.SystemObjectType, method.Parameters.Count).ToArray(), + method.Parameters.Select(p => p.Name).ToArray(), + method.Parameters.Select(p => (System.Reflection.ParameterAttributes)p.GetOrCreateDefinition().Attributes).ToArray(), + (System.Reflection.MethodImplAttributes)method.ImplAttributes); + + foreach (var genericParameter in method.GenericParameters) + { + var genericParameterContext = new GenericParameterTypeAnalysisContext(genericParameter.Name!, genericParameter.Number, Il2CppTypeEnum.IL2CPP_TYPE_MVAR, (System.Reflection.GenericParameterAttributes)genericParameter.Attributes, methodContext); + methodContext.GenericParameters.Add(genericParameterContext); + } + + typeContext.Methods.Add(methodContext); + + var methodResolver = new ContextResolver(methodContext, runtimeContext); + + if (!methodResolver.TryResolve(method.Signature?.ReturnType, out var returnTypeContext)) + { + typeContext.Methods.Remove(methodContext); + return false; + } + else + { + methodContext.SetDefaultReturnType(returnTypeContext); + } + + if (!methodResolver.TryResolve(method.Parameters.Select(p => p.ParameterType), out var parameterTypeContexts)) + { + typeContext.Methods.Remove(methodContext); + return false; + } + else + { + for (var i = 0; i < parameterTypeContexts.Count; i++) + { + var parameter = (InjectedParameterAnalysisContext)methodContext.Parameters[i]; + parameter.SetDefaultParameterType(parameterTypeContexts[i]); + } + } + + if (!TryAddGenericConstraints(method, methodContext, runtimeContext)) + { + typeContext.Methods.Remove(methodContext); + return false; + } + + CopyCustomAttributes(method, methodContext, typeContext.DeclaringAssembly, runtimeContext); + for (var i = 0; i < method.Parameters.Count; i++) + { + CopyCustomAttributes(method.Parameters[i].GetOrCreateDefinition(), methodContext.Parameters[i], typeContext.DeclaringAssembly, runtimeContext); + } + for (var i = 0; i < method.GenericParameters.Count; i++) + { + CopyCustomAttributes(method.GenericParameters[i], methodContext.GenericParameters[i], typeContext.DeclaringAssembly, runtimeContext); + } + + methodContext.IsUnstripped = true; + + return true; + + static bool TryAddGenericConstraints(MethodDefinition method, InjectedMethodAnalysisContext methodContext, RuntimeContext runtimeContext) + { + var methodResolver = new ContextResolver(methodContext, runtimeContext); + var anyInvalidConstraints = false; + for (var i = 0; i < method.GenericParameters.Count; i++) + { + var genericParameter = method.GenericParameters[i]; + var genericParameterContext = methodContext.GenericParameters[i]; + foreach (var constraint in genericParameter.Constraints) + { + if (methodResolver.TryResolve(constraint.Constraint, out var constraintType)) + { + genericParameterContext.ConstraintTypes.Add(constraintType); + } + else + { + anyInvalidConstraints = true; + break; + } + } + if (anyInvalidConstraints) + break; + } + + return !anyInvalidConstraints; + } + } + + private static bool TryInjectProperty(PropertyDefinition property, TypeAnalysisContext typeContext, RuntimeContext runtimeContext, Dictionary methodLookup) + { + var resolver = new ContextResolver(typeContext, runtimeContext); + + if (!resolver.TryResolve(property.Signature?.ReturnType, out var propertyTypeContext)) + return false; + + var getMethodContext = methodLookup.TryGetValue(property.GetMethod); + if (getMethodContext is null && property.GetMethod is not null) + return false; + + var setMethodContext = methodLookup.TryGetValue(property.SetMethod); + if (setMethodContext is null && property.SetMethod is not null) + return false; + + var propertyContext = new InjectedPropertyAnalysisContext( + property.Name!, + propertyTypeContext, + getMethodContext, + setMethodContext, + (System.Reflection.PropertyAttributes)property.Attributes, + typeContext); + typeContext.Properties.Add(propertyContext); + + CopyCustomAttributes(property, propertyContext, typeContext.DeclaringAssembly, runtimeContext); + + propertyContext.IsUnstripped = true; + + return true; + } + + private static bool TryInjectEvent(EventDefinition @event, TypeAnalysisContext typeContext, RuntimeContext runtimeContext, Dictionary methodLookup) + { + var resolver = new ContextResolver(typeContext, runtimeContext); + + if (!resolver.TryResolve(@event.EventType, out var eventTypeContext)) + return false; + + var addMethodContext = methodLookup.TryGetValue(@event.AddMethod); + if (addMethodContext is null && @event.AddMethod is not null) + return false; + + var removeMethodContext = methodLookup.TryGetValue(@event.RemoveMethod); + if (removeMethodContext is null && @event.RemoveMethod is not null) + return false; + + var fireMethodContext = methodLookup.TryGetValue(@event.FireMethod); + if (fireMethodContext is null && @event.FireMethod is not null) + return false; + + var eventContext = new InjectedEventAnalysisContext( + @event.Name!, + eventTypeContext, + addMethodContext, + removeMethodContext, + fireMethodContext, + (System.Reflection.EventAttributes)@event.Attributes, + typeContext); + typeContext.Events.Add(eventContext); + + CopyCustomAttributes(@event, eventContext, typeContext.DeclaringAssembly, runtimeContext); + + eventContext.IsUnstripped = true; + + return true; + } + + private static void CopyCustomAttributes(IHasCustomAttribute source, HasCustomAttributes destination, AssemblyAnalysisContext assembly, RuntimeContext runtimeContext) + { + foreach (var customAttribute in source.CustomAttributes) + { + if (customAttribute.Constructor is null or { Signature: null or { ParameterTypes.Count: > 0 } }) + continue; // Skip custom attributes with parameters or an invalid constructor + + if (!new ContextResolver(assembly, runtimeContext).TryResolve(customAttribute.Constructor, out var constructorContext)) + continue; // Skip custom attributes with an invalid constructor + + destination.CustomAttributes ??= []; + destination.CustomAttributes.Add(new AnalyzedCustomAttribute((MethodAnalysisContext)constructorContext)); + } + } + + private static bool IsEmpty(ModuleDefinition module) + { + return module.TopLevelTypes.Count == 0 || (module.TopLevelTypes.Count == 1 && module.TopLevelTypes[0].IsModuleType); + } +} diff --git a/Il2CppInterop.Generator/UnstripProcessingLayer.cs b/Il2CppInterop.Generator/UnstripProcessingLayer.cs new file mode 100644 index 00000000..33c783c9 --- /dev/null +++ b/Il2CppInterop.Generator/UnstripProcessingLayer.cs @@ -0,0 +1,45 @@ +using AsmResolver.DotNet; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator; + +public class UnstripProcessingLayer : UnstripBaseProcessingLayer +{ + public override string Name => "Unstrip external assemblies into the Cpp2IL context system"; + + public override string Id => "unstrip"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var assemblyList = appContext.GetExtraData>(AssembliesKey); + if (assemblyList is null) + { + var directoryPath = appContext.GetExtraData(DirectoryKey); + if (directoryPath is null) + { + Logger.WarnNewline($"No assemblies specified - processor will not run. You need to provide the {DirectoryKey}, either by programmatically adding it as extra data in the app context, or by specifying it in the --processor-config command line option.", nameof(UnstripProcessingLayer)); + return; + } + + RuntimeContext runtimeContext = new(DotNetRuntimeInfo.NetFramework(4, 7), null, KnownCorLibs.MsCorLib_v4_0_0_0, null, Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories).Append(directoryPath)); + assemblyList = Directory.GetFiles(directoryPath, "*.dll", SearchOption.AllDirectories) + .Select(path => AssemblyDefinition.FromFile(path, createRuntimeContext: false)) + .ToList(); + + foreach (var assembly in assemblyList) + { + runtimeContext.AddAssembly(assembly); + } + } + + if (assemblyList.Count == 0) + { + Logger.WarnNewline("No assemblies provided - processor will not run.", nameof(UnstripProcessingLayer)); + return; + } + + Logger.InfoNewline($"Unstripping {assemblyList.Count} assemblies...", nameof(UnstripProcessingLayer)); + + InjectAssemblies(appContext, assemblyList, true, false); + } +} diff --git a/Il2CppInterop.Generator/UserFriendlyOverloadProcessingLayer.cs b/Il2CppInterop.Generator/UserFriendlyOverloadProcessingLayer.cs new file mode 100644 index 00000000..826e3e27 --- /dev/null +++ b/Il2CppInterop.Generator/UserFriendlyOverloadProcessingLayer.cs @@ -0,0 +1,219 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Generator.Operands; +using Il2CppInterop.Generator.Visitors; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using CilOpCodes = AsmResolver.PE.DotNet.Cil.CilOpCodes; + +namespace Il2CppInterop.Generator; + +public sealed class UserFriendlyOverloadProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "User-Friendly Overloads"; + public override string Id => "user_friendly_overloads"; + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + const string ArrayNamespace = "Il2CppInterop.Runtime.InteropTypes.Arrays"; + const string ArrayClassName = nameof(Il2CppArrayRank1<>) + "`1"; + + var il2CppArrayBase = appContext.ResolveTypeOrThrow(typeof(Il2CppArrayRank1<>)); + var il2CppArrayBase_ToManagedArray = il2CppArrayBase.Methods.Single(m => m.Name == "op_Explicit" && m.ReturnType is SzArrayTypeAnalysisContext); + var il2CppArrayBase_FromManagedArray = il2CppArrayBase.Methods.Single(m => m.Name == "op_Explicit" && m.Parameters.Count == 1 && m.Parameters[0].ParameterType is SzArrayTypeAnalysisContext); + + var unsafeClass = appContext.Mscorlib.GetTypeByFullNameOrThrow("System.Runtime.CompilerServices.Unsafe"); + var unsafeAsMethod = unsafeClass.Methods.Single(m => m.Name == nameof(Unsafe.As) && m.GenericParameters.Count == 2); + + var systemObject = appContext.SystemTypes.SystemObjectType; + var il2CppSystemIObject = appContext.Il2CppMscorlib.GetTypeByFullNameOrThrow("Il2CppSystem.IObject"); + + foreach (var assembly in appContext.Assemblies) + { + if (assembly.IsReferenceAssembly || assembly.IsInjected) + continue; + foreach (var type in assembly.Types) + { + if (type.IsInjected) + continue; + + // for instead of foreach because we might be modifying the collection + for (var methodIndex = 0; methodIndex < type.Methods.Count; methodIndex++) + { + var method = type.Methods[methodIndex]; + if (method.IsInjected) + continue; + if (!method.IsPublic || method.IsSpecialName) + continue; // Don't generate overloads for non-public or special name methods + if (method.IsVirtual && !method.IsNewSlot) + continue; // Don't generate overloads for overrides/implementations + + method = method.MostUserFriendlyOverload; + + var anyPossibleConversions = method.Parameters.Any(p => + { + // Convert Il2CppArrayBase to T[] + if (p.ParameterType is GenericInstanceTypeAnalysisContext { GenericType: { Namespace: ArrayNamespace, Name: ArrayClassName } }) + return true; + + // Convert Il2Cpp delegate type to System delegate type + if (p.ParameterType.IsIl2CppDelegate) + { + // If GenericInstanceTypeAnalysisContext held an instantiated list of methods, this would be easier. + var nonGenericType = (p.ParameterType as GenericInstanceTypeAnalysisContext)?.GenericType ?? p.ParameterType; + return nonGenericType.Methods.Any(m => m.Name == "op_Explicit"); + } + + // Convert ref Il2CppSystem.Int32 to ref int + if (p.ParameterType is ByRefTypeAnalysisContext { ElementType.KnownType.IsIl2CppPrimitiveType: true }) + return true; + + // Convert Il2Cpp primitive to System primitive + // Although we include these conversions in the generated overload, they don't justify an overload on their own since implicit conversions exist. + + return false; + }); + if (!anyPossibleConversions) + continue; + + const MethodAttributes AttributesToRemove = MethodAttributes.Virtual | MethodAttributes.Abstract | MethodAttributes.Final | MethodAttributes.NewSlot; + var newMethod = new InjectedMethodAnalysisContext(type, method.Name, appContext.SystemTypes.SystemVoidType, method.Attributes & ~AttributesToRemove, []) + { + IsInjected = true, + }; + type.Methods.Add(newMethod); + + // Have to use the index here because we overwrite the "method" local variable above + type.Methods[methodIndex].MostUserFriendlyOverload = newMethod; + + newMethod.CopyGenericParameters(method, true); + + var visitor = TypeReplacementVisitor.CreateForMethodCopying(method, newMethod); + + TypeAnalysisContext[] parameterTypes = new TypeAnalysisContext[method.Parameters.Count]; + MethodAnalysisContext?[] conversionMethods = new MethodAnalysisContext?[method.Parameters.Count]; + + var conversionCount = 0; + for (var i = 0; i < method.Parameters.Count; i++) + { + var parameter = method.Parameters[i]; + + // Change Il2CppArrayBase to T[] + if (parameter.ParameterType is GenericInstanceTypeAnalysisContext { GenericType: { Namespace: ArrayNamespace, Name: ArrayClassName }, GenericArguments: [var elementType] }) + { + parameterTypes[i] = visitor.Replace(elementType).MakeSzArrayType(); + conversionMethods[i] = il2CppArrayBase_FromManagedArray.MakeConcreteGeneric([elementType], []); + conversionCount++; + continue; + } + + // Change ref Il2CppSystem.Int32 to ref int + if (parameter.ParameterType is ByRefTypeAnalysisContext { ElementType: { KnownType.IsIl2CppPrimitiveType: true } byRefElementType }) + { + var systemPrimitive = byRefElementType.KnownType.ToSystemType().ToContext(appContext); + parameterTypes[i] = systemPrimitive.MakeByReferenceType(); + conversionMethods[i] = unsafeAsMethod.MakeGenericInstanceMethod(byRefElementType, systemPrimitive); + conversionCount++; + continue; + } + + // Change Il2Cpp delegate type to System delegate type + if (parameter.ParameterType.IsIl2CppDelegate) + { + MethodAnalysisContext? explicitConversionMethod; + if (parameter.ParameterType is GenericInstanceTypeAnalysisContext genericInstance) + { + var managedDelegateType = genericInstance.GenericType.ManagedDelegateType; + explicitConversionMethod = genericInstance.GenericType.Methods.SingleOrDefault(m => m.Name == "op_Explicit" && m.Parameters[0].ParameterType == managedDelegateType) + ?.MakeConcreteGeneric(genericInstance.GenericArguments, []); + } + else + { + var managedDelegateType = parameter.ParameterType.ManagedDelegateType; + explicitConversionMethod = parameter.ParameterType.Methods.SingleOrDefault(m => m.Name == "op_Explicit" && m.Parameters[0].ParameterType == managedDelegateType); + } + if (explicitConversionMethod is not null) + { + parameterTypes[i] = explicitConversionMethod.Parameters[0].ParameterType; + conversionMethods[i] = explicitConversionMethod; + conversionCount++; + continue; + } + } + + // Change Il2Cpp primitive to System primitive + { + var knownType = parameter.ParameterType.KnownType; + if (knownType.IsIl2CppPrimitiveType || knownType is KnownTypeCode.Il2CppSystem_String) + { + var systemType = knownType.ToSystemType().ToContext(appContext); + parameterTypes[i] = systemType; + conversionMethods[i] = parameter.ParameterType.GetImplicitConversionFrom(systemType); + conversionCount++; + continue; + } + else if (knownType == KnownTypeCode.Il2CppSystem_IObject) + { + parameterTypes[i] = systemObject; + conversionCount++; + continue; + // No conversion method. We special case this in the loop below. + } + } + + // No conversion + { + parameterTypes[i] = visitor.Replace(parameter.ParameterType); + } + } + + newMethod.SetDefaultReturnType(visitor.Replace(method.ReturnType)); + + for (var i = 0; i < method.Parameters.Count; i++) + { + var parameter = method.Parameters[i]; + + var newParameter = new InjectedParameterAnalysisContext(parameter.Name, parameterTypes[i], parameter.Attributes, parameter.ParameterIndex, newMethod); + newMethod.Parameters.Add(newParameter); + } + + var instructionCount = (newMethod.IsStatic ? 0 : 1) + + newMethod.Parameters.Count + + conversionCount + + 2; + + List instructions = new(instructionCount); + + if (!newMethod.IsStatic) + { + instructions.Add(CilOpCodes.Ldarg_0); // Load "this" + } + + for (var i = 0; i < newMethod.Parameters.Count; i++) + { + instructions.Add(CilOpCodes.Ldarg, newMethod.Parameters[i]); + var conversionMethod = conversionMethods[i]; + if (conversionMethod is not null) + { + instructions.Add(CilOpCodes.Call, conversionMethod); + } + else if (newMethod.Parameters[i].ParameterType == systemObject && method.Parameters[i].ParameterType == il2CppSystemIObject) + { + // Special case for Il2CppSystem.IObject to System.Object conversion, since there's no conversion method for this + instructions.Add(CilOpCodes.Isinst, il2CppSystemIObject); + } + } + + instructions.Add(newMethod.IsStatic || type.IsValueType ? CilOpCodes.Call : CilOpCodes.Callvirt, method.MaybeMakeConcreteGeneric(type.GenericParameters, newMethod.GenericParameters)); + + instructions.Add(CilOpCodes.Ret); + + newMethod.PutExtraData(new NativeMethodBody() + { + Instructions = instructions, + }); + } + } + } + } +} diff --git a/Il2CppInterop.Generator/Usings.cs b/Il2CppInterop.Generator/Usings.cs index 5bf58772..24350aa0 100644 --- a/Il2CppInterop.Generator/Usings.cs +++ b/Il2CppInterop.Generator/Usings.cs @@ -1,3 +1,4 @@ -global using ILProcessor = AsmResolver.DotNet.Code.Cil.CilInstructionCollection; -global using OpCode = AsmResolver.PE.DotNet.Cil.CilOpCode; -global using OpCodes = AsmResolver.PE.DotNet.Cil.CilOpCodes; +global using AsmResolver.PE.DotNet.Cil; +global using Il2CppInterop.Generator.Extensions; +global using Logger = Cpp2IL.Core.Logging.Logger; +global using Object = Il2CppSystem.Object; diff --git a/Il2CppInterop.Generator/Utils/CorlibReferences.cs b/Il2CppInterop.Generator/Utils/CorlibReferences.cs deleted file mode 100644 index cf341c04..00000000 --- a/Il2CppInterop.Generator/Utils/CorlibReferences.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.Utils; - -internal static class CorlibReferences -{ - /// - /// This is used in the TargetFrameworkAttribute. - /// - public static string TargetFrameworkName => ".NET 6.0"; - public static AssemblyReference TargetCorlib => KnownCorLibs.SystemRuntime_v6_0_0_0; - - public static void RewriteCorlibReference(AssemblyReference assemblyNameReference) - { - CopyValues(assemblyNameReference, TargetCorlib); - } - - private static void CopyValues(AssemblyReference target, AssemblyReference source) - { - target.Attributes = source.Attributes; - target.Culture = source.Culture; - target.DisableJitCompileOptimizer = source.DisableJitCompileOptimizer; - target.EnableJitCompileTracking = source.EnableJitCompileTracking; - target.HashValue = source.HashValue?.ToArray(); - target.HasPublicKey = source.HasPublicKey; - target.IsRetargetable = source.IsRetargetable; - target.IsWindowsRuntime = source.IsWindowsRuntime; - target.Name = source.Name; - target.PublicKeyOrToken = source.PublicKeyOrToken?.ToArray(); - target.Version = source.Version; - } - - public static TypeSignature ImportCorlibReference(this ModuleDefinition module, string fullName) - { - return module.DefaultImporter.ImportTypeSignature(typeof(string).Assembly.GetType(fullName)); - } - - public static TypeSignature Void(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Void; - } - - public static TypeSignature Bool(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Boolean; - } - - public static TypeSignature IntPtr(this ModuleDefinition module) - { - return module.CorLibTypeFactory.IntPtr; - } - - public static TypeSignature String(this ModuleDefinition module) - { - return module.CorLibTypeFactory.String; - } - - public static TypeSignature SByte(this ModuleDefinition module) - { - return module.CorLibTypeFactory.SByte; - } - - public static TypeSignature Byte(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Byte; - } - - public static TypeSignature Short(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Int16; - } - - public static TypeSignature Int(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Int32; - } - - public static TypeSignature Long(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Int64; - } - - public static TypeSignature UShort(this ModuleDefinition module) - { - return module.CorLibTypeFactory.UInt16; - } - - public static TypeSignature UInt(this ModuleDefinition module) - { - return module.CorLibTypeFactory.UInt32; - } - - public static TypeSignature ULong(this ModuleDefinition module) - { - return module.CorLibTypeFactory.UInt64; - } - - public static TypeSignature Float(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Single; - } - - public static TypeSignature Double(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Double; - } - - public static TypeSignature Char(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Char; - } - - public static TypeSignature Type(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(Type)); - } - - public static TypeSignature Object(this ModuleDefinition module) - { - return module.CorLibTypeFactory.Object; - } - - public static TypeSignature Enum(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(Enum)); - } - - public static TypeSignature ValueType(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(ValueType)); - } - - public static TypeSignature Delegate(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(Delegate)); - } - - public static TypeSignature MulticastDelegate(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(MulticastDelegate)); - } - - public static TypeSignature DefaultMemberAttribute(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(DefaultMemberAttribute)); - } - - public static TypeSignature NotSupportedException(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(NotSupportedException)); - } - - public static TypeSignature FlagsAttribute(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(FlagsAttribute)); - } - - public static TypeSignature ObsoleteAttribute(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(ObsoleteAttribute)); - } - - public static TypeSignature Attribute(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(Attribute)); - } - - public static TypeSignature RuntimeTypeHandle(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(RuntimeTypeHandle)); - } - - public static TypeSignature ExtensionAttribute(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(ExtensionAttribute)); - } - - public static TypeSignature ParamArrayAttribute(this ModuleDefinition module) - { - return module.DefaultImporter.ImportTypeSignature(typeof(ParamArrayAttribute)); - } - - public static TypeSignature Action(this ModuleDefinition module, int n = 0) - { - return n switch - { - 0 => module.ImportCorlibReference("System.Action"), - 1 => module.ImportCorlibReference("System.Action`1"), - _ => module.ImportCorlibReference($"System.Action`{n}") - }; - } - - public static TypeSignature Func(this ModuleDefinition module, int n = 0) - { - return n switch - { - 0 => module.ImportCorlibReference("System.Func`1"), - 1 => module.ImportCorlibReference("System.Func`2"), - _ => module.ImportCorlibReference($"System.Func`{n + 1}") - }; - } - - public static MemberReference TypeGetTypeFromHandle(this ModuleDefinition module) - { - var type = module.Type(); - MethodSignature signature = MethodSignature.CreateStatic(type, module.RuntimeTypeHandle()); - return new MemberReference(type.ToTypeDefOrRef(), nameof(System.Type.GetTypeFromHandle), signature); - } - - public static MemberReference TypeGetIsValueType(this ModuleDefinition module) - { - var type = module.Type(); - return new MemberReference(type.ToTypeDefOrRef(), "get_IsValueType", MethodSignature.CreateInstance(module.Bool())); - } - - public static MemberReference TypeGetFullName(this ModuleDefinition module) - { - var type = module.Type(); - return new MemberReference(type.ToTypeDefOrRef(), "get_FullName", MethodSignature.CreateInstance(module.String())); - } - - public static MemberReference StringEquals(this ModuleDefinition module) - { - var @string = module.String(); - MethodSignature signature = MethodSignature.CreateStatic(module.Bool(), @string, @string); - return new MemberReference(@string.ToTypeDefOrRef(), nameof(string.Equals), signature); - } - - public static MemberReference ExtensionAttributeCtor(this ModuleDefinition module) - { - return MakeConstructorReference(module, module.ExtensionAttribute().ToTypeDefOrRef()); - } - - public static MemberReference ParamArrayAttributeCtor(this ModuleDefinition module) - { - return MakeConstructorReference(module, module.ParamArrayAttribute().ToTypeDefOrRef()); - } - - public static MemberReference FlagsAttributeCtor(this ModuleDefinition module) - { - return MakeConstructorReference(module, module.FlagsAttribute().ToTypeDefOrRef()); - } - - public static MemberReference NotSupportedExceptionCtor(this ModuleDefinition module) - { - return MakeConstructorReference(module, module.NotSupportedException().ToTypeDefOrRef(), module.String()); - } - - public static MemberReference ObsoleteAttributeCtor(this ModuleDefinition module) - { - return MakeConstructorReference(module, module.ObsoleteAttribute().ToTypeDefOrRef(), module.String()); - } - - private static MemberReference MakeConstructorReference(ModuleDefinition module, ITypeDefOrRef type, params TypeSignature[] parameters) - { - MethodSignature signature = MethodSignature.CreateInstance(module.Void(), parameters); - return new MemberReference(type, ".ctor", signature); - } -} diff --git a/Il2CppInterop.Generator/Utils/FieldAccessorGenerator.cs b/Il2CppInterop.Generator/Utils/FieldAccessorGenerator.cs deleted file mode 100644 index 7a2ffa27..00000000 --- a/Il2CppInterop.Generator/Utils/FieldAccessorGenerator.cs +++ /dev/null @@ -1,130 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Utils; - -internal static class FieldAccessorGenerator -{ - public static void MakeGetter(FieldDefinition field, FieldRewriteContext fieldContext, PropertyDefinition property, - RuntimeAssemblyReferences imports) - { - var attributes = Field2MethodAttrs(field.Attributes) | MethodAttributes.SpecialName | MethodAttributes.HideBySig; - var getter = new MethodDefinition("get_" + property.Name, - attributes, - MethodSignatureCreator.CreateMethodSignature(attributes, property.Signature!.ReturnType, 0)); - - getter.CilMethodBody = new(); - var getterBody = getter.CilMethodBody.Instructions; - property.DeclaringType!.Methods.Add(getter); - - CilLocalVariable local0; - if (field.IsStatic) - { - local0 = new CilLocalVariable(property.Signature.ReturnType.IsValueType() - ? property.Signature.ReturnType - : imports.Module.IntPtr()); - getter.CilMethodBody.LocalVariables.Add(local0); - - var localIsPointer = false; - if (field.Signature!.FieldType.IsValueType() && !property.Signature.ReturnType.IsValueType()) - { - var pointerStore = imports.Il2CppClassPointerStore.MakeGenericInstanceType(property.Signature.ReturnType).ToTypeDefOrRef(); - var pointerStoreType = property.DeclaringType.DeclaringModule!.DefaultImporter.ImportType(pointerStore); - getterBody.Add(OpCodes.Ldsfld, - new MemberReference(pointerStoreType, "NativeClassPtr", new FieldSignature(imports.Module.IntPtr()))); - getterBody.Add(OpCodes.Ldc_I4, 0); - getterBody.Add(OpCodes.Conv_U); - getterBody.Add(OpCodes.Call, imports.IL2CPP_il2cpp_class_value_size.Value); - getterBody.Add(OpCodes.Conv_U); - getterBody.Add(OpCodes.Localloc); - getterBody.Add(OpCodes.Stloc, local0); - localIsPointer = true; - } - - getterBody.Add(OpCodes.Ldsfld, fieldContext.PointerField); - if (localIsPointer) - getterBody.Add(OpCodes.Ldloc, local0); - else - getterBody.Add(OpCodes.Ldloca_S, local0); - getterBody.Add(OpCodes.Conv_U); - getterBody.Add(OpCodes.Call, imports.IL2CPP_il2cpp_field_static_get_value.Value); - - if (property.Signature.ReturnType.IsValueType()) - { - getterBody.Add(OpCodes.Ldloc, local0); - getterBody.Add(OpCodes.Ret); - - property.GetMethod = getter; - return; - } - } - else - { - local0 = new CilLocalVariable(imports.Module.IntPtr()); - getter.CilMethodBody.LocalVariables.Add(local0); - - getterBody.EmitObjectToPointer(fieldContext.DeclaringType.OriginalType.ToTypeSignature(), fieldContext.DeclaringType.NewType.ToTypeSignature(), - fieldContext.DeclaringType, 0, false, false, false, false, out _); - getterBody.Add(OpCodes.Ldsfld, fieldContext.PointerField); - getterBody.Add(OpCodes.Call, imports.IL2CPP_il2cpp_field_get_offset.Value); - getterBody.Add(OpCodes.Add); - - getterBody.Add(OpCodes.Stloc_0); - } - - getterBody.EmitPointerToObject(fieldContext.OriginalField.Signature!.FieldType, property.Signature.ReturnType, - fieldContext.DeclaringType, local0, !field.IsStatic, false); - - if (property.Signature.ReturnType.IsPointerLike()) - getterBody.Add(OpCodes.Ldind_I); - - getterBody.Add(OpCodes.Ret); - - property.GetMethod = getter; - } - - public static void MakeSetter(FieldDefinition field, FieldRewriteContext fieldContext, PropertyDefinition property, - RuntimeAssemblyReferences imports) - { - var attributes = Field2MethodAttrs(field.Attributes) | MethodAttributes.SpecialName | MethodAttributes.HideBySig; - var setter = new MethodDefinition("set_" + property.Name, - attributes, - MethodSignatureCreator.CreateMethodSignature(attributes, imports.Module.Void(), 0, property.Signature!.ReturnType)); - property.DeclaringType!.Methods.Add(setter); - setter.CilMethodBody = new(); - var setterBody = setter.CilMethodBody.Instructions; - - if (field.IsStatic) - { - setterBody.Add(OpCodes.Ldsfld, fieldContext.PointerField); - setterBody.EmitObjectToPointer(field.Signature!.FieldType, property.Signature.ReturnType, fieldContext.DeclaringType, 0, false, - true, true, true, out _); - setterBody.Add(OpCodes.Call, imports.IL2CPP_il2cpp_field_static_set_value.Value); - } - else - { - setterBody.EmitObjectToPointer(fieldContext.DeclaringType.OriginalType.ToTypeSignature(), fieldContext.DeclaringType.NewType.ToTypeSignature(), - fieldContext.DeclaringType, 0, false, false, false, false, out _); - setterBody.Add(OpCodes.Dup); - setterBody.Add(OpCodes.Ldsfld, fieldContext.PointerField); - setterBody.Add(OpCodes.Call, imports.IL2CPP_il2cpp_field_get_offset.Value); - setterBody.Add(OpCodes.Add); - setterBody.EmitObjectStore(field.Signature!.FieldType, property.Signature.ReturnType, fieldContext.DeclaringType, 1); - } - - setterBody.Add(OpCodes.Ret); - - property.SetMethod = setter; - } - - private static MethodAttributes Field2MethodAttrs(FieldAttributes fieldAttributes) - { - if ((fieldAttributes & FieldAttributes.Static) != 0) - return MethodAttributes.Public | MethodAttributes.Static; - return MethodAttributes.Public; - } -} diff --git a/Il2CppInterop.Generator/Utils/Memoize.cs b/Il2CppInterop.Generator/Utils/Memoize.cs deleted file mode 100644 index bbab4b7b..00000000 --- a/Il2CppInterop.Generator/Utils/Memoize.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Il2CppInterop.Generator.Utils; - -public class Memoize where TParam : notnull -{ - private readonly Dictionary _cache = new(); - private readonly Func _func; - - public Memoize(Func func) => _func = func; - - public TResult Get(TParam param) - { - if (_cache.TryGetValue(param.GetHashCode(), out var result)) - { - return result; - } - - result = _func(param); - _cache.Add(param.GetHashCode(), result); - return result; - } -} diff --git a/Il2CppInterop.Generator/Utils/MethodSignatureCreator.cs b/Il2CppInterop.Generator/Utils/MethodSignatureCreator.cs deleted file mode 100644 index 023ca43d..00000000 --- a/Il2CppInterop.Generator/Utils/MethodSignatureCreator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; - -namespace Il2CppInterop.Generator.Utils; - -internal static class MethodSignatureCreator -{ - public static MethodSignature CreateMethodSignature(MethodAttributes attributes, TypeSignature returnType, int genericParameterCount, params TypeSignature[] parameterTypes) - { - return CreateMethodSignature((attributes & MethodAttributes.Static) != 0, returnType, genericParameterCount, parameterTypes); - } - - public static MethodSignature CreateMethodSignature(bool isStatic, TypeSignature returnType, int genericParameterCount, params TypeSignature[] parameterTypes) - { - return isStatic ? MethodSignature.CreateStatic(returnType, genericParameterCount, parameterTypes) : MethodSignature.CreateInstance(returnType, genericParameterCount, parameterTypes); - } -} diff --git a/Il2CppInterop.Generator/Utils/ReferenceCreator.cs b/Il2CppInterop.Generator/Utils/ReferenceCreator.cs deleted file mode 100644 index b9272be6..00000000 --- a/Il2CppInterop.Generator/Utils/ReferenceCreator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AsmResolver; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; - -namespace Il2CppInterop.Generator.Utils; -internal static class ReferenceCreator -{ - public static MemberReference CreateFieldReference(Utf8String? name, TypeSignature fieldType, IMemberRefParent? parent) - { - return new MemberReference(parent, name, new FieldSignature(fieldType)); - } - - public static MemberReference CreateInstanceMethodReference(Utf8String? name, TypeSignature returnType, IMemberRefParent? parent, params TypeSignature[] parameterTypes) - { - return new MemberReference(parent, name, MethodSignature.CreateInstance(returnType, parameterTypes)); - } - - public static MemberReference CreateStaticMethodReference(Utf8String? name, TypeSignature returnType, IMemberRefParent? parent, params TypeSignature[] parameterTypes) - { - return new MemberReference(parent, name, MethodSignature.CreateStatic(returnType, parameterTypes)); - } -} diff --git a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs deleted file mode 100644 index fbdfc204..00000000 --- a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs +++ /dev/null @@ -1,516 +0,0 @@ -// ReSharper disable InconsistentNaming - -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using Il2CppInterop.Common.Attributes; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Utils; - -public class RuntimeAssemblyReferences -{ - private readonly Dictionary allTypes = new(); - private readonly RewriteGlobalContext globalCtx; - - public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext globalContext) - { - Module = module; - globalCtx = globalContext; - InitTypeRefs(); - InitMethodRefs(); - } - - public ModuleDefinition Module { get; } -#nullable disable - public Memoize Il2CppRefrenceArrayctor { get; private set; } - public Lazy Il2CppStringArrayctor { get; private set; } - public Memoize Il2CppStructArrayctor { get; private set; } - public Memoize Il2CppRefrenceArrayctor_size { get; private set; } - public Lazy Il2CppStringArrayctor_size { get; private set; } - public Memoize Il2CppStructArrayctor_size { get; private set; } - public Lazy Il2CppArrayBase_get_Length { get; private set; } - public Memoize Il2CppArrayBase_get_Item { get; private set; } - public Memoize Il2CppArrayBase_set_Item { get; private set; } - public Lazy IL2CPP_Il2CppObjectBaseToPtr { get; private set; } - public Lazy IL2CPP_Il2CppObjectBaseToPtrNotNull { get; private set; } - public Lazy IL2CPP_Il2CppStringToManaged { get; private set; } - public Lazy IL2CPP_ManagedStringToIl2Cpp { get; private set; } - public Lazy Il2CppObjectBase_Cast { get; private set; } - public Lazy Il2CppObjectBase_TryCast { get; private set; } - public Lazy Il2CppObjectPool_Get { get; private set; } - public Lazy IL2CPP_ResolveICall { get; private set; } - public Lazy IL2CPP_il2cpp_gc_wbarrier_set_field { get; private set; } - public Lazy IL2CPP_FieldWriteWbarrierStub { get; private set; } - public Lazy IL2CPP_il2cpp_field_get_offset { get; private set; } - public Lazy IL2CPP_il2cpp_field_static_get_value { get; private set; } - public Lazy IL2CPP_il2cpp_field_static_set_value { get; private set; } - public Lazy IL2CPP_il2cpp_runtime_invoke { get; private set; } - public Lazy IL2CPP_il2cpp_runtime_class_init { get; private set; } - public Lazy IL2CPP_il2cpp_object_unbox { get; private set; } - public Lazy IL2CPP_il2cpp_value_box { get; private set; } - public Lazy IL2CPP_il2cpp_class_value_size { get; private set; } - public Lazy IL2CPP_il2cpp_object_get_class { get; private set; } - public Lazy IL2CPP_il2cpp_class_is_valuetype { get; private set; } - public Lazy Il2CppException_RaiseExceptionIfNecessary { get; private set; } - public Lazy IL2CPP_il2cpp_object_get_virtual_method { get; private set; } - public Lazy IL2CPP_GetIl2CppField { get; private set; } - public Lazy IL2CPP_GetIl2CppNestedType { get; private set; } - public Lazy IL2CPP_GetIl2CppClass { get; private set; } - public Lazy IL2CPP_GetIl2CppMethod { get; private set; } - public Lazy IL2CPP_GetIl2CppMethodByToken { get; private set; } - public Lazy IL2CPP_il2cpp_class_get_type { get; private set; } - public Lazy IL2CPP_il2cpp_class_from_type { get; private set; } - public Lazy IL2CPP_il2cpp_object_new { get; private set; } - public Lazy IL2CPP_il2cpp_method_get_from_reflection { get; private set; } - public Lazy IL2CPP_il2cpp_method_get_object { get; private set; } - public Lazy IL2CPP_PointerToValueGeneric { get; private set; } - public Lazy IL2CPP_RenderTypeName { get; private set; } - public Lazy OriginalNameAttributector { get; private set; } - public Lazy ObfuscatedNameAttributector { get; private set; } - public Lazy CallerCountAttributector { get; private set; } - public Lazy CachedScanResultsAttributector { get; private set; } - public Lazy Il2CppSystemDelegateCombine { get; private set; } - public Lazy Il2CppSystemDelegateRemove { get; private set; } - public Lazy Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle { get; private set; } - - public IMethodDescriptor WriteFieldWBarrier => globalCtx.HasGcWbarrierFieldWrite - ? IL2CPP_il2cpp_gc_wbarrier_set_field.Value - : IL2CPP_FieldWriteWbarrierStub.Value; - - public TypeSignature Il2CppObjectBase { get; private set; } - public TypeSignature Il2CppObjectPool { get; private set; } - public TypeSignature Il2CppStringArray { get; private set; } - public TypeSignature Il2CppArrayBase { get; private set; } - public TypeSignature Il2CppStructArray { get; private set; } - public TypeSignature Il2CppReferenceArray { get; private set; } - public TypeSignature Il2CppClassPointerStore { get; private set; } - public TypeSignature Il2Cpp { get; set; } - public TypeSignature RuntimeReflectionHelper { get; private set; } - public TypeSignature DelegateSupport { get; private set; } - public TypeSignature Il2CppException { get; private set; } -#nullable enable - private TypeSignature ResolveType(string typeName) - { - return allTypes[typeName]; - } - - private void InitTypeRefs() - { - allTypes["System.Void"] = Module.DefaultImporter.ImportTypeSignature(typeof(void)); - allTypes["System.String[]"] = Module.DefaultImporter.ImportTypeSignature(typeof(string[])); - allTypes["System.IntPtr"] = Module.DefaultImporter.ImportTypeSignature(typeof(IntPtr)); - allTypes["System.String"] = Module.DefaultImporter.ImportTypeSignature(typeof(string)); - allTypes["System.UInt32"] = Module.DefaultImporter.ImportTypeSignature(typeof(uint)); - allTypes["System.Void*"] = Module.DefaultImporter.ImportTypeSignature(typeof(void*)); - allTypes["System.Void**"] = Module.DefaultImporter.ImportTypeSignature(typeof(void**)); - allTypes["System.IntPtr&"] = Module.DefaultImporter.ImportTypeSignature(typeof(IntPtr).MakeByRefType()); - allTypes["System.Int32"] = Module.DefaultImporter.ImportTypeSignature(typeof(int)); - allTypes["System.UInt32&"] = Module.DefaultImporter.ImportTypeSignature(typeof(uint).MakeByRefType()); - allTypes["System.Boolean"] = Module.DefaultImporter.ImportTypeSignature(typeof(bool)); - allTypes["System.Int64"] = Module.DefaultImporter.ImportTypeSignature(typeof(long)); - - var assemblyRef = new AssemblyReference("Il2CppInterop.Runtime", new Version(0, 0, 0, 0)); - Module.AssemblyReferences.Add(assemblyRef); - - Il2CppObjectBase = - new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.InteropTypes", "Il2CppObjectBase").ToTypeSignature(); - - Il2CppObjectPool = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.Runtime", "Il2CppObjectPool").ToTypeSignature(); - - Il2CppStringArray = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.InteropTypes.Arrays", "Il2CppStringArray").ToTypeSignature(); - - Il2CppArrayBase = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.InteropTypes.Arrays", "Il2CppArrayBase`1").ToTypeSignature(); - - var nonGenericIl2CppArrayBase = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.InteropTypes.Arrays", "Il2CppArrayBase").ToTypeSignature(); - - var genericIl2CppArrayBase = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.InteropTypes.Arrays", "Il2CppArrayBase`1").ToTypeSignature(); - - Il2CppStructArray = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.InteropTypes.Arrays", "Il2CppStructArray`1").ToTypeSignature(); - - Il2CppReferenceArray = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime.InteropTypes.Arrays", "Il2CppReferenceArray`1").ToTypeSignature(); - - Il2CppClassPointerStore = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime", "Il2CppClassPointerStore`1").ToTypeSignature(); - - Il2Cpp = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime", "IL2CPP").ToTypeSignature(); - - RuntimeReflectionHelper = - new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime", "RuntimeReflectionHelper").ToTypeSignature(); - - DelegateSupport = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime", "DelegateSupport").ToTypeSignature(); - - Il2CppException = new TypeReference(Module, assemblyRef, "Il2CppInterop.Runtime", "Il2CppException").ToTypeSignature(); - - allTypes["Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase"] = Il2CppObjectBase; - allTypes["Il2CppInterop.Runtime.Runtime.Il2CppObjectPool"] = Il2CppObjectPool; - allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"] = nonGenericIl2CppArrayBase; - allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"] = genericIl2CppArrayBase; - allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray"] = Il2CppStringArray; - allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray"] = Il2CppReferenceArray; - allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray"] = Il2CppStructArray; - allTypes["Il2CppInterop.Runtime.Il2CppException"] = Il2CppException; - allTypes["Il2CppInterop.Runtime.IL2CPP"] = Il2Cpp; - } - - private void InitMethodRefs() - { - Il2CppRefrenceArrayctor = new((param) => - { - var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray"); - var gp = new GenericParameterSignature(GenericParameterType.Type, 0); - var giOwner = owner.MakeGenericInstanceType(param).ToTypeDefOrRef(); - return ReferenceCreator.CreateInstanceMethodReference(".ctor", ResolveType("System.Void"), - giOwner, gp.MakeSzArrayType()); - }); - - Il2CppStringArrayctor = new Lazy(() => - { - return ReferenceCreator.CreateInstanceMethodReference(".ctor", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray").ToTypeDefOrRef(), ResolveType("System.String[]")); - }); - - Il2CppStructArrayctor = new((param) => - { - var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray"); - var gp = new GenericParameterSignature(GenericParameterType.Type, 0); - var giOwner = new GenericInstanceTypeSignature(owner.ToTypeDefOrRef(), false); - giOwner.TypeArguments.Add(param); - var mr = ReferenceCreator.CreateInstanceMethodReference(".ctor", ResolveType("System.Void"), - giOwner.ToTypeDefOrRef()); - var paramType = gp.MakeSzArrayType(); - ((MethodSignature)mr.Signature!).ParameterTypes.Add(paramType); - return mr; - }); - - Il2CppRefrenceArrayctor_size = new((param) => - { - var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray"); - var giOwner = new GenericInstanceTypeSignature(owner.ToTypeDefOrRef(), false); - giOwner.TypeArguments.Add(param); - var mr = ReferenceCreator.CreateInstanceMethodReference(".ctor", ResolveType("System.Void"), - giOwner.ToTypeDefOrRef(), ResolveType("System.Int64")); - return mr; - }); - - Il2CppStringArrayctor_size = new Lazy(() => - { - var mr = ReferenceCreator.CreateInstanceMethodReference(".ctor", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray").ToTypeDefOrRef(), ResolveType("System.Int64")); - return mr; - }); - - Il2CppStructArrayctor_size = new((param) => - { - var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray"); - var giOwner = owner.MakeGenericInstanceType(param).ToTypeDefOrRef(); - var mr = ReferenceCreator.CreateInstanceMethodReference(".ctor", ResolveType("System.Void"), - giOwner, ResolveType("System.Int64")); - return mr; - }); - - Il2CppArrayBase_get_Length = new Lazy(() => - { - var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"); - var mr = ReferenceCreator.CreateInstanceMethodReference("get_Length", ResolveType("System.Int32"), - owner.ToTypeDefOrRef()); - return mr; - }); - - Il2CppArrayBase_get_Item = new((param) => - { - var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"); - var giOwner = owner.MakeGenericInstanceType(param).ToTypeDefOrRef(); - var mr = ReferenceCreator.CreateInstanceMethodReference("get_Item", new GenericParameterSignature(Module, GenericParameterType.Type, 0), - giOwner, ResolveType("System.Int32")); - return mr; - }); - - Il2CppArrayBase_set_Item = new((param) => - { - var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"); - var giOwner = owner.MakeGenericInstanceType(param).ToTypeDefOrRef(); - var mr = ReferenceCreator.CreateInstanceMethodReference("set_Item", Module.Void(), - giOwner, ResolveType("System.Int32"), new GenericParameterSignature(Module, GenericParameterType.Type, 0)); - return mr; - }); - - IL2CPP_Il2CppObjectBaseToPtr = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("Il2CppObjectBaseToPtr", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase")); - return mr; - }); - - IL2CPP_Il2CppObjectBaseToPtrNotNull = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("Il2CppObjectBaseToPtrNotNull", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), - ResolveType("Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase")); - return mr; - }); - - IL2CPP_Il2CppStringToManaged = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("Il2CppStringToManaged", ResolveType("System.String"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_ManagedStringToIl2Cpp = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("ManagedStringToIl2Cpp", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.String")); - return mr; - }); - - Il2CppObjectBase_Cast = new Lazy(() => - { - var gp0 = new GenericParameterSignature(GenericParameterType.Method, 0); - var signature = MethodSignature.CreateInstance(gp0, 1); - var mr = new MemberReference(ResolveType("Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase").ToTypeDefOrRef(), "Cast", signature); - return mr; - }); - - Il2CppObjectBase_TryCast = new Lazy(() => - { - var gp0 = new GenericParameterSignature(GenericParameterType.Method, 0); - var signature = MethodSignature.CreateInstance(gp0, 1); - var mr = new MemberReference(ResolveType("Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase").ToTypeDefOrRef(), "TryCast", signature); - return mr; - }); - - Il2CppObjectPool_Get = new Lazy(() => - { - var gp0 = new GenericParameterSignature(GenericParameterType.Method, 0); - var signature = MethodSignature.CreateStatic(gp0, 1, ResolveType("System.IntPtr")); - var mr = new MemberReference(ResolveType("Il2CppInterop.Runtime.Runtime.Il2CppObjectPool").ToTypeDefOrRef(), "Get", signature); - return mr; - }); - - IL2CPP_ResolveICall = new Lazy(() => - { - var gp0 = new GenericParameterSignature(GenericParameterType.Method, 0); - var signature = MethodSignature.CreateStatic(gp0, 1, ResolveType("System.String")); - var mr = new MemberReference(ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), "ResolveICall", signature); - return mr; - }); - - IL2CPP_il2cpp_gc_wbarrier_set_field = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_gc_wbarrier_set_field", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.IntPtr"), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_FieldWriteWbarrierStub = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("FieldWriteWbarrierStub", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.IntPtr"), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_field_get_offset = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_field_get_offset", ResolveType("System.UInt32"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_field_static_get_value = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_field_static_get_value", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.Void*")); - return mr; - }); - - IL2CPP_il2cpp_field_static_set_value = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_field_static_set_value", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.Void*")); - return mr; - }); - - IL2CPP_il2cpp_runtime_invoke = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_runtime_invoke", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.IntPtr"), ResolveType("System.Void**"), ResolveType("System.IntPtr&")); - return mr; - }); - - IL2CPP_il2cpp_runtime_class_init = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_runtime_class_init", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_object_unbox = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_object_unbox", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_value_box = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_value_box", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_class_value_size = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_class_value_size", ResolveType("System.Int32"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.UInt32&")); - return mr; - }); - - IL2CPP_il2cpp_object_get_class = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_object_get_class", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_class_is_valuetype = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_class_is_valuetype", ResolveType("System.Boolean"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - Il2CppException_RaiseExceptionIfNecessary = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("RaiseExceptionIfNecessary", ResolveType("System.Void"), - ResolveType("Il2CppInterop.Runtime.Il2CppException").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_object_get_virtual_method = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_object_get_virtual_method", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_GetIl2CppField = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("GetIl2CppField", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.String")); - return mr; - }); - - IL2CPP_GetIl2CppNestedType = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("GetIl2CppNestedType", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.String")); - return mr; - }); - - IL2CPP_GetIl2CppClass = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("GetIl2CppClass", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.String"), ResolveType("System.String"), ResolveType("System.String")); - return mr; - }); - - IL2CPP_GetIl2CppMethod = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("GetIl2CppMethod", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), - ResolveType("System.IntPtr"), ResolveType("System.Boolean"), ResolveType("System.String"), ResolveType("System.String"), ResolveType("System.String[]")); - return mr; - }); - - IL2CPP_GetIl2CppMethodByToken = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("GetIl2CppMethodByToken", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.Int32")); - return mr; - }); - - IL2CPP_il2cpp_class_get_type = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_class_get_type", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_class_from_type = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_class_from_type", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_object_new = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_object_new", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_method_get_from_reflection = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_method_get_from_reflection", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_il2cpp_method_get_object = new Lazy(() => - { - var mr = ReferenceCreator.CreateStaticMethodReference("il2cpp_method_get_object", ResolveType("System.IntPtr"), - ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), ResolveType("System.IntPtr"), ResolveType("System.IntPtr")); - return mr; - }); - - IL2CPP_PointerToValueGeneric = new Lazy(() => - { - var gp0 = new GenericParameterSignature(GenericParameterType.Method, 0); - var signature = MethodSignature.CreateStatic(gp0, 1, ResolveType("System.IntPtr"), ResolveType("System.Boolean"), ResolveType("System.Boolean")); - var mr = new MemberReference(ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), "PointerToValueGeneric", signature); - return mr; - }); - - IL2CPP_RenderTypeName = new Lazy(() => - { - var gp0 = new GenericParameterSignature(GenericParameterType.Method, 0); - var signature = MethodSignature.CreateStatic(ResolveType("System.String"), 1, ResolveType("System.Boolean")); - var mr = new MemberReference(ResolveType("Il2CppInterop.Runtime.IL2CPP").ToTypeDefOrRef(), "RenderTypeName", signature); - return mr; - }); - - OriginalNameAttributector = new Lazy(() => ReferenceCreator.CreateInstanceMethodReference(".ctor", - Module.Void(), - Module.DefaultImporter.ImportType(typeof(OriginalNameAttribute)), Module.String(), Module.String(), Module.String())); - - ObfuscatedNameAttributector = new Lazy(() => ReferenceCreator.CreateInstanceMethodReference(".ctor", - Module.Void(), - Module.DefaultImporter.ImportType(typeof(ObfuscatedNameAttribute)), Module.String())); - - CallerCountAttributector = new Lazy(() => - ReferenceCreator.CreateInstanceMethodReference(".ctor", Module.Void(), Module.DefaultImporter.ImportType(typeof(CallerCountAttribute)), Module.Int())); - - CachedScanResultsAttributector = new Lazy(() => - ReferenceCreator.CreateInstanceMethodReference(".ctor", Module.Void(), - Module.DefaultImporter.ImportType(typeof(CachedScanResultsAttribute)))); - - Il2CppSystemDelegateCombine = new Lazy(() => - Module.DefaultImporter.ImportMethod(globalCtx.GetAssemblyByName("mscorlib").NewAssembly.ManifestModule! - .GetType("Il2CppSystem.Delegate").Methods.Single(m => m.Name == "Combine" && m.Parameters.Count == 2))); - - Il2CppSystemDelegateRemove = new Lazy(() => - Module.DefaultImporter.ImportMethod(globalCtx.GetAssemblyByName("mscorlib").NewAssembly.ManifestModule! - .GetType("Il2CppSystem.Delegate").Methods.Single(m => m.Name == "Remove"))); - - Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle = new Lazy(() => - { - var declaringTypeRef = RuntimeReflectionHelper; - var returnTypeRef = Module.DefaultImporter.ImportType(globalCtx.GetAssemblyByName("mscorlib").NewAssembly.ManifestModule! - .GetType("Il2CppSystem.RuntimeTypeHandle")); - var signature = MethodSignature.CreateStatic(returnTypeRef.ToTypeSignature(), 1); - var methodReference = new MemberReference(declaringTypeRef.ToTypeDefOrRef(), "GetRuntimeTypeHandle", signature); - return Module.DefaultImporter.ImportMethod(methodReference); - }); - } -} diff --git a/Il2CppInterop.Generator/Utils/TimingCookie.cs b/Il2CppInterop.Generator/Utils/TimingCookie.cs deleted file mode 100644 index 9eed28bc..00000000 --- a/Il2CppInterop.Generator/Utils/TimingCookie.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Diagnostics; -using Il2CppInterop.Common; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Utils; - -internal readonly struct TimingCookie : IDisposable -{ - private readonly Stopwatch myStopwatch; - - public TimingCookie(string message) - { - Logger.Instance.LogInformation("{Message}...", message); - myStopwatch = Stopwatch.StartNew(); - } - - public void Dispose() - { - Logger.Instance.LogInformation("Done in {Elapsed}", myStopwatch.Elapsed); - } -} diff --git a/Il2CppInterop.Generator/Utils/UniquificationContext.cs b/Il2CppInterop.Generator/Utils/UniquificationContext.cs deleted file mode 100644 index 662654ef..00000000 --- a/Il2CppInterop.Generator/Utils/UniquificationContext.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Utils; - -public class UniquificationContext -{ - private readonly GeneratorOptions myGeneratorOptions; - private readonly SortedSet<(string, float)> myPrefixes = new(new Item2Comparer()); - private readonly Dictionary myUniquifiersCount = new(); - - public UniquificationContext(GeneratorOptions generatorOptions) - { - myGeneratorOptions = generatorOptions; - } - - public bool CheckFull() - { - return myUniquifiersCount.Count >= myGeneratorOptions.TypeDeobfuscationMaxUniquifiers; - } - - public void Push(string str, bool noSubstring = false) - { - if (str.IsInvalidInSource()) return; - - var stringPrefix = noSubstring - ? str - : SubstringBounded(str, 0, myGeneratorOptions.TypeDeobfuscationCharsPerUniquifier); - var currentCount = myUniquifiersCount[stringPrefix] = myUniquifiersCount.GetOrCreate(stringPrefix, _ => 0) + 1; - myPrefixes.Add((stringPrefix, myUniquifiersCount.Count + currentCount * 2 + myPrefixes.Count / 100f)); - } - - public void Push(List strings, bool noSubstring = false) - { - foreach (var str in strings) - Push(str, noSubstring); - } - - public string GetTop() - { - return string.Join("", - myPrefixes.Take(myGeneratorOptions.TypeDeobfuscationMaxUniquifiers).Select(it => it.Item1)); - } - - private static string SubstringBounded(string str, int startIndex, int length) - { - length = Math.Min(length, str.Length); - return str.Substring(startIndex, length); - } - - private class Item2Comparer : IComparer<(string, float)> - { - public int Compare((string, float) x, (string, float) y) - { - return x.Item2.CompareTo(y.Item2); - } - } -} diff --git a/Il2CppInterop.Generator/Utils/UnstripGenerator.cs b/Il2CppInterop.Generator/Utils/UnstripGenerator.cs deleted file mode 100644 index 2b7fb54d..00000000 --- a/Il2CppInterop.Generator/Utils/UnstripGenerator.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Text; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Common; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Generator.Utils; - -public static class UnstripGenerator -{ - public static TypeDefinition CreateDelegateTypeForICallMethod(MethodDefinition unityMethod, - MethodDefinition convertedMethod, RuntimeAssemblyReferences imports) - { - var delegateType = new TypeDefinition("", unityMethod.Name + "Delegate", - TypeAttributes.Sealed | TypeAttributes.NestedPrivate, imports.Module.MulticastDelegate().ToTypeDefOrRef()); - - var constructor = new MethodDefinition(".ctor", - MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RuntimeSpecialName | - MethodAttributes.Public, MethodSignature.CreateInstance(imports.Module.Void(), imports.Module.Object(), imports.Module.IntPtr())); - constructor.ImplAttributes = MethodImplAttributes.CodeTypeMask; - delegateType.Methods.Add(constructor); - - var invokeMethod = new MethodDefinition("Invoke", - MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public, - MethodSignature.CreateInstance(imports.Module.Void())); // todo - invokeMethod.ImplAttributes = MethodImplAttributes.CodeTypeMask; - delegateType.Methods.Add(invokeMethod); - - invokeMethod.Signature!.ReturnType = convertedMethod.Signature!.ReturnType.IsValueType() - ? convertedMethod.Signature.ReturnType - : imports.Module.IntPtr(); - if (!convertedMethod.IsStatic) - invokeMethod.AddParameter(imports.Module.IntPtr(), "@this"); - foreach (var convertedParameter in convertedMethod.Parameters) - invokeMethod.AddParameter( - convertedParameter.ParameterType.IsValueType() - ? convertedParameter.ParameterType - : imports.Module.IntPtr(), - convertedParameter.Name, - convertedParameter.Definition!.Attributes & ~ParameterAttributes.Optional); - - return delegateType; - } - - public static void GenerateInvokerMethodBody(MethodDefinition newMethod, FieldDefinition delegateField, - TypeDefinition delegateType, TypeRewriteContext enclosingType, RuntimeAssemblyReferences imports) - { - var body = newMethod.CilMethodBody!.Instructions; - - body.Add(OpCodes.Ldsfld, delegateField); - if (!newMethod.IsStatic) - { - body.Add(OpCodes.Ldarg_0); - body.Add(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtrNotNull.Value); - } - - var argOffset = newMethod.IsStatic ? 0 : 1; - - for (var i = 0; i < newMethod.Parameters.Count; i++) - { - var param = newMethod.Parameters[i]; - var paramType = param.ParameterType; - if (paramType.IsValueType() || (paramType is ByReferenceTypeSignature && paramType.GetElementType().IsValueType())) - { - body.AddLoadArgument(i + argOffset); - } - else - { - body.EmitObjectToPointer(param.ParameterType, param.ParameterType, enclosingType, i + argOffset, false, - true, true, true, out var refVar); - if (refVar != null) - { - Logger.Instance.LogTrace("Method {NewMethod} has a reference-typed ref parameter, this will be ignored", - newMethod.ToString()); - } - } - } - - body.Add(OpCodes.Call, delegateType.Methods.Single(it => it.Name == "Invoke")); - if (!newMethod.Signature!.ReturnType.IsValueTypeLike()) - { - var pointerVar = new CilLocalVariable(imports.Module.IntPtr()); - newMethod.CilMethodBody.LocalVariables.Add(pointerVar); - body.Add(OpCodes.Stloc, pointerVar); - body.EmitPointerToObject(newMethod.Signature.ReturnType, newMethod.Signature.ReturnType, enclosingType, pointerVar, false, - false); - } - - body.Add(OpCodes.Ret); - } - - public static FieldDefinition GenerateStaticCtorSuffix(TypeDefinition enclosingType, TypeDefinition delegateType, - MethodDefinition unityMethod, RuntimeAssemblyReferences imports) - { - var delegateField = new FieldDefinition(delegateType.Name + "Field", - FieldAttributes.Static | FieldAttributes.Private | FieldAttributes.InitOnly, new FieldSignature(delegateType.ToTypeSignature())); - enclosingType.Fields.Add(delegateField); - - var staticCtor = enclosingType.GetOrCreateStaticConstructor(); - - var bodyProcessor = staticCtor.CilMethodBody!.Instructions; - - bodyProcessor.Remove(staticCtor.CilMethodBody.Instructions.Last()); // remove ret - - bodyProcessor.Add(OpCodes.Ldstr, GetICallSignature(unityMethod)); - - var methodRef = imports.IL2CPP_ResolveICall.Value.MakeGenericInstanceMethod(delegateType.ToTypeSignature()); - bodyProcessor.Add(OpCodes.Call, enclosingType.DeclaringModule!.DefaultImporter.ImportMethod(methodRef)); - bodyProcessor.Add(OpCodes.Stsfld, delegateField); - - bodyProcessor.Add(OpCodes.Ret); // restore ret - - return delegateField; - } - - private static string GetICallSignature(MethodDefinition unityMethod) - { - var builder = new StringBuilder(); - builder.Append(unityMethod.DeclaringType!.FullName); - builder.Append("::"); - builder.Append(unityMethod.Name); - - return builder.ToString(); - } -} diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs deleted file mode 100644 index a65934b5..00000000 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ /dev/null @@ -1,500 +0,0 @@ -using System.Diagnostics; -using AsmResolver; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Collections; -using AsmResolver.DotNet.Signatures; -using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Passes; - -namespace Il2CppInterop.Generator.Utils; - -public static class UnstripTranslator -{ - public static bool TranslateMethod(MethodDefinition original, MethodDefinition target, - TypeRewriteContext typeRewriteContext, RuntimeAssemblyReferences imports) - { - if (original.CilMethodBody is null) - return true; - - target.CilMethodBody = new(); - - var globalContext = typeRewriteContext.AssemblyContext.GlobalContext; - Dictionary localVariableMap = new(); - foreach (var variableDefinition in original.CilMethodBody.LocalVariables) - { - var variableType = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, variableDefinition.VariableType, - imports); - if (variableType == null) - return false; - var newVariableDefinition = new CilLocalVariable(variableType); - target.CilMethodBody.LocalVariables.Add(newVariableDefinition); - localVariableMap.Add(variableDefinition, newVariableDefinition); - } - - // We expand macros because our instructions are not mapped one-to-one, - // so specialized instructions like Br_S need to be expanded to Br for safety. - // In pass 90, we optimize all macros, so we won't need to worry about that here. - original.CilMethodBody.Instructions.ExpandMacros(); - - List> labelMap = new(); - Dictionary instructionMap = new(); - - var targetBuilder = target.CilMethodBody.Instructions; - foreach (var bodyInstruction in original.CilMethodBody.Instructions) - { - if (bodyInstruction.Operand is null) - { - CilInstruction newInstruction; - switch (bodyInstruction.OpCode.Code) - { - case CilCode.Ldlen: - //This is Il2CppArrayBase.Length - newInstruction = targetBuilder.Add(OpCodes.Callvirt, - imports.Module.DefaultImporter.ImportMethod(imports.Il2CppArrayBase_get_Length.Value)); - break; - - case CilCode.Ldelem_Ref: - //This is Il2CppReferenceArray.get_Item but the T is not known because the operand is null. - return false; - - case CilCode.Stelem_Ref: - //This is Il2CppReferenceArray.set_Item but the T is not known because the operand is null. - return false; - - case CilCode.Ldelem_I1: - case CilCode.Ldelem_I2: - case CilCode.Ldelem_I4: - case CilCode.Ldelem_U4: - //This is Il2CppArrayBase.get_Item but the T could be either the cooresponding primitive or an enum. - return false; - - case CilCode.Ldelem_U1: - //This is Il2CppArrayBase.get_Item but the T could be either byte, bool, or an enum. - return false; - - case CilCode.Ldelem_U2: - //This is Il2CppArrayBase.get_Item but the T could be either ushort, char, or an enum. - return false; - - case CilCode.Ldelem_I8: - //This is Il2CppArrayBase.get_Item but the T could be either signed, unsigned, or an enum. - return false; - - case CilCode.Ldelem_I: - //This is Il2CppArrayBase.get_Item but the T could be either signed, unsigned, or a pointer. - return false; - - case CilCode.Ldelem_R4: - { - var getMethod = imports.Il2CppArrayBase_get_Item.Get(imports.Module.CorLibTypeFactory.Single); - newInstruction = targetBuilder.Add(OpCodes.Callvirt, imports.Module.DefaultImporter.ImportMethod(getMethod)); - } - break; - - case CilCode.Ldelem_R8: - { - var getMethod = imports.Il2CppArrayBase_get_Item.Get(imports.Module.CorLibTypeFactory.Double); - newInstruction = targetBuilder.Add(OpCodes.Callvirt, imports.Module.DefaultImporter.ImportMethod(getMethod)); - } - break; - - case >= CilCode.Stelem_I and <= CilCode.Stelem_I8: - //This is Il2CppStructArray.set_Item - return false; - - case CilCode.Stelem_R4: - { - var setMethod = imports.Il2CppArrayBase_set_Item.Get(imports.Module.CorLibTypeFactory.Single); - newInstruction = targetBuilder.Add(OpCodes.Callvirt, imports.Module.DefaultImporter.ImportMethod(setMethod)); - } - break; - - case CilCode.Stelem_R8: - { - var setMethod = imports.Il2CppArrayBase_set_Item.Get(imports.Module.CorLibTypeFactory.Double); - newInstruction = targetBuilder.Add(OpCodes.Callvirt, imports.Module.DefaultImporter.ImportMethod(setMethod)); - } - break; - - case >= CilCode.Ldind_I1 and <= CilCode.Ldind_Ref: - //This is for by ref parameters - goto default; - - case >= CilCode.Stind_Ref and <= CilCode.Stind_R8: - //This is for by ref parameters - goto default; - - default: - //Noop, ldnull, ldarg_0, mul, add, etc. - newInstruction = targetBuilder.Add(bodyInstruction.OpCode); - break; - } - - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineField) - { - // This code doesn't handle fields in the corlib types well. - // Static fields are fine, but references to instance fields can't be redirected. - - var fieldArg = (IFieldDescriptor)bodyInstruction.Operand; - var useSystemCorlibType = fieldArg.Signature?.HasThis ?? true; - var fieldDeclarer = - Pass80UnstripMethods.ResolveTypeInNewAssembliesRaw(globalContext, fieldArg.DeclaringType!.ToTypeSignature(), imports, useSystemCorlibType); - if (fieldDeclarer == null) - return false; - var fieldDeclarerDefinition = fieldDeclarer.Resolve(); - if (fieldDeclarerDefinition == null) - return false; - - var fieldDeclarerContext = globalContext.GetContextForNewType(fieldDeclarerDefinition); - var propertyName = fieldDeclarerContext.Fields.SingleOrDefault(it => it.OriginalField.Name == fieldArg.Name)?.UnmangledName; - - var newField = fieldDeclarerDefinition.Fields.SingleOrDefault(it => it.Name == fieldArg.Name) - ?? fieldDeclarerDefinition.Fields.SingleOrDefault(it => it.Name == propertyName); - if (newField != null) - { - var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, imports.Module.DefaultImporter.ImportField(newField)); - instructionMap.Add(bodyInstruction, newInstruction); - } - else - { - if (propertyName == null) - { - return false; - } - else if (bodyInstruction.OpCode == OpCodes.Ldfld || bodyInstruction.OpCode == OpCodes.Ldsfld) - { - var getterMethod = fieldDeclarerDefinition.Properties - .SingleOrDefault(it => it.Name == propertyName)?.GetMethod; - if (getterMethod == null) - return false; - - var newInstruction = targetBuilder.Add(OpCodes.Call, imports.Module.DefaultImporter.ImportMethod(getterMethod)); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.OpCode == OpCodes.Stfld || bodyInstruction.OpCode == OpCodes.Stsfld) - { - var setterMethod = fieldDeclarerDefinition.Properties - .SingleOrDefault(it => it.Name == propertyName)?.SetMethod; - if (setterMethod == null) - return false; - - var newInstruction = targetBuilder.Add(OpCodes.Call, imports.Module.DefaultImporter.ImportMethod(setterMethod)); - instructionMap.Add(bodyInstruction, newInstruction); - } - else - { - //Ldflda, Ldsflda - return false; - } - } - } - else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineMethod) - { - // This code doesn't handle methods in the corlib types well. - // Static methods are fine, but references to instance methods can't be redirected. - - var methodArg = (IMethodDescriptor)bodyInstruction.Operand; - var useSystemCorlibType = methodArg.Signature?.HasThis ?? true; - var methodDeclarer = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, methodArg.DeclaringType?.ToTypeSignature(), imports, useSystemCorlibType); - if (methodDeclarer == null) - return false; - - var newReturnType = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, methodArg.Signature?.ReturnType, imports); - if (newReturnType == null) - return false; - - var newMethodSignature = methodArg.Signature!.HasThis - ? MethodSignature.CreateInstance(newReturnType, methodArg.Signature.GenericParameterCount) - : MethodSignature.CreateStatic(newReturnType, methodArg.Signature.GenericParameterCount); - foreach (var methodArgParameter in methodArg.Signature.ParameterTypes) - { - var newParamType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, - methodArgParameter, imports); - if (newParamType == null) - return false; - - newMethodSignature.ParameterTypes.Add(newParamType); - } - - var memberReference = new MemberReference(methodDeclarer.ToTypeDefOrRef(), methodArg.Name, newMethodSignature); - - IMethodDescriptor newMethod; - if (methodArg is MethodSpecification genericMethod) - { - if (genericMethod.Signature is null) - return false; - - TypeSignature[] typeArguments = new TypeSignature[genericMethod.Signature.TypeArguments.Count]; - for (var i = 0; i < genericMethod.Signature.TypeArguments.Count; i++) - { - var newTypeArgument = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, genericMethod.Signature.TypeArguments[i], imports); - if (newTypeArgument == null) - return false; - - typeArguments[i] = newTypeArgument; - } - - newMethod = memberReference.MakeGenericInstanceMethod(typeArguments); - } - else - { - newMethod = memberReference; - } - - var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, imports.Module.DefaultImporter.ImportMethod(newMethod)); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineType) - { - var targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, ((ITypeDefOrRef)bodyInstruction.Operand).ToTypeSignature(), imports); - if (targetType == null) - return false; - - if ((bodyInstruction.OpCode == OpCodes.Castclass && !targetType.IsValueType) || - (bodyInstruction.OpCode == OpCodes.Unbox_Any && targetType is GenericParameterSignature)) - { - // Compilers use unbox.any for casting to generic parameter types. - // Castclass is only used for reference types. - // Both can be translated to Il2CppObjectBase.Cast(). - var newInstruction = targetBuilder.Add(OpCodes.Call, - imports.Module.DefaultImporter.ImportMethod(imports.Il2CppObjectBase_Cast.Value.MakeGenericInstanceMethod(targetType))); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.OpCode == OpCodes.Isinst && !targetType.IsValueType) - { - var newInstruction = targetBuilder.Add(OpCodes.Call, - imports.Module.DefaultImporter.ImportMethod(imports.Il2CppObjectBase_TryCast.Value.MakeGenericInstanceMethod(targetType))); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.OpCode == OpCodes.Newarr) - { - var newInstruction = targetBuilder.Add(OpCodes.Conv_I8); - - ITypeDefOrRef il2cppTypeArray; - if (targetType.IsValueType) - { - return false; - } - else if (targetType.FullName == "System.String") - { - il2cppTypeArray = imports.Il2CppStringArray.ToTypeDefOrRef(); - } - else - { - il2cppTypeArray = imports.Il2CppReferenceArray.MakeGenericInstanceType(targetType).ToTypeDefOrRef(); - } - targetBuilder.Add(OpCodes.Newobj, imports.Module.DefaultImporter.ImportMethod( - ReferenceCreator.CreateInstanceMethodReference(".ctor", imports.Module.Void(), il2cppTypeArray, imports.Module.Long()))); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.OpCode == OpCodes.Ldelema) - { - // Not implemented - return false; - } - else if (bodyInstruction.OpCode == OpCodes.Ldelem) - { - var getMethod = imports.Il2CppArrayBase_get_Item.Get(targetType); - var newInstruction = targetBuilder.Add(OpCodes.Callvirt, imports.Module.DefaultImporter.ImportMethod(getMethod)); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.OpCode == OpCodes.Stelem) - { - var setMethod = imports.Il2CppArrayBase_set_Item.Get(targetType); - var newInstruction = targetBuilder.Add(OpCodes.Callvirt, imports.Module.DefaultImporter.ImportMethod(setMethod)); - instructionMap.Add(bodyInstruction, newInstruction); - } - else - { - var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, targetType.ToTypeDefOrRef()); - instructionMap.Add(bodyInstruction, newInstruction); - } - } - else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineSig) - { - // todo: rewrite sig if this ever happens in unity types - return false; - } - else if (bodyInstruction.OpCode.OperandType == CilOperandType.InlineTok) - { - Debug.Assert(bodyInstruction.OpCode.Code is CilCode.Ldtoken); - switch (bodyInstruction.Operand) - { - case ITypeDefOrRef typeDefOrRef: - { - var targetTok = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, typeDefOrRef.ToTypeSignature(), imports); - if (targetTok == null) - return false; - - var newInstruction = targetBuilder.Add(OpCodes.Call, - imports.Module.DefaultImporter.ImportMethod(imports.Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle.Value.MakeGenericInstanceMethod(targetTok))); - instructionMap.Add(bodyInstruction, newInstruction); - } - break; - default: - // Ldtoken is also used for members, which is not implemented. - return false; - } - } - else if (bodyInstruction.OpCode.OperandType is CilOperandType.InlineSwitch && bodyInstruction.Operand is IReadOnlyList labels) - { - List newLabels = new(labels.Count); - for (var i = 0; i < labels.Count; i++) - { - if (labels[i] is CilInstructionLabel oldLabel) - { - var newLabel = new CilInstructionLabel(); - labelMap.Add(new(oldLabel, newLabel)); - newLabels.Add(newLabel); - } - else - { - return false; - } - } - var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, newLabels); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.Operand is string or Utf8String - || bodyInstruction.Operand.GetType().IsPrimitive) - { - var newInstruction = new CilInstruction(bodyInstruction.OpCode, bodyInstruction.Operand); - targetBuilder.Add(newInstruction); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.Operand is Parameter parameter) - { - var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, target.Parameters.GetBySignatureIndex(parameter.MethodSignatureIndex)); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.Operand is CilLocalVariable localVariable) - { - var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, localVariableMap[localVariable]); - instructionMap.Add(bodyInstruction, newInstruction); - } - else if (bodyInstruction.Operand is CilInstructionLabel label) - { - var newLabel = new CilInstructionLabel(); - labelMap.Add(new(label, newLabel)); - var newInstruction = targetBuilder.Add(bodyInstruction.OpCode, newLabel); - instructionMap.Add(bodyInstruction, newInstruction); - } - else - { - return false; - } - } - - foreach ((var oldLabel, var newLabel) in labelMap) - { - newLabel.Instruction = instructionMap[oldLabel.Instruction!]; - } - - // Copy exception handlers - foreach (var exceptionHandler in original.CilMethodBody.ExceptionHandlers) - { - var newExceptionHandler = new CilExceptionHandler - { - HandlerType = exceptionHandler.HandlerType - }; - - switch (exceptionHandler.TryStart) - { - case null: - break; - case CilInstructionLabel { Instruction: not null } tryStart: - newExceptionHandler.TryStart = new CilInstructionLabel(instructionMap[tryStart.Instruction]); - break; - default: - return false; - } - - switch (exceptionHandler.TryEnd) - { - case null: - break; - case CilInstructionLabel { Instruction: not null } tryEnd: - newExceptionHandler.TryEnd = new CilInstructionLabel(instructionMap[tryEnd.Instruction]); - break; - default: - return false; - } - - switch (exceptionHandler.HandlerStart) - { - case null: - break; - case CilInstructionLabel { Instruction: not null } handlerStart: - newExceptionHandler.HandlerStart = new CilInstructionLabel(instructionMap[handlerStart.Instruction]); - break; - default: - return false; - } - - switch (exceptionHandler.HandlerEnd) - { - case null: - break; - case CilInstructionLabel { Instruction: not null } handlerEnd: - newExceptionHandler.HandlerEnd = new CilInstructionLabel(instructionMap[handlerEnd.Instruction]); - break; - default: - return false; - } - - switch (exceptionHandler.FilterStart) - { - case null: - break; - case CilInstructionLabel { Instruction: not null } filterStart: - newExceptionHandler.FilterStart = new CilInstructionLabel(instructionMap[filterStart.Instruction]); - break; - default: - return false; - } - - switch (exceptionHandler.ExceptionType?.ToTypeSignature()) - { - case null: - break; - case CorLibTypeSignature { ElementType: ElementType.Object }: - newExceptionHandler.ExceptionType = imports.Module.CorLibTypeFactory.Object.ToTypeDefOrRef(); - break; - default: - // In the future, we will throw exact exceptions, but we don't right now, - // so attempting to catch a specific exception type will always fail. - return false; - } - - target.CilMethodBody.ExceptionHandlers.Add(newExceptionHandler); - } - - return true; - } - - public static void ReplaceBodyWithException(MethodDefinition newMethod, RuntimeAssemblyReferences imports) - { - newMethod.CilMethodBody = new(); - var processor = newMethod.CilMethodBody.Instructions; - - processor.Add(OpCodes.Ldstr, "Method unstripping failed"); - processor.Add(OpCodes.Newobj, imports.Module.NotSupportedExceptionCtor()); - processor.Add(OpCodes.Throw); - processor.Add(OpCodes.Ret); - } - - //Required for deconstruction on net472 - private static void Deconstruct(this KeyValuePair pair, out CilInstructionLabel key, out CilInstructionLabel value) - { - key = pair.Key; - value = pair.Value; - } -} diff --git a/Il2CppInterop.Generator/Utils/XrefScanMetadataGenerationUtil.cs b/Il2CppInterop.Generator/Utils/XrefScanMetadataGenerationUtil.cs deleted file mode 100644 index bb0355dc..00000000 --- a/Il2CppInterop.Generator/Utils/XrefScanMetadataGenerationUtil.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Generator.Contexts; -using Il2CppInterop.Generator.Extensions; - -namespace Il2CppInterop.Generator.Utils; - -internal static class XrefScanMetadataGenerationUtil -{ - internal static long MetadataInitForMethodRva; - internal static IntPtr MetadataInitForMethodFileOffset; - - private static readonly (string Assembly, string Type, string Method)[] MetadataInitCandidates = - { - ("UnityEngine.CoreModule", "UnityEngine.Object", ".cctor"), - ("mscorlib", "System.Exception", "get_Message"), - ("mscorlib", "System.IntPtr", "Equals") - }; - - private static void FindMetadataInitForMethod(RewriteGlobalContext context, long gameAssemblyBase) - { - foreach (var metadataInitCandidate in MetadataInitCandidates) - { - var assembly = - context.Assemblies.FirstOrDefault(it => - it.OriginalAssembly.Name == metadataInitCandidate.Assembly); - var unityObjectCctor = assembly?.TryGetTypeByName(metadataInitCandidate.Type)?.OriginalType.Methods - .FirstOrDefault(it => it.Name == metadataInitCandidate.Method); - - if (unityObjectCctor == null) continue; - - MetadataInitForMethodFileOffset = - (IntPtr)(long)XrefScannerLowLevel - .JumpTargets((IntPtr)(gameAssemblyBase + unityObjectCctor.ExtractOffset())).First(); - MetadataInitForMethodRva = (long)MetadataInitForMethodFileOffset - gameAssemblyBase - - unityObjectCctor.ExtractOffset() + unityObjectCctor.ExtractRva(); - - return; - } - - throw new ApplicationException("Unable to find a method with metadata init reference"); - } - - internal static (long FlagRva, long TokenRva) FindMetadataInitForMethod(MethodRewriteContext method, - long gameAssemblyBase) - { - if (MetadataInitForMethodRva == 0) - FindMetadataInitForMethod(method.DeclaringType.AssemblyContext.GlobalContext, gameAssemblyBase); - - var codeStart = (IntPtr)(gameAssemblyBase + method.FileOffset); - var firstCall = XrefScannerLowLevel.JumpTargets(codeStart).FirstOrDefault(); - if (firstCall != MetadataInitForMethodFileOffset || firstCall == IntPtr.Zero) return (0, 0); - - var tokenPointer = - XrefScanUtilFinder.FindLastRcxReadAddressBeforeCallTo(codeStart, MetadataInitForMethodFileOffset); - var initFlagPointer = - XrefScanUtilFinder.FindByteWriteTargetRightAfterCallTo(codeStart, MetadataInitForMethodFileOffset); - - if (tokenPointer == IntPtr.Zero || initFlagPointer == IntPtr.Zero) return (0, 0); - - return ((long)initFlagPointer - gameAssemblyBase - method.FileOffset + method.Rva, - (long)tokenPointer - gameAssemblyBase - method.FileOffset + method.Rva); - } -} diff --git a/Il2CppInterop.Generator/Visitors/BooleanAndTypeVisitor.cs b/Il2CppInterop.Generator/Visitors/BooleanAndTypeVisitor.cs new file mode 100644 index 00000000..4adb4c09 --- /dev/null +++ b/Il2CppInterop.Generator/Visitors/BooleanAndTypeVisitor.cs @@ -0,0 +1,30 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Visitors; + +public abstract class BooleanAndTypeVisitor : TypeVisitor +{ + public override bool Visit(ArrayTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(BoxedTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(ByRefTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(CustomModifierTypeAnalysisContext type) => Visit(type.ElementType) && Visit(type.ModifierType); + public override bool Visit(GenericInstanceTypeAnalysisContext type) + { + if (!Visit(type.GenericType)) + return false; + + foreach (var genericArgument in type.GenericArguments) + { + if (!Visit(genericArgument)) + return false; + } + + return true; + } + public override bool Visit(GenericParameterTypeAnalysisContext type) => true; + public override bool Visit(PinnedTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(PointerTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(SentinelTypeAnalysisContext type) => true; + public override bool Visit(SzArrayTypeAnalysisContext type) => Visit(type.ElementType); + protected override bool VisitSimpleType(TypeAnalysisContext type) => true; +} diff --git a/Il2CppInterop.Generator/Visitors/BooleanOrTypeVisitor.cs b/Il2CppInterop.Generator/Visitors/BooleanOrTypeVisitor.cs new file mode 100644 index 00000000..c8b303b1 --- /dev/null +++ b/Il2CppInterop.Generator/Visitors/BooleanOrTypeVisitor.cs @@ -0,0 +1,30 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Visitors; + +public abstract class BooleanOrTypeVisitor : TypeVisitor +{ + public override bool Visit(ArrayTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(BoxedTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(ByRefTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(CustomModifierTypeAnalysisContext type) => Visit(type.ElementType) || Visit(type.ModifierType); + public override bool Visit(GenericInstanceTypeAnalysisContext type) + { + if (Visit(type.GenericType)) + return true; + + foreach (var genericArgument in type.GenericArguments) + { + if (Visit(genericArgument)) + return true; + } + + return false; + } + public override bool Visit(GenericParameterTypeAnalysisContext type) => false; + public override bool Visit(PinnedTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(PointerTypeAnalysisContext type) => Visit(type.ElementType); + public override bool Visit(SentinelTypeAnalysisContext type) => false; + public override bool Visit(SzArrayTypeAnalysisContext type) => Visit(type.ElementType); + protected override bool VisitSimpleType(TypeAnalysisContext type) => false; +} diff --git a/Il2CppInterop.Generator/Visitors/DefaultTypeVisitor.cs b/Il2CppInterop.Generator/Visitors/DefaultTypeVisitor.cs new file mode 100644 index 00000000..b993da71 --- /dev/null +++ b/Il2CppInterop.Generator/Visitors/DefaultTypeVisitor.cs @@ -0,0 +1,68 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Visitors; + +public abstract class DefaultTypeVisitor : TypeVisitor +{ + public override T Visit(ArrayTypeAnalysisContext type) => CombineResults(type, Visit(type.ElementType)); + public override T Visit(BoxedTypeAnalysisContext type) => CombineResults(type, Visit(type.ElementType)); + public override T Visit(ByRefTypeAnalysisContext type) => CombineResults(type, Visit(type.ElementType)); + public override T Visit(CustomModifierTypeAnalysisContext type) => CombineResults(type, Visit(type.ElementType), Visit(type.ModifierType)); + public override T Visit(GenericInstanceTypeAnalysisContext type) + { + var genericTypeResult = Visit(type.GenericType); + var genericArgumentsResults = new T[type.GenericArguments.Count]; + for (var i = 0; i < type.GenericArguments.Count; i++) + { + genericArgumentsResults[i] = Visit(type.GenericArguments[i]); + } + return CombineResults(type, genericTypeResult, genericArgumentsResults); + } + /// + /// If not overridden, this will return . + /// + public override T Visit(GenericParameterTypeAnalysisContext type) => default!; + public override T Visit(PinnedTypeAnalysisContext type) => CombineResults(type, Visit(type.ElementType)); + public override T Visit(PointerTypeAnalysisContext type) => CombineResults(type, Visit(type.ElementType)); + /// + /// If not overridden, this will return . + /// + public override T Visit(SentinelTypeAnalysisContext type) => default!; + public override T Visit(SzArrayTypeAnalysisContext type) => CombineResults(type, Visit(type.ElementType)); + /// + /// If not overridden, this will return . + /// + protected override T VisitSimpleType(TypeAnalysisContext type) => default!; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(ArrayTypeAnalysisContext type, T elementResult) => elementResult; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(BoxedTypeAnalysisContext type, T elementResult) => elementResult; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(ByRefTypeAnalysisContext type, T elementResult) => elementResult; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(CustomModifierTypeAnalysisContext type, T elementResult, T modifierResult) => elementResult; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(GenericInstanceTypeAnalysisContext type, T genericTypeResult, T[] genericArgumentsResults) => genericTypeResult; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(PinnedTypeAnalysisContext type, T elementResult) => elementResult; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(PointerTypeAnalysisContext type, T elementResult) => elementResult; + /// + /// If not overridden, this will return the . + /// + protected virtual T CombineResults(SzArrayTypeAnalysisContext type, T elementResult) => elementResult; +} diff --git a/Il2CppInterop.Generator/Visitors/TypeConversionVisitor.cs b/Il2CppInterop.Generator/Visitors/TypeConversionVisitor.cs new file mode 100644 index 00000000..fd61bce9 --- /dev/null +++ b/Il2CppInterop.Generator/Visitors/TypeConversionVisitor.cs @@ -0,0 +1,83 @@ +using Cpp2IL.Core.Model.Contexts; +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace Il2CppInterop.Generator.Visitors; + +internal sealed class TypeConversionVisitor : TypeReplacementVisitor +{ + private TypeConversionVisitor(Dictionary replacements) : base(replacements) + { + } + + public required TypeAnalysisContext Pointer { get; init; } + public required TypeAnalysisContext ByRef { get; init; } + public required TypeAnalysisContext ArrayRank1 { get; init; } + public required TypeAnalysisContext ArrayRank2 { get; init; } + public required TypeAnalysisContext ArrayRank3 { get; init; } + public required TypeAnalysisContext ArrayRank4 { get; init; } + public required TypeAnalysisContext ArrayRank5 { get; init; } + + public static TypeConversionVisitor Create(ApplicationAnalysisContext appContext) + { + var il2CppMscorlib = appContext.AssembliesByName["Il2Cppmscorlib"]; + var mscorlib = appContext.AssembliesByName["mscorlib"]; + var il2CppInteropRuntime = appContext.AssembliesByName["Il2CppInterop.Runtime"]; + + var pointer = il2CppInteropRuntime.GetTypeByFullNameOrThrow(typeof(Pointer<>)); + var byRef = il2CppInteropRuntime.GetTypeByFullNameOrThrow(typeof(ByReference<>)); + var arrayRank1 = il2CppInteropRuntime.GetTypeByFullNameOrThrow(typeof(Il2CppArrayRank1<>)); + var arrayRank2 = il2CppInteropRuntime.GetTypeByFullNameOrThrow(typeof(Il2CppArrayRank2<>)); + var arrayRank3 = il2CppInteropRuntime.GetTypeByFullNameOrThrow(typeof(Il2CppArrayRank3<>)); + var arrayRank4 = il2CppInteropRuntime.GetTypeByFullNameOrThrow(typeof(Il2CppArrayRank4<>)); + var arrayRank5 = il2CppInteropRuntime.GetTypeByFullNameOrThrow(typeof(Il2CppArrayRank5<>)); + + (string, string)[] replacements = + [ + ("Il2CppSystem.Object", "Il2CppSystem.IObject"), + ("Il2CppSystem.Enum", "Il2CppSystem.IEnum"), + ("Il2CppSystem.ValueType", "Il2CppSystem.IValueType"), + ]; + + var replacementDictionary = replacements.ToDictionary(pair => il2CppMscorlib.GetTypeByFullNameOrThrow(pair.Item1), pair => il2CppMscorlib.GetTypeByFullNameOrThrow(pair.Item2), TypeAnalysisContextEqualityComparer.Instance); + + return new TypeConversionVisitor(replacementDictionary) + { + Pointer = pointer, + ByRef = byRef, + ArrayRank1 = arrayRank1, + ArrayRank2 = arrayRank2, + ArrayRank3 = arrayRank3, + ArrayRank4 = arrayRank4, + ArrayRank5 = arrayRank5, + }; + } + + protected override TypeAnalysisContext CombineResults(ArrayTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return type.Rank switch + { + 1 => ArrayRank1.MakeGenericInstanceType([elementResult]), + 2 => ArrayRank2.MakeGenericInstanceType([elementResult]), + 3 => ArrayRank3.MakeGenericInstanceType([elementResult]), + 4 => ArrayRank4.MakeGenericInstanceType([elementResult]), + 5 => ArrayRank5.MakeGenericInstanceType([elementResult]), + _ => throw new NotImplementedException($"Support for arrays with rank {type.Rank} has not been implemented."), + }; + } + + protected override TypeAnalysisContext CombineResults(SzArrayTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return ArrayRank1.MakeGenericInstanceType([elementResult]); + } + + protected override TypeAnalysisContext CombineResults(PointerTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return Pointer.MakeGenericInstanceType([elementResult]); + } + + protected override TypeAnalysisContext CombineResults(ByRefTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return ByRef.MakeGenericInstanceType([elementResult]); + } +} diff --git a/Il2CppInterop.Generator/Visitors/TypeReplacementVisitor.cs b/Il2CppInterop.Generator/Visitors/TypeReplacementVisitor.cs new file mode 100644 index 00000000..dc16bb67 --- /dev/null +++ b/Il2CppInterop.Generator/Visitors/TypeReplacementVisitor.cs @@ -0,0 +1,201 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Visitors; + +internal class TypeReplacementVisitor(Dictionary replacements) : DefaultTypeVisitor +{ + private readonly Dictionary _replacements = replacements; + + public static TypeReplacementVisitor Null { get; } = new NullTypeReplacementVisitor(); + + public static TypeReplacementVisitor CreateForMethodCopying(MethodAnalysisContext source, MethodAnalysisContext destination) + { + Debug.Assert(source.GenericParameters.Count == destination.GenericParameters.Count); + Debug.Assert(source.DeclaringType is not null && destination.DeclaringType is not null); + Debug.Assert(source.DeclaringType.GenericParameters.Count == destination.DeclaringType.GenericParameters.Count); + if (source.GenericParameters.Count == 0 && (source.DeclaringType == destination.DeclaringType || source.DeclaringType.GenericParameters.Count == 0)) + return new([]); + + var replacements = new Dictionary(source.GenericParameters.Count); + for (var i = source.GenericParameters.Count - 1; i >= 0; i--) + { + replacements.Add(source.GenericParameters[i], destination.GenericParameters[i]); + } + if (source.DeclaringType != destination.DeclaringType) + { + for (var i = source.DeclaringType.GenericParameters.Count - 1; i >= 0; i--) + { + replacements.Add(source.DeclaringType.GenericParameters[i], destination.DeclaringType.GenericParameters[i]); + } + } + + return new TypeReplacementVisitor(replacements); + } + + public static TypeReplacementVisitor Combine(TypeReplacementVisitor first, TypeReplacementVisitor second) + { + return new CombinedTypeReplacementVisitor(first, second); + } + + [return: NotNullIfNotNull(nameof(type))] + public TypeAnalysisContext? Replace(TypeAnalysisContext? type) + { + return type is null ? null : Visit(type); + } + + public IEnumerable Replace(IEnumerable types) + { + foreach (var type in types) + { + yield return Replace(type); + } + } + + public IReadOnlyList Replace(IReadOnlyList types) + { + if (types.Count == 0) + return []; + + var results = new TypeAnalysisContext[types.Count]; + for (var i = types.Count - 1; i >= 0; i--) + { + results[i] = Replace(types[i]); + } + + return results; + } + + public void Modify(List types) + { + for (var i = 0; i < types.Count; i++) + { + types[i] = Replace(types[i]); + } + } + + [return: NotNullIfNotNull(nameof(method))] + public MethodAnalysisContext? Replace(MethodAnalysisContext? method) + { + if (method is null) + return null; + + if (method is not ConcreteGenericMethodAnalysisContext concreteGenericMethod) + return method; + + var typeArguments = Replace(concreteGenericMethod.TypeGenericParameters); + var methodArguments = Replace(concreteGenericMethod.MethodGenericParameters); + + return new ConcreteGenericMethodAnalysisContext(concreteGenericMethod.BaseMethodContext, typeArguments, methodArguments); + } + + [return: NotNullIfNotNull(nameof(field))] + public FieldAnalysisContext? Replace(FieldAnalysisContext? field) + { + if (field is null) + return null; + + if (field is not ConcreteGenericFieldAnalysisContext concreteGenericField) + return field; + + var declaringType = (GenericInstanceTypeAnalysisContext)Replace(concreteGenericField.DeclaringType); + + return declaringType == concreteGenericField.DeclaringType ? field : new ConcreteGenericFieldAnalysisContext(concreteGenericField.BaseFieldContext, declaringType); + } + + protected override TypeAnalysisContext VisitSimpleType(TypeAnalysisContext type) + { + return _replacements.TryGetValue(type, out var replacement) ? replacement : type; + } + + protected override TypeAnalysisContext CombineResults(ArrayTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return type.ElementType == elementResult ? type : elementResult.MakeArrayType(type.Rank); + } + + protected override TypeAnalysisContext CombineResults(BoxedTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return type.ElementType == elementResult ? type : elementResult.MakeBoxedType(); + } + + protected override TypeAnalysisContext CombineResults(ByRefTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return type.ElementType == elementResult ? type : elementResult.MakeByReferenceType(); + } + + protected override TypeAnalysisContext CombineResults(CustomModifierTypeAnalysisContext type, TypeAnalysisContext elementResult, TypeAnalysisContext modifierResult) + { + if (type.ElementType == elementResult && type.ModifierType == modifierResult) + return type; + return elementResult.MakeCustomModifierType(modifierResult, type.Required); + } + + protected override TypeAnalysisContext CombineResults(GenericInstanceTypeAnalysisContext type, TypeAnalysisContext genericTypeResult, TypeAnalysisContext[] genericArgumentsResults) + { + if (type.GenericType == genericTypeResult && type.GenericArguments.SequenceEqual(genericArgumentsResults)) + return type; + return genericTypeResult.MakeGenericInstanceType(genericArgumentsResults!); + } + + public override TypeAnalysisContext Visit(GenericParameterTypeAnalysisContext type) + { + return _replacements.TryGetValue(type, out var replacement) ? replacement : type; + } + + protected override TypeAnalysisContext CombineResults(PinnedTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return type.ElementType == elementResult ? type : elementResult.MakePinnedType(); + } + + protected override TypeAnalysisContext CombineResults(PointerTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return type.ElementType == elementResult ? type : elementResult.MakePointerType(); + } + + public override TypeAnalysisContext Visit(SentinelTypeAnalysisContext type) + { + return _replacements.TryGetValue(type, out var replacement) ? replacement : type; + } + + protected override TypeAnalysisContext CombineResults(SzArrayTypeAnalysisContext type, TypeAnalysisContext elementResult) + { + return type.ElementType == elementResult ? type : elementResult.MakeSzArrayType(); + } + + private sealed class CombinedTypeReplacementVisitor(TypeReplacementVisitor first, TypeReplacementVisitor second) : TypeReplacementVisitor([]) + { + public override TypeAnalysisContext Visit(TypeAnalysisContext type) => second.Visit(first.Visit(type)); + protected override TypeAnalysisContext Visit(ReferencedTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(WrappedTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(ArrayTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(BoxedTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(ByRefTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(CustomModifierTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(GenericInstanceTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(GenericParameterTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(PinnedTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(PointerTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(SentinelTypeAnalysisContext type) => second.Visit(first.Visit(type)); + public override TypeAnalysisContext Visit(SzArrayTypeAnalysisContext type) => second.Visit(first.Visit(type)); + protected override TypeAnalysisContext VisitSimpleType(TypeAnalysisContext type) => second.Visit(first.Visit(type)); + } + + private sealed class NullTypeReplacementVisitor() : TypeReplacementVisitor([]) + { + public override TypeAnalysisContext Visit(TypeAnalysisContext type) => type; + protected override TypeAnalysisContext Visit(ReferencedTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(WrappedTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(ArrayTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(BoxedTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(ByRefTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(CustomModifierTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(GenericInstanceTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(GenericParameterTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(PinnedTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(PointerTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(SentinelTypeAnalysisContext type) => type; + public override TypeAnalysisContext Visit(SzArrayTypeAnalysisContext type) => type; + protected override TypeAnalysisContext VisitSimpleType(TypeAnalysisContext type) => type; + } +} diff --git a/Il2CppInterop.Generator/Visitors/TypeVisitor.cs b/Il2CppInterop.Generator/Visitors/TypeVisitor.cs new file mode 100644 index 00000000..f7ff65e8 --- /dev/null +++ b/Il2CppInterop.Generator/Visitors/TypeVisitor.cs @@ -0,0 +1,42 @@ +using Cpp2IL.Core.Model.Contexts; + +namespace Il2CppInterop.Generator.Visitors; + +public abstract class TypeVisitor +{ + public virtual T Visit(TypeAnalysisContext type) => type switch + { + ReferencedTypeAnalysisContext referencedType => Visit(referencedType), + _ => VisitSimpleType(type), + }; + protected virtual T Visit(ReferencedTypeAnalysisContext type) => type switch + { + WrappedTypeAnalysisContext wrappedType => Visit(wrappedType), + GenericInstanceTypeAnalysisContext genericInstanceType => Visit(genericInstanceType), + GenericParameterTypeAnalysisContext genericParameterType => Visit(genericParameterType), + SentinelTypeAnalysisContext sentinelType => Visit(sentinelType), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Type is not supported"), + }; + public virtual T Visit(WrappedTypeAnalysisContext type) => type switch + { + ArrayTypeAnalysisContext arrayType => Visit(arrayType), + BoxedTypeAnalysisContext boxedType => Visit(boxedType), + ByRefTypeAnalysisContext byRefType => Visit(byRefType), + CustomModifierTypeAnalysisContext customModifierType => Visit(customModifierType), + PinnedTypeAnalysisContext pinnedType => Visit(pinnedType), + PointerTypeAnalysisContext pointerType => Visit(pointerType), + SzArrayTypeAnalysisContext szArrayType => Visit(szArrayType), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Type is not supported"), + }; + public abstract T Visit(ArrayTypeAnalysisContext type); + public abstract T Visit(BoxedTypeAnalysisContext type); + public abstract T Visit(ByRefTypeAnalysisContext type); + public abstract T Visit(CustomModifierTypeAnalysisContext type); + public abstract T Visit(GenericInstanceTypeAnalysisContext type); + public abstract T Visit(GenericParameterTypeAnalysisContext type); + public abstract T Visit(PinnedTypeAnalysisContext type); + public abstract T Visit(PointerTypeAnalysisContext type); + public abstract T Visit(SentinelTypeAnalysisContext type); + public abstract T Visit(SzArrayTypeAnalysisContext type); + protected abstract T VisitSimpleType(TypeAnalysisContext type); +} diff --git a/Il2CppInterop.Generator/XrefScans/XrefScanImpl.cs b/Il2CppInterop.Generator/XrefScans/XrefScanImpl.cs deleted file mode 100644 index fbcb3aba..00000000 --- a/Il2CppInterop.Generator/XrefScans/XrefScanImpl.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Il2CppInterop.Common.XrefScans; - -namespace Il2CppInterop.Generator.XrefScans; - -internal class XrefScanImpl : IXrefScannerImpl -{ - public (XrefScanUtil.InitMetadataForMethod, IntPtr)? GetMetadataResolver() - { - return null; - } - - public bool XrefGlobalClassFilter(IntPtr movTarget) - { - return false; - } -} diff --git a/Il2CppInterop.HarmonySupport/Extensions.cs b/Il2CppInterop.HarmonySupport/Extensions.cs new file mode 100644 index 00000000..3b36149d --- /dev/null +++ b/Il2CppInterop.HarmonySupport/Extensions.cs @@ -0,0 +1,81 @@ +using System.Reflection; +using System.Reflection.Emit; + +namespace Il2CppInterop.HarmonySupport; + +internal static class Extensions +{ + public static void Emit(this ILGenerator il, OpCode opCode, MethodBase methodBase) + { + if (methodBase is MethodInfo methodInfo) + { + il.Emit(opCode, methodInfo); + } + else if (methodBase is ConstructorInfo constructorInfo) + { + il.Emit(opCode, constructorInfo); + } + else + { + throw new InvalidOperationException("Can't emit a call to a MethodBase that isn't either a MethodInfo or a ConstructorInfo"); + } + } + + public static void EmitLdarg(this ILGenerator il, int index) + { + switch (index) + { + case 0: + il.Emit(OpCodes.Ldarg_0); + break; + case 1: + il.Emit(OpCodes.Ldarg_1); + break; + case 2: + il.Emit(OpCodes.Ldarg_2); + break; + case 3: + il.Emit(OpCodes.Ldarg_3); + break; + default: + if (index <= byte.MaxValue) + { + il.Emit(OpCodes.Ldarg_S, (byte)index); + } + else + { + il.Emit(OpCodes.Ldarg, index); + } + break; + } + } + + public static void EmitStarg(this ILGenerator il, int index) + { + switch (index) + { + case 0: + il.Emit(OpCodes.Starg_S, (byte)0); + break; + case 1: + il.Emit(OpCodes.Starg_S, (byte)1); + break; + case 2: + il.Emit(OpCodes.Starg_S, (byte)2); + break; + case 3: + il.Emit(OpCodes.Starg_S, (byte)3); + break; + default: + if (index <= byte.MaxValue) + { + il.Emit(OpCodes.Starg_S, (byte)index); + } + else + { + il.Emit(OpCodes.Starg, index); + } + break; + } + } +} diff --git a/Il2CppInterop.HarmonySupport/HarmonyBackendComponent.cs b/Il2CppInterop.HarmonySupport/HarmonyBackendComponent.cs deleted file mode 100644 index 89661413..00000000 --- a/Il2CppInterop.HarmonySupport/HarmonyBackendComponent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using HarmonyLib.Public.Patching; -using Il2CppInterop.Common.Host; -using Il2CppInterop.Runtime; -using Il2CppInterop.Runtime.Injection; - -namespace Il2CppInterop.HarmonySupport; - -public static class HarmonySupport -{ - public static T AddHarmonySupport(this T host) where T : BaseHost - { - host.AddComponent(new HarmonySupportComponent()); - return host; - } -} - -internal class HarmonySupportComponent : IHostComponent -{ - public void Dispose() => PatchManager.ResolvePatcher -= TryResolve; - - public void Start() => PatchManager.ResolvePatcher += TryResolve; - - private static void TryResolve(object sender, PatchManager.PatcherResolverEventArgs args) - { - var declaringType = args.Original.DeclaringType; - if (declaringType == null) return; - if (Il2CppType.From(declaringType, false) == null || - ClassInjector.IsManagedTypeInjected(declaringType)) - { - return; - } - - var backend = new Il2CppDetourMethodPatcher(args.Original); - if (backend.IsValid) - { - args.MethodPatcher = backend; - } - } -} diff --git a/Il2CppInterop.HarmonySupport/Il2CppDetourMethodPatcher.cs b/Il2CppInterop.HarmonySupport/Il2CppDetourMethodPatcher.cs deleted file mode 100644 index 0bd437ee..00000000 --- a/Il2CppInterop.HarmonySupport/Il2CppDetourMethodPatcher.cs +++ /dev/null @@ -1,482 +0,0 @@ -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.InteropServices; -using HarmonyLib; -using HarmonyLib.Public.Patching; -using Il2CppInterop.Common; -using Il2CppInterop.Runtime; -using Il2CppInterop.Runtime.Injection; -using Il2CppInterop.Runtime.InteropTypes; -using Il2CppInterop.Runtime.Runtime; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo; -using Il2CppInterop.Runtime.Startup; -using Microsoft.Extensions.Logging; -using MonoMod.Cil; -using MonoMod.RuntimeDetour; -using MonoMod.Utils; -using Detour = MonoMod.RuntimeDetour.Detour; -using IDetour = Il2CppInterop.Runtime.Injection.IDetour; -using ValueType = Il2CppSystem.ValueType; - -namespace Il2CppInterop.HarmonySupport; - -internal unsafe class Il2CppDetourMethodPatcher : MethodPatcher -{ - private static readonly MethodInfo IL2CPPToManagedStringMethodInfo - = AccessTools.Method(typeof(IL2CPP), - nameof(IL2CPP.Il2CppStringToManaged)); - - private static readonly MethodInfo ManagedToIL2CPPStringMethodInfo - = AccessTools.Method(typeof(IL2CPP), - nameof(IL2CPP.ManagedStringToIl2Cpp)); - - private static readonly MethodInfo ObjectBaseToPtrMethodInfo - = AccessTools.Method(typeof(IL2CPP), - nameof(IL2CPP.Il2CppObjectBaseToPtr)); - - private static readonly MethodInfo ObjectBaseToPtrNotNullMethodInfo - = AccessTools.Method(typeof(IL2CPP), - nameof(IL2CPP.Il2CppObjectBaseToPtrNotNull)); - - private static readonly MethodInfo ReportExceptionMethodInfo - = AccessTools.Method(typeof(Il2CppDetourMethodPatcher), nameof(ReportException)); - - // Map each value type to correctly sized store opcode to prevent memory overwrite - // Special case: bool is byte in Il2Cpp - private static readonly Dictionary StIndOpcodes = new() - { - [typeof(byte)] = OpCodes.Stind_I1, - [typeof(sbyte)] = OpCodes.Stind_I1, - [typeof(bool)] = OpCodes.Stind_I1, - [typeof(short)] = OpCodes.Stind_I2, - [typeof(ushort)] = OpCodes.Stind_I2, - [typeof(int)] = OpCodes.Stind_I4, - [typeof(uint)] = OpCodes.Stind_I4, - [typeof(long)] = OpCodes.Stind_I8, - [typeof(ulong)] = OpCodes.Stind_I8, - [typeof(float)] = OpCodes.Stind_R4, - [typeof(double)] = OpCodes.Stind_R8 - }; - - private static readonly List DelegateCache = new(); - private static readonly List DetourCache = new(); - - private INativeMethodInfoStruct modifiedNativeMethodInfo; - - private IDetour nativeDetour; - - private INativeMethodInfoStruct originalNativeMethodInfo; - - /// - /// Constructs a new instance of method patcher. - /// - /// - public Il2CppDetourMethodPatcher(MethodBase original) : base(original) => Init(); - - internal bool IsValid { get; private set; } - - private void Init() - { - try - { - var methodField = Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original); - - if (methodField == null) - { - var fieldInfoField = - Il2CppInteropUtils.GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(Original); - - if (fieldInfoField != null) - { - throw new - Exception($"Method {Original.FullDescription()} is a field accessor, it can't be patched."); - } - - // Generated method is probably unstripped, it can be safely handed to IL handler - return; - } - - // Get the native MethodInfo struct for the target method - originalNativeMethodInfo = - UnityVersionHandler.Wrap((Il2CppMethodInfo*)(IntPtr)methodField.GetValue(null)); - - // Create a modified native MethodInfo struct, that will point towards the trampoline - modifiedNativeMethodInfo = UnityVersionHandler.NewMethod(); - Buffer.MemoryCopy(originalNativeMethodInfo.Pointer.ToPointer(), - modifiedNativeMethodInfo.Pointer.ToPointer(), UnityVersionHandler.MethodSize(), - UnityVersionHandler.MethodSize()); - IsValid = true; - } - catch (Exception e) - { - Logger.Instance.LogWarning( - "Failed to init IL2CPP patch backend for {Original}, using normal patch handlers: {ErrorMessage}", - Original.FullDescription(), e.Message); - } - } - - /// - public override DynamicMethodDefinition PrepareOriginal() => null; - - /// - public override MethodBase DetourTo(MethodBase replacement) - { - // // Unpatch an existing detour if it exists - if (nativeDetour != null) - { - // Point back to the original method before we unpatch - modifiedNativeMethodInfo.MethodPointer = originalNativeMethodInfo.MethodPointer; - nativeDetour.Dispose(); - } - - // Generate a new DMD of the modified unhollowed method, and apply harmony patches to it - var copiedDmd = CopyOriginal(); - - HarmonyManipulator.Manipulate(copiedDmd.OriginalMethod, copiedDmd.OriginalMethod.GetPatchInfo(), - new ILContext(copiedDmd.Definition)); - - // Generate the MethodInfo instances - var managedHookedMethod = copiedDmd.Generate(); - var unmanagedTrampolineMethod = GenerateNativeToManagedTrampoline(managedHookedMethod).Generate(); - - // Apply a detour from the unmanaged implementation to the patched harmony method - var unmanagedDelegateType = DelegateTypeFactory.instance.CreateDelegateType(unmanagedTrampolineMethod, - CallingConvention.Cdecl); - - var unmanagedDelegate = unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType); - DelegateCache.Add(unmanagedDelegate); - - nativeDetour = - Il2CppInteropRuntime.Instance.DetourProvider.Create(originalNativeMethodInfo.MethodPointer, unmanagedDelegate); - nativeDetour.Apply(); - modifiedNativeMethodInfo.MethodPointer = nativeDetour.OriginalTrampoline; - - var detour = new Detour(Original, managedHookedMethod); - detour.Apply(); - DetourCache.Add(detour); - - return managedHookedMethod; - } - - /// - public override DynamicMethodDefinition CopyOriginal() - { - var dmd = new DynamicMethodDefinition(Original); - dmd.Definition.Name = "UnhollowedWrapper_" + dmd.Definition.Name; - var cursor = new ILCursor(new ILContext(dmd.Definition)); - - - // Remove il2cpp_object_get_virtual_method - if (cursor.TryGotoNext(x => x.MatchLdarg(0), - x => x.MatchCall(typeof(IL2CPP), - nameof(IL2CPP.Il2CppObjectBaseToPtr)), - x => x.MatchLdsfld(out _), - x => x.MatchCall(typeof(IL2CPP), - nameof(IL2CPP.il2cpp_object_get_virtual_method)))) - { - cursor.RemoveRange(4); - } - else - { - cursor.Goto(0) - .GotoNext(x => - x.MatchLdsfld(Il2CppInteropUtils - .GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original))) - .Remove(); - } - - // Replace original IL2CPPMethodInfo pointer with a modified one that points to the trampoline - cursor - .Emit(Mono.Cecil.Cil.OpCodes.Ldc_I8, modifiedNativeMethodInfo.Pointer.ToInt64()) - .Emit(Mono.Cecil.Cil.OpCodes.Conv_I); - - return dmd; - } - - // Tries to guess whether a function needs a return buffer for the return struct, in all cases except win64 it's undefined behaviour - private static bool IsReturnBufferNeeded(int size) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#return-values - return size != 1 && size != 4 && size != 8; - } - - if (Environment.Is64BitProcess) - { - // x64 gcc and clang seem to use a return buffer for everything above 16 bytes - return size > 16; - } - - // Looks like on x32 gcc and clang return buffer is always used - return true; - } - - private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo targetManagedMethodInfo) - { - // managedParams are the interop types used on the managed side - // unmanagedParams are IntPtr references that are used by IL2CPP compiled assembly - var paramStartIndex = 0; - - var managedReturnType = AccessTools.GetReturnedType(Original); - var unmanagedReturnType = managedReturnType.NativeType(); - - var returnSize = IntPtr.Size; - - var isReturnValueType = managedReturnType.IsSubclassOf(typeof(ValueType)); - if (isReturnValueType) - { - uint align = 0; - returnSize = IL2CPP.il2cpp_class_value_size(Il2CppClassPointerStore.GetNativeClassPointer(managedReturnType), ref align); - } - - var hasReturnBuffer = isReturnValueType && IsReturnBufferNeeded(returnSize); - if (hasReturnBuffer) - // C compilers seem to return large structs by allocating a return buffer on caller's side and passing it as the first parameter - // TODO: Handle ARM - // TODO: Check if this applies to values other than structs - { - unmanagedReturnType = typeof(IntPtr); - paramStartIndex++; - } - - if (!Original.IsStatic) - { - paramStartIndex++; - } - - var managedParams = Original.GetParameters().Select(x => x.ParameterType).ToArray(); - var unmanagedParams = - new Type[managedParams.Length + paramStartIndex + - 1]; // +1 for methodInfo at the end - - if (hasReturnBuffer) - // With GCC the return buffer seems to be the first param, same is likely with other compilers too - { - unmanagedParams[0] = typeof(IntPtr); - } - - if (!Original.IsStatic) - { - unmanagedParams[paramStartIndex - 1] = typeof(IntPtr); - } - - unmanagedParams[^1] = typeof(Il2CppMethodInfo*); - Array.Copy(managedParams.Select(TrampolineHelpers.NativeType).ToArray(), 0, - unmanagedParams, paramStartIndex, managedParams.Length); - - var dmd = new DynamicMethodDefinition("(il2cpp -> managed) " + Original.Name, - unmanagedReturnType, - unmanagedParams - ); - - var il = dmd.GetILGenerator(); - il.BeginExceptionBlock(); - - // Declare a list of variables to dereference back to the original pointers. - // This is required due to the needed interop type conversions, so we can't directly pass some addresses as byref types - var indirectVariables = new LocalBuilder[managedParams.Length]; - - if (!Original.IsStatic) - { - EmitConvertArgumentToManaged(il, paramStartIndex - 1, Original.DeclaringType, out _); - } - - for (var i = 0; i < managedParams.Length; ++i) - { - EmitConvertArgumentToManaged(il, i + paramStartIndex, managedParams[i], out indirectVariables[i]); - } - - // Run the managed method - il.Emit(OpCodes.Call, targetManagedMethodInfo); - - // Store the managed return type temporarily (if there was one) - LocalBuilder managedReturnVariable = null; - if (managedReturnType != typeof(void)) - { - managedReturnVariable = il.DeclareLocal(managedReturnType); - il.Emit(OpCodes.Stloc, managedReturnVariable); - } - - // Convert any managed byref values into their relevant IL2CPP types, and then store the values into their relevant dereferenced pointers - for (var i = 0; i < managedParams.Length; ++i) - { - if (indirectVariables[i] == null) - { - continue; - } - - il.Emit(OpCodes.Ldarg_S, i + paramStartIndex); - il.Emit(OpCodes.Ldloc, indirectVariables[i]); - var directType = managedParams[i].GetElementType(); - EmitConvertManagedTypeToIL2CPP(il, directType); - il.Emit(StIndOpcodes.TryGetValue(directType, out var stindOpCodde) ? stindOpCodde : OpCodes.Stind_I); - } - - // Handle any lingering exceptions - il.BeginCatchBlock(typeof(Exception)); - il.Emit(OpCodes.Call, ReportExceptionMethodInfo); - il.EndExceptionBlock(); - - // Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return - if (managedReturnVariable != null) - { - if (hasReturnBuffer) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldloc, managedReturnVariable); - il.Emit(OpCodes.Call, ObjectBaseToPtrNotNullMethodInfo); - EmitUnbox(il); - il.Emit(OpCodes.Ldc_I4, returnSize); - il.Emit(OpCodes.Cpblk); - - // Return the same pointer to the return buffer - il.Emit(OpCodes.Ldarg_0); - } - else - { - il.Emit(OpCodes.Ldloc, managedReturnVariable); - EmitConvertManagedTypeToIL2CPP(il, managedReturnType); - } - } - - il.Emit(OpCodes.Ret); - - return dmd; - } - - private static void EmitUnbox(ILGenerator il) - { - il.Emit(OpCodes.Ldc_I4_2); - il.Emit(OpCodes.Conv_I); - il.Emit(OpCodes.Sizeof, typeof(void*)); - il.Emit(OpCodes.Mul); - il.Emit(OpCodes.Add); - } - - private static void ReportException(Exception ex) => - Logger.Instance.LogError(ex, "During invoking native->managed trampoline"); - - private static void EmitConvertManagedTypeToIL2CPP(ILGenerator il, Type returnType) - { - if (returnType == typeof(string)) - { - il.Emit(OpCodes.Call, ManagedToIL2CPPStringMethodInfo); - } - else if (!returnType.IsValueType && returnType.IsSubclassOf(typeof(Il2CppObjectBase))) - { - il.Emit(OpCodes.Call, ObjectBaseToPtrMethodInfo); - } - } - - private static void EmitConvertArgumentToManaged(ILGenerator il, - int argIndex, - Type managedParamType, - out LocalBuilder variable) - { - variable = null; - - bool needsBoxing = managedParamType.IsSubclassOf(typeof(ValueType)); - - if (needsBoxing) - { - var classPtr = Il2CppClassPointerStore.GetNativeClassPointer(managedParamType); - - // il2cpp_value_box uses .NET boxing semantics which boxes Nullable as just T, - // losing the HasValue field. Manually box Nullable to preserve full data. - bool isNullable = managedParamType.IsGenericType && - managedParamType.GetGenericTypeDefinition().FullName == "Il2CppSystem.Nullable`1"; - - if (isNullable) - { - uint align = 0; - var valueSize = IL2CPP.il2cpp_class_value_size(classPtr, ref align); - - il.Emit(OpCodes.Ldc_I8, classPtr.ToInt64()); - il.Emit(OpCodes.Conv_I); - il.Emit(OpCodes.Call, AccessTools.Method(typeof(IL2CPP), nameof(IL2CPP.il2cpp_object_new))); - var objLocal = il.DeclareLocal(typeof(IntPtr)); - il.Emit(OpCodes.Stloc, objLocal); - il.Emit(Environment.Is64BitProcess ? OpCodes.Ldarg : OpCodes.Ldarga_S, argIndex); - il.Emit(OpCodes.Ldloc, objLocal); - il.Emit(OpCodes.Call, AccessTools.Method(typeof(IL2CPP), nameof(IL2CPP.il2cpp_object_unbox))); - il.Emit(OpCodes.Ldc_I4, (int)valueSize); - il.Emit(OpCodes.Call, AccessTools.Method(typeof(Il2CppDetourMethodPatcher), nameof(CopyMemory))); - il.Emit(OpCodes.Ldloc, objLocal); - } - else - { - // Box struct into object first before conversion - il.Emit(OpCodes.Ldc_I8, classPtr.ToInt64()); - il.Emit(OpCodes.Conv_I); - // On x64, struct is always a pointer but it is a non-pointer on x86 - // We don't handle byref structs on x86 yet but we're yet to encounter those - il.Emit(Environment.Is64BitProcess ? OpCodes.Ldarg : OpCodes.Ldarga_S, argIndex); - il.Emit(OpCodes.Call, - AccessTools.Method(typeof(IL2CPP), - nameof(IL2CPP.il2cpp_value_box))); - } - } - else - { - il.Emit(OpCodes.Ldarg_S, argIndex); - } - - if (managedParamType.IsValueType) // don't need to convert blittable types - { - return; - } - - void EmitCreateIl2CppObject(Type originalType) - { - var endLabel = il.DefineLabel(); - var notNullLabel = il.DefineLabel(); - - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Brtrue_S, notNullLabel); - - il.Emit(OpCodes.Pop); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Br_S, endLabel); - - il.MarkLabel(notNullLabel); - il.Emit(OpCodes.Call, AccessTools.Method(typeof(Il2CppObjectPool), nameof(Il2CppObjectPool.Get)).MakeGenericMethod(originalType)); - - il.MarkLabel(endLabel); - } - - void HandleTypeConversion(Type originalType) - { - if (originalType == typeof(string)) - { - il.Emit(OpCodes.Call, IL2CPPToManagedStringMethodInfo); - } - else if (originalType.IsSubclassOf(typeof(Il2CppObjectBase))) - { - EmitCreateIl2CppObject(originalType); - } - } - - if (managedParamType.IsByRef) - { - // TODO: directType being ValueType is not handled yet (but it's not that common in games). Implement when needed. - var directType = managedParamType.GetElementType(); - - variable = il.DeclareLocal(directType); - - il.Emit(OpCodes.Ldind_I); - - HandleTypeConversion(directType); - - il.Emit(OpCodes.Stloc, variable); - il.Emit(OpCodes.Ldloca, variable); - } - else - { - HandleTypeConversion(managedParamType); - } - } - - private static void CopyMemory(IntPtr src, IntPtr dest, int size) => - Buffer.MemoryCopy(src.ToPointer(), dest.ToPointer(), size, size); -} diff --git a/Il2CppInterop.HarmonySupport/Il2CppInterop.HarmonySupport.csproj b/Il2CppInterop.HarmonySupport/Il2CppInterop.HarmonySupport.csproj index 6e8e09f1..237103ae 100644 --- a/Il2CppInterop.HarmonySupport/Il2CppInterop.HarmonySupport.csproj +++ b/Il2CppInterop.HarmonySupport/Il2CppInterop.HarmonySupport.csproj @@ -1,24 +1,18 @@ - + + Il2CppInterop.HarmonySupport Module to allow using Harmony with Il2CppInterop assemblies - net6.0 - Il2CppInterop.HarmonySupport + net10.0 + true - + - - - - - - false - ..\Il2CppInterop.Runtime\Libs\Il2Cppmscorlib.dll - + diff --git a/Il2CppInterop.HarmonySupport/Il2CppInteropDetour.cs b/Il2CppInterop.HarmonySupport/Il2CppInteropDetour.cs new file mode 100644 index 00000000..4f32c7d3 --- /dev/null +++ b/Il2CppInterop.HarmonySupport/Il2CppInteropDetour.cs @@ -0,0 +1,348 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Injection; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; +using Microsoft.Extensions.Logging; +using Mono.Cecil; +using MonoMod.Cil; +using MonoMod.Core; +using MonoMod.Utils; + +namespace Il2CppInterop.HarmonySupport; + +internal sealed class Il2CppInteropDetour : ICoreDetourWithClone +{ + private static readonly MethodInfo ReportExceptionMethodInfo + = typeof(Il2CppInteropDetour).GetMethod(nameof(ReportException), BindingFlags.NonPublic | BindingFlags.Static)!; + + internal readonly INativeMethodInfoStruct _nativeSourceClone; + private readonly Delegate _thunkDelegate; + + internal ICoreDetour? _detour; + internal ICoreNativeDetour? _nativeDetour; + + public MethodBase Source { get; } + public INativeMethodInfoStruct NativeSource { get; } + public MethodBase Target { get; } + + public bool IsApplied => _nativeDetour?.IsApplied ?? false; + + public DynamicMethodDefinition SourceMethodCloneIL { get; private set; } + public MethodInfo SourceMethodClone { get; private set; } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public Il2CppInteropDetour(MethodBase source, INativeMethodInfoStruct nativeSource, MethodBase target) + { + Source = source; + NativeSource = nativeSource; + Target = target; + + _nativeSourceClone = UnityVersionHandler.NewMethod(); + unsafe + { + Buffer.MemoryCopy(NativeSource.MethodInfoPointer, _nativeSourceClone.MethodInfoPointer, UnityVersionHandler.MethodSize(), UnityVersionHandler.MethodSize()); + } + + SourceMethodCloneIL = CopyOriginal(); + SourceMethodClone = SourceMethodCloneIL.Generate(); + + try + { + var thunk = GenerateNativeToManagedThunk().Generate(); + var thunkDelegateType = TrampolineBuilder.GetOrCreateDelegateType((MethodInfo)source); + _thunkDelegate = thunk.CreateDelegate(thunkDelegateType); + } + catch (Exception e) + { + Logger.Instance.LogError(e, "Exception during generating native to managed thunk"); + throw; + } + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private DynamicMethodDefinition CopyOriginal() + { + try + { + var dmd = new DynamicMethodDefinition(Source); + dmd.Definition.Name = "RuntimeModified_" + dmd.Definition.Name; + + var cursor = new ILCursor(new ILContext(dmd.Definition)); + + foreach (var instr in cursor.Instrs) + { + if (instr.OpCode != Mono.Cecil.Cil.OpCodes.Call || + instr.Operand is not MethodReference methodRef) + continue; + + var name = methodRef.Name; + + if (name.StartsWith("UnsafeInvoke_", StringComparison.Ordinal)) + { + instr.Operand = ProcessWrapper(methodRef, "UnsafeImplementation_"); + } + else if (name.StartsWith("UnsafeConstruct", StringComparison.Ordinal)) + { + instr.Operand = ProcessWrapper(methodRef, "UnsafeConstructor"); + } + else if (name.StartsWith("UnsafeImplementation_", StringComparison.Ordinal) || name.StartsWith("UnsafeConstructor", StringComparison.Ordinal)) + { + instr.Operand = ProcessPointerPatchedLeaf(methodRef); + } + } + + return dmd; + } + catch (Exception e) + { + Logger.Instance.LogError(e, "Exception during creating runtime modified original"); + throw; + } + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private MethodInfo ProcessWrapper(MethodReference outerRef, string innerPrefix) + { + var dmd = CreateWrappedDMD(outerRef); + var cursor = new ILCursor(new ILContext(dmd.Definition)); + + foreach (var instr in cursor.Instrs) + { + if (instr.OpCode == Mono.Cecil.Cil.OpCodes.Call && + instr.Operand is MethodReference inner && + inner.Name.StartsWith(innerPrefix, StringComparison.Ordinal)) + { + instr.Operand = ProcessPointerPatchedLeaf(inner); + } + } + + return dmd.Generate(); + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private MethodInfo ProcessPointerPatchedLeaf(MethodReference leafRef) + { + var dmd = CreateWrappedDMD(leafRef); + var cursor = new ILCursor(new ILContext(dmd.Definition)); + + cursor.Goto(0) + .GotoNext(x => + x.MatchLdsfld(Il2CppInternalsAccess + .GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Source)!)) + .Remove(); + + var ptr = _nativeSourceClone.Pointer.ToInt64(); + + cursor.Emit(Mono.Cecil.Cil.OpCodes.Ldc_I8, ptr) + .Emit(Mono.Cecil.Cil.OpCodes.Conv_I); + + return dmd.Generate(); + } + + private DynamicMethodDefinition CreateWrappedDMD(MethodReference methodRef) + { + var dmd = new DynamicMethodDefinition(methodRef.ResolveReflection()); + dmd.Definition.Name = "RuntimeModified_" + dmd.Definition.Name; + return dmd; + } + + [RequiresDynamicCode("")] + [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "The constructor for this class requires dynamic code generation, which warns the user before this method can ever be called.")] + public void Apply() + { + if (NativeSource.MethodPointer == default) + { + Logger.Instance.LogWarning("Method pointer for {Source} was null", Source.Name); + return; + } + + // Unpatch an existing detour if it exists + if (_nativeDetour != null) + { + // Point back to the original method before we unpatch + _nativeSourceClone.MethodPointer = NativeSource.MethodPointer; + _nativeDetour.Dispose(); + } + + _detour = DetourFactory.Current.CreateDetour(Source, Target); + + _nativeDetour = DetourFactory.Current.CreateNativeDetour(NativeSource.MethodPointer, Marshal.GetFunctionPointerForDelegate(_thunkDelegate)); + if (!_nativeDetour.HasOrigEntrypoint) + { + throw new Exception("HasOrigEntrypoint has to be true"); + } + _nativeSourceClone.MethodPointer = _nativeDetour.OrigEntrypoint; + } + + public void Undo() + { + _detour?.Undo(); + _nativeDetour?.Undo(); + } + + public void Dispose() + { + _detour?.Dispose(); + _nativeDetour?.Dispose(); + + Marshal.FreeHGlobal(_nativeSourceClone.Pointer); + } + + // Tries to guess whether a function needs a return buffer for the return struct, in all cases except win64 it's undefined behaviour + private static bool IsReturnBufferNeeded(int size) + { + if (OperatingSystem.IsWindows()) + { + // https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#return-values + return size is not 1 and not 4 and not 8; + } + + if (Environment.Is64BitProcess) + { + // x64 gcc and clang seem to use a return buffer for everything above 16 bytes + return size > 16; + } + + // Looks like on x32 gcc and clang return buffer is always used + return true; + } + + private static Type GetReturnType(MethodBase methodOrConstructor) + { + return methodOrConstructor is ConstructorInfo ? typeof(void) : ((MethodInfo)methodOrConstructor).ReturnType; + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private DynamicMethodDefinition GenerateNativeToManagedThunk() + { + // managedParams are the interop types used on the managed side + // unmanagedParams are IntPtr references that are used by IL2CPP compiled assembly + var paramStartIndex = 0; + + var managedReturnType = GetReturnType(Source); + var unmanagedReturnType = TrampolineBuilder.GetNativeType(managedReturnType); + + var returnSize = IntPtr.Size; + + var isReturnValueType = managedReturnType.IsValueType; + if (isReturnValueType) + { + returnSize = IL2CPP.il2cpp_class_value_size(Il2CppType.GetClassPointer(managedReturnType), out _); + } + + var hasReturnBuffer = isReturnValueType && IsReturnBufferNeeded(returnSize); + if (hasReturnBuffer) + // C compilers seem to return large structs by allocating a return buffer on caller's side and passing it as the first parameter + // TODO: Handle ARM + // TODO: Check if this applies to values other than structs + { + unmanagedReturnType = typeof(IntPtr); + paramStartIndex++; + } + + if (!Source.IsStatic) + { + paramStartIndex++; + } + + var managedParams = Source.GetParameters().Select(x => x.ParameterType).ToArray(); + var unmanagedParams = + new Type[managedParams.Length + paramStartIndex + + 1]; // +1 for methodInfo at the end + + if (hasReturnBuffer) + // With GCC the return buffer seems to be the first param, same is likely with other compilers too + { + unmanagedParams[0] = typeof(IntPtr); + } + + if (!Source.IsStatic) + { + unmanagedParams[paramStartIndex - 1] = typeof(IntPtr); + } + + unmanagedParams[^1] = typeof(Il2CppMethodInfo*); + Array.Copy(managedParams.Select(TrampolineBuilder.GetNativeType).ToArray(), 0, + unmanagedParams, paramStartIndex, managedParams.Length); + + var dmd = new DynamicMethodDefinition("(il2cpp -> managed) " + Source.Name, + unmanagedReturnType, + unmanagedParams + ); + + var il = dmd.GetILGenerator(); + il.BeginExceptionBlock(); + + if (!Source.IsStatic) + { + EmitConvertArgumentToManaged(il, paramStartIndex - 1, Source.DeclaringType!); + } + + for (var i = 0; i < managedParams.Length; ++i) + { + EmitConvertArgumentToManaged(il, i + paramStartIndex, managedParams[i]); + } + + // Run the managed method + il.Emit(OpCodes.Call, Target); + + // Store the managed return type temporarily (if there was one) + LocalBuilder? managedReturnVariable = null; + if (managedReturnType != typeof(void)) + { + managedReturnVariable = il.DeclareLocal(managedReturnType); + il.Emit(OpCodes.Stloc, managedReturnVariable); + } + + // Handle any lingering exceptions + il.BeginCatchBlock(typeof(Exception)); + il.Emit(OpCodes.Call, ReportExceptionMethodInfo); + il.EndExceptionBlock(); + + // Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return + if (managedReturnVariable != null) + { + if (hasReturnBuffer) + { + il.Emit(OpCodes.Ldloc, managedReturnVariable); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, typeof(Il2CppType).GetMethod(nameof(Il2CppType.WriteToPointer))!.MakeGenericMethod(managedReturnType)); + + // Return the same pointer to the return buffer + il.Emit(OpCodes.Ldarg_0); + } + else + { + var unmanagedReturnVariable = il.DeclareLocal(unmanagedReturnType); + il.Emit(OpCodes.Ldloc, managedReturnVariable); + il.Emit(OpCodes.Ldloca, unmanagedReturnVariable); + il.Emit(OpCodes.Call, typeof(Il2CppType).GetMethod(nameof(Il2CppType.WriteToPointer))!.MakeGenericMethod(managedReturnType)); + il.Emit(OpCodes.Ldloc, unmanagedReturnVariable); + } + } + il.Emit(OpCodes.Ret); + + return dmd; + } + + private static void ReportException(Exception ex) => + Logger.Instance.LogError(ex, "During invoking native->managed trampoline"); + + [RequiresDynamicCode("")] + private static void EmitConvertArgumentToManaged(ILGenerator il, + int argIndex, + Type managedParamType) + { + il.Emit(OpCodes.Ldarga, argIndex); + il.Emit(OpCodes.Call, typeof(Il2CppType).GetMethod(nameof(Il2CppType.ReadFromPointer))!.MakeGenericMethod(managedParamType)); + } +} diff --git a/Il2CppInterop.HarmonySupport/Il2CppInteropDetourFactory.cs b/Il2CppInterop.HarmonySupport/Il2CppInteropDetourFactory.cs new file mode 100644 index 00000000..45ba9322 --- /dev/null +++ b/Il2CppInterop.HarmonySupport/Il2CppInteropDetourFactory.cs @@ -0,0 +1,85 @@ +using System.Diagnostics.CodeAnalysis; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Injection; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; +using MonoMod.Core; + +namespace Il2CppInterop.HarmonySupport; + +public sealed class Il2CppInteropDetourFactory(IDetourFactory? fallback = null) : IDetourFactory +{ + private readonly IDetourFactory _fallback = fallback ?? DetourFactory.Current; + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + [SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "This method will never be called in a trimmed context.")] + [SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "This method will never be called in an AOT context.")] + public ICoreDetour CreateDetour(CreateDetourRequest request) + { + ArgumentNullException.ThrowIfNull(request.Source); + ArgumentNullException.ThrowIfNull(request.Target); + + if (TryCreateDetour(request, out var detour)) + { + return detour; + } + + return _fallback.CreateDetour(request); + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static bool TryCreateDetour(CreateDetourRequest request, [NotNullWhen(true)] out Il2CppInteropDetour? detour) + { + var declaringType = request.Source.DeclaringType; + if (declaringType != null && TypeInjector.IsPreexistingType(declaringType)) + { + if (!request.CreateSourceCloneIfNotILClone) + { + throw new InvalidOperationException("IDetourFactory consumer has to support CreateSourceCloneIfNotILClone"); + } + + var methodField = Il2CppInternalsAccess.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(request.Source); + + if (methodField == null) + { + var fieldInfoField = Il2CppInternalsAccess.GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(request.Source); + + if (fieldInfoField != null) + { + throw new Exception($"Method {request.Source} is a field accessor, it can't be patched."); + } + + // Generated method is probably unstripped, it can be safely handed to IL handler + detour = null; + return false; + } + + var nativeSourcePointer = methodField.GetValue(null) ?? throw new Exception(); + INativeMethodInfoStruct nativeSource; + unsafe + { + nativeSource = UnityVersionHandler.Wrap((Il2CppMethodInfo*)(IntPtr)nativeSourcePointer); + } + + detour = new Il2CppInteropDetour(request.Source, nativeSource, request.Target); + if (request.ApplyByDefault) + { + detour.Apply(); + } + + return true; + } + + detour = null; + return false; + } + + public ICoreNativeDetour CreateNativeDetour(CreateNativeDetourRequest request) + { + return _fallback.CreateNativeDetour(request); + } + + public bool SupportsNativeDetourOrigEntrypoint => true; +} diff --git a/Il2CppInterop.Runtime/Attributes/AlsoInitializeAttribute.cs b/Il2CppInterop.Runtime/Attributes/AlsoInitializeAttribute.cs deleted file mode 100644 index b2f1d4b8..00000000 --- a/Il2CppInterop.Runtime/Attributes/AlsoInitializeAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Attributes; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] -public class AlsoInitializeAttribute : Attribute -{ - public readonly Type LinkedType; - - public AlsoInitializeAttribute(Type linkedType) - { - LinkedType = linkedType; - } -} diff --git a/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs b/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs deleted file mode 100644 index 4916018c..00000000 --- a/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Il2CppInterop.Runtime.Attributes; - -[AttributeUsage(AttributeTargets.Class)] -public class ClassInjectionAssemblyTargetAttribute : Attribute -{ - private readonly string[] assemblies; - - public ClassInjectionAssemblyTargetAttribute(string assembly) - { - if (string.IsNullOrWhiteSpace(assembly)) assemblies = new string[0]; - else assemblies = new[] { assembly }; - } - - public ClassInjectionAssemblyTargetAttribute(string[] assemblies) - { - if (assemblies is null) this.assemblies = new string[0]; - else this.assemblies = assemblies; - } - - internal IntPtr[] GetImagePointers() - { - var result = new List(); - foreach (var assembly in assemblies) - { - var intPtr = IL2CPP.GetIl2CppImage(assembly); - if (intPtr != IntPtr.Zero) result.Add(intPtr); - } - - return result.ToArray(); - } -} diff --git a/Il2CppInterop.Runtime/Attributes/HideFromIl2CppAttribute.cs b/Il2CppInterop.Runtime/Attributes/HideFromIl2CppAttribute.cs deleted file mode 100644 index 78af88c8..00000000 --- a/Il2CppInterop.Runtime/Attributes/HideFromIl2CppAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Attributes; - -/// -/// This attribute indicates that the target should not be exposed to IL2CPP in injected classes -/// -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property | - AttributeTargets.Event)] -public class HideFromIl2CppAttribute : Attribute -{ -} diff --git a/Il2CppInterop.Runtime/Attributes/Il2CppImplementsAttribute.cs b/Il2CppInterop.Runtime/Attributes/Il2CppImplementsAttribute.cs deleted file mode 100644 index 9a86fded..00000000 --- a/Il2CppInterop.Runtime/Attributes/Il2CppImplementsAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Attributes; - -[AttributeUsage(AttributeTargets.Class)] -public class Il2CppImplementsAttribute : Attribute -{ - public Il2CppImplementsAttribute(params Type[] interfaces) - { - Interfaces = interfaces; - } - - public Type[] Interfaces { get; } -} diff --git a/Il2CppInterop.Runtime/DelegateSupport.cs b/Il2CppInterop.Runtime/DelegateSupport.cs index 5299beff..0ec0fcf4 100644 --- a/Il2CppInterop.Runtime/DelegateSupport.cs +++ b/Il2CppInterop.Runtime/DelegateSupport.cs @@ -1,227 +1,44 @@ using System; using System.Collections.Concurrent; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Extensions; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; -using Il2CppInterop.Runtime.Runtime; -using Microsoft.Extensions.Logging; -using Object = Il2CppSystem.Object; -using ValueType = Il2CppSystem.ValueType; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppInterop.Runtime.Structs; namespace Il2CppInterop.Runtime; public static class DelegateSupport { - private static readonly ConcurrentDictionary ourDelegateTypes = new(); + private static readonly ConcurrentDictionary NativeToManagedTrampolines = new(); - private static readonly AssemblyBuilder AssemblyBuilder = - AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Il2CppTrampolineDelegates"), AssemblyBuilderAccess.Run); - - private static readonly ModuleBuilder ModuleBuilder = - AssemblyBuilder.DefineDynamicModule("Il2CppTrampolineDelegates"); - - private static readonly ConcurrentDictionary NativeToManagedTrampolines = new(); - - internal static Type GetOrCreateDelegateType(MethodSignature signature, MethodInfo managedMethod) - { - return ourDelegateTypes.GetOrAdd(signature, - (signature, managedMethodInner) => - CreateDelegateType(managedMethodInner, signature), - managedMethod); - } - - private static Type CreateDelegateType(MethodInfo managedMethodInner, MethodSignature signature) - { - var typeName = "Il2CppToManagedDelegate_" + managedMethodInner.DeclaringType + "_" + signature.GetHashCode() + - (signature.HasThis ? "HasThis" : "") + - (signature.ConstructedFromNative ? "FromNative" : ""); - - var newType = ModuleBuilder.DefineType(typeName, TypeAttributes.Sealed | TypeAttributes.Public, - typeof(MulticastDelegate)); - newType.SetCustomAttribute(new CustomAttributeBuilder( - typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new[] { typeof(CallingConvention) })!, - new object[] { CallingConvention.Cdecl })); - - var ctor = newType.DefineConstructor( - MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | - MethodAttributes.Public, CallingConventions.HasThis, new[] { typeof(object), typeof(IntPtr) }); - ctor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask); - - var parameterOffset = signature.HasThis ? 1 : 0; - var managedParameters = managedMethodInner.GetParameters(); - var parameterTypes = new Type[managedParameters.Length + 1 + parameterOffset]; - - if (signature.HasThis) - parameterTypes[0] = typeof(IntPtr); - - parameterTypes[parameterTypes.Length - 1] = typeof(Il2CppMethodInfo*); - for (var i = 0; i < managedParameters.Length; i++) - parameterTypes[i + parameterOffset] = managedParameters[i].ParameterType.NativeType(); - - newType.DefineMethod("Invoke", - MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public, - CallingConventions.HasThis, - managedMethodInner.ReturnType.NativeType(), - parameterTypes).SetImplementationFlags(MethodImplAttributes.CodeTypeMask); - - newType.DefineMethod("BeginInvoke", - MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | - MethodAttributes.Public, - CallingConventions.HasThis, typeof(IAsyncResult), - parameterTypes.Concat(new[] { typeof(AsyncCallback), typeof(object) }).ToArray()) - .SetImplementationFlags(MethodImplAttributes.CodeTypeMask); - - newType.DefineMethod("EndInvoke", - MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public, - CallingConventions.HasThis, - managedMethodInner.ReturnType.NativeType(), - new[] { typeof(IAsyncResult) }).SetImplementationFlags(MethodImplAttributes.CodeTypeMask); - - return newType.CreateType(); - } - - private static string ExtractSignature(MethodInfo methodInfo) + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static Delegate GetOrCreateNativeToManagedTrampoline([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type delegateType) { - var builder = new StringBuilder(); - builder.Append(methodInfo.ReturnType.FullName); - if (!methodInfo.IsStatic) - { - builder.Append('_'); - builder.Append(methodInfo.DeclaringType!.FullName); - } - foreach (var parameterInfo in methodInfo.GetParameters()) - { - builder.Append('_'); - builder.Append(parameterInfo.ParameterType.FullName); - } - - return builder.ToString(); + return NativeToManagedTrampolines.GetOrAdd(delegateType, CreateNativeToManagedTrampoline); } - private static Delegate GetOrCreateNativeToManagedTrampoline(MethodSignature signature, - Il2CppSystem.Reflection.MethodInfo nativeMethod, MethodInfo managedMethod) + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static Delegate CreateNativeToManagedTrampoline([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type delegateType) { - return NativeToManagedTrampolines.GetOrAdd(managedMethod, - (_, tuple) => GenerateNativeToManagedTrampoline(tuple.nativeMethod, tuple.managedMethod, tuple.signature), - (nativeMethod, managedMethod, signature)); + var invokeMethod = Il2CppToMonoDelegateReference.GetOrCreateInvokeMethod(delegateType); + return TrampolineBuilder.CreateTrampoline(invokeMethod, false); } - private static Delegate GenerateNativeToManagedTrampoline(Il2CppSystem.Reflection.MethodInfo nativeMethod, - MethodInfo managedMethod, MethodSignature signature) - { - var returnType = managedMethod.ReturnType.NativeType(); - - var managedParameters = managedMethod.GetParameters(); - var nativeParameters = nativeMethod.GetParameters(); - var parameterTypes = new Type[managedParameters.Length + 1 + 1]; // thisptr for target, methodInfo last arg - parameterTypes[0] = typeof(IntPtr); - parameterTypes[managedParameters.Length + 1] = typeof(Il2CppMethodInfo*); - for (var i = 0; i < managedParameters.Length; i++) - parameterTypes[i + 1] = managedParameters[i].ParameterType.NativeType(); - - var trampoline = new DynamicMethod("(il2cpp delegate trampoline) " + ExtractSignature(managedMethod), - MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, returnType, parameterTypes, - typeof(DelegateSupport), true); - var bodyBuilder = trampoline.GetILGenerator(); - - var tryLabel = bodyBuilder.BeginExceptionBlock(); - - bodyBuilder.Emit(OpCodes.Ldarg_0); - bodyBuilder.Emit(OpCodes.Call, - typeof(ClassInjectorBase).GetMethod(nameof(ClassInjectorBase.GetMonoObjectFromIl2CppPointer))!); - bodyBuilder.Emit(OpCodes.Castclass, typeof(Il2CppToMonoDelegateReference)); - bodyBuilder.Emit(OpCodes.Ldfld, - typeof(Il2CppToMonoDelegateReference).GetField(nameof(Il2CppToMonoDelegateReference.ReferencedDelegate))); - - for (var i = 0; i < managedParameters.Length; i++) - { - var parameterType = managedParameters[i].ParameterType; - - bodyBuilder.Emit(OpCodes.Ldarg, i + 1); - if (parameterType == typeof(string)) - { - bodyBuilder.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.Il2CppStringToManaged))!); - } - else if (!parameterType.IsValueType) - { - var labelNull = bodyBuilder.DefineLabel(); - var labelDone = bodyBuilder.DefineLabel(); - bodyBuilder.Emit(OpCodes.Brfalse, labelNull); - bodyBuilder.Emit(OpCodes.Ldarg, i + 1); - bodyBuilder.Emit(OpCodes.Newobj, parameterType.GetConstructor(new[] { typeof(IntPtr) })!); - bodyBuilder.Emit(OpCodes.Br, labelDone); - bodyBuilder.MarkLabel(labelNull); - bodyBuilder.Emit(OpCodes.Ldnull); - bodyBuilder.MarkLabel(labelDone); - } - } - - bodyBuilder.Emit(OpCodes.Call, managedMethod); - - if (managedMethod.ReturnType == typeof(string)) - { - bodyBuilder.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.ManagedStringToIl2Cpp))!); - } - else if (!managedMethod.ReturnType.IsValueType) - { - var labelNull = bodyBuilder.DefineLabel(); - var labelDone = bodyBuilder.DefineLabel(); - bodyBuilder.Emit(OpCodes.Dup); - bodyBuilder.Emit(OpCodes.Brfalse, labelNull); - bodyBuilder.Emit(OpCodes.Call, - typeof(Il2CppObjectBase).GetProperty(nameof(Il2CppObjectBase.Pointer))!.GetMethod); - bodyBuilder.Emit(OpCodes.Br, labelDone); - bodyBuilder.MarkLabel(labelNull); - bodyBuilder.Emit(OpCodes.Pop); - bodyBuilder.Emit(OpCodes.Ldc_I4_0); - bodyBuilder.Emit(OpCodes.Conv_I); - bodyBuilder.MarkLabel(labelDone); - } - - LocalBuilder returnLocal = null; - if (returnType != typeof(void)) - { - returnLocal = bodyBuilder.DeclareLocal(returnType); - bodyBuilder.Emit(OpCodes.Stloc, returnLocal); - } - - var exceptionLocal = bodyBuilder.DeclareLocal(typeof(Exception)); - bodyBuilder.BeginCatchBlock(typeof(Exception)); - bodyBuilder.Emit(OpCodes.Stloc, exceptionLocal); - bodyBuilder.Emit(OpCodes.Ldstr, "Exception in IL2CPP-to-Managed trampoline, not passing it to il2cpp: "); - bodyBuilder.Emit(OpCodes.Ldloc, exceptionLocal); - bodyBuilder.Emit(OpCodes.Callvirt, typeof(object).GetMethod(nameof(ToString))!); - bodyBuilder.Emit(OpCodes.Call, - typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) })!); - bodyBuilder.Emit(OpCodes.Call, typeof(DelegateSupport).GetMethod(nameof(LogError), BindingFlags.Static | BindingFlags.NonPublic)!); - - bodyBuilder.EndExceptionBlock(); - - if (returnLocal != null) - bodyBuilder.Emit(OpCodes.Ldloc, returnLocal); - bodyBuilder.Emit(OpCodes.Ret); - - return trampoline.CreateDelegate(GetOrCreateDelegateType(signature, managedMethod)); - } - - private static void LogError(string message) - { - Logger.Instance.LogError("{Message}", message); - } - - public static TIl2Cpp? ConvertDelegate(Delegate @delegate) where TIl2Cpp : Il2CppObjectBase + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public static TIl2Cpp? ConvertDelegate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TIl2Cpp>(Delegate @delegate) where TIl2Cpp : Il2CppSystem.Delegate, IIl2CppType { if (@delegate == null) return null; - if (!typeof(Il2CppSystem.Delegate).IsAssignableFrom(typeof(TIl2Cpp))) - throw new ArgumentException($"{typeof(TIl2Cpp)} is not a delegate"); - var managedInvokeMethod = @delegate.GetType().GetMethod("Invoke")!; var parameterInfos = managedInvokeMethod.GetParameters(); foreach (var parameterInfo in parameterInfos) @@ -230,20 +47,13 @@ private static void LogError(string message) if (parameterType.IsGenericParameter) throw new ArgumentException( $"Delegate has unsubstituted generic parameter ({parameterType}) which is not supported"); - - if (parameterType.BaseType == typeof(ValueType)) - throw new ArgumentException( - $"Delegate has parameter of type {parameterType} (non-blittable struct) which is not supported"); } - var classTypePtr = Il2CppClassPointerStore.GetNativeClassPointer(typeof(TIl2Cpp)); + var classTypePtr = Il2CppType.GetClassPointer(); if (classTypePtr == IntPtr.Zero) throw new ArgumentException($"Type {typeof(TIl2Cpp)} has uninitialized class pointer"); - if (Il2CppClassPointerStore.NativeClassPtr == IntPtr.Zero) - ClassInjector.RegisterTypeInIl2Cpp(); - - var il2CppDelegateType = Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(classTypePtr)); + var il2CppDelegateType = Il2CppSystem.Type.FromClassPointer(classTypePtr); var nativeDelegateInvokeMethod = il2CppDelegateType.GetMethod("Invoke"); var nativeParameters = nativeDelegateInvokeMethod.GetParameters(); @@ -254,33 +64,16 @@ private static void LogError(string message) for (var i = 0; i < nativeParameters.Count; i++) { var nativeType = nativeParameters[i].ParameterType; - var managedType = parameterInfos[i].ParameterType; - - if (nativeType.IsPrimitive || managedType.IsPrimitive) - { - if (nativeType.FullName != managedType.FullName) - throw new ArgumentException( - $"Parameter type mismatch at parameter {i}: {nativeType.FullName} != {managedType.FullName}"); - continue; - } + var typePointerForManagedType = Il2CppTypePointerStore.GetNativeTypePointer(parameterInfos[i].ParameterType); + var managedType = Il2CppSystem.Type.FromTypePointer(typePointerForManagedType); - var classPointerFromManagedType = (IntPtr)typeof(Il2CppClassPointerStore<>).MakeGenericType(managedType) - .GetField(nameof(Il2CppClassPointerStore.NativeClassPtr)).GetValue(null); - - var classPointerFromNativeType = IL2CPP.il2cpp_class_from_type(nativeType._impl.value); - - if (classPointerFromManagedType != classPointerFromNativeType) + if (nativeType != managedType) throw new ArgumentException( - $"Parameter type at {i} has mismatched native type pointers; types: {nativeType.FullName} != {managedType.FullName}"); - - if (nativeType.IsByRef || managedType.IsByRef) - throw new ArgumentException($"Parameter at {i} is passed by reference, this is not supported"); + $"Parameter type at {i} has mismatched native type pointers; types: {nativeType?.FullName} != {managedType?.FullName}"); } - var signature = new MethodSignature(nativeDelegateInvokeMethod, true); - var managedTrampoline = - GetOrCreateNativeToManagedTrampoline(signature, nativeDelegateInvokeMethod, managedInvokeMethod); + var managedTrampoline = GetOrCreateNativeToManagedTrampoline(@delegate.GetType()); var methodInfo = UnityVersionHandler.NewMethod(); methodInfo.MethodPointer = Marshal.GetFunctionPointerForDelegate(managedTrampoline); @@ -290,17 +83,7 @@ private static void LogError(string message) var delegateReference = new Il2CppToMonoDelegateReference(@delegate, methodInfo.Pointer); - Il2CppSystem.Delegate converted; - if (UnityVersionHandler.MustUseDelegateConstructor) - { - converted = ((TIl2Cpp)Activator.CreateInstance(typeof(TIl2Cpp), delegateReference.Cast(), - methodInfo.Pointer)).Cast(); - } - else - { - var nativeDelegatePtr = IL2CPP.il2cpp_object_new(classTypePtr); - converted = new Il2CppSystem.Delegate(nativeDelegatePtr); - } + TIl2Cpp converted = (TIl2Cpp)Activator.CreateInstance(typeof(TIl2Cpp), delegateReference, (Il2CppSystem.IntPtr)methodInfo.Pointer)!; converted.method_ptr = methodInfo.MethodPointer; converted.method_info = nativeDelegateInvokeMethod; // todo: is this truly a good hack? @@ -311,106 +94,18 @@ private static void LogError(string message) { // U2021.2.0+ hack in case the constructor did the wrong thing anyway converted.invoke_impl = converted.method_ptr; - converted.method_code = converted.m_target.Pointer; + converted.method_code = delegateReference.Pointer; } - return converted.Cast(); + return converted; } - internal class MethodSignature : IEquatable - { - public readonly bool ConstructedFromNative; - public readonly bool HasThis; - private readonly int _hashCode; - - public MethodSignature(Il2CppSystem.Reflection.MethodInfo methodInfo, bool hasThis) - { - HasThis = hasThis; - ConstructedFromNative = true; - - var hashCode = new HashCode(); - - hashCode.Add(methodInfo.ReturnType.GetHashCode()); - if (hasThis) hashCode.Add(methodInfo.DeclaringType.GetHashCode()); - foreach (var parameterInfo in methodInfo.GetParameters()) - { - hashCode.Add(parameterInfo.ParameterType.GetHashCode()); - } + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetParameters")] + [return: UnsafeAccessorType($"Il2CppInterop.Runtime.InteropTypes.Arrays.{nameof(Il2CppArrayRank1<>)}`1[[Il2CppSystem.Reflection.ParameterInfo, Il2Cppmscorlib]]")] + private static extern object GetParametersInternal(Il2CppSystem.Reflection.MethodBase method); - _hashCode = hashCode.ToHashCode(); - } - - public MethodSignature(MethodInfo methodInfo, bool hasThis) - { - HasThis = hasThis; - ConstructedFromNative = false; - - var hashCode = new HashCode(); - - hashCode.Add(methodInfo.ReturnType.NativeType()); - if (hasThis) hashCode.Add(methodInfo.DeclaringType.NativeType()); - foreach (var parameterInfo in methodInfo.GetParameters()) - { - hashCode.Add(parameterInfo.ParameterType.NativeType()); - } - - _hashCode = hashCode.ToHashCode(); - } - - public override int GetHashCode() - { - return _hashCode; - } - - public bool Equals(MethodSignature other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return _hashCode.GetHashCode() == other.GetHashCode(); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((MethodSignature)obj); - } - - public static bool operator ==(MethodSignature left, MethodSignature right) - { - return Equals(left, right); - } - - public static bool operator !=(MethodSignature left, MethodSignature right) - { - return !Equals(left, right); - } - } - - private class Il2CppToMonoDelegateReference : Object + private static IReadOnlyList GetParameters(this Il2CppSystem.Reflection.MethodBase method) { - public IntPtr MethodInfo; - public Delegate ReferencedDelegate; - - public Il2CppToMonoDelegateReference(IntPtr obj0) : base(obj0) - { - } - - public Il2CppToMonoDelegateReference(Delegate referencedDelegate, IntPtr methodInfo) : base( - ClassInjector.DerivedConstructorPointer()) - { - ClassInjector.DerivedConstructorBody(this); - - ReferencedDelegate = referencedDelegate; - MethodInfo = methodInfo; - } - - ~Il2CppToMonoDelegateReference() - { - Marshal.FreeHGlobal(MethodInfo); - MethodInfo = IntPtr.Zero; - ReferencedDelegate = null; - } + return (IReadOnlyList)GetParametersInternal(method); } } diff --git a/Il2CppInterop.Runtime/Exceptions/IIl2CppException.cs b/Il2CppInterop.Runtime/Exceptions/IIl2CppException.cs new file mode 100644 index 00000000..054c4a60 --- /dev/null +++ b/Il2CppInterop.Runtime/Exceptions/IIl2CppException.cs @@ -0,0 +1,6 @@ +namespace Il2CppInterop.Runtime.Exceptions; + +public interface IIl2CppException +{ + Il2CppException CreateSystemException(); +} diff --git a/Il2CppInterop.Runtime/Exceptions/Il2CppException.cs b/Il2CppInterop.Runtime/Exceptions/Il2CppException.cs new file mode 100644 index 00000000..89e14df3 --- /dev/null +++ b/Il2CppInterop.Runtime/Exceptions/Il2CppException.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.Exceptions; + +public class Il2CppException : Exception +{ + [ThreadStatic] + private static byte[]? ourMessageBytes; + + public static Func? ParseMessageHook; + + public readonly Il2CppSystem.Exception Il2cppObject; + + public Il2CppException(Il2CppSystem.Exception il2cppObject) + { + Il2cppObject = il2cppObject; + } + + public override string Message => BuildMessage(Il2cppObject); + + private static unsafe string BuildMessage(Il2CppSystem.Exception il2cppException) + { + var exception = il2cppException.Pointer; + + if (ParseMessageHook != null) + return ParseMessageHook(exception); + + ourMessageBytes ??= new byte[65536]; + fixed (byte* message = ourMessageBytes) + { + IL2CPP.il2cpp_format_exception(exception, message, ourMessageBytes.Length); + } + + var builtMessage = Encoding.UTF8.GetString(ourMessageBytes, 0, Array.IndexOf(ourMessageBytes, (byte)0)); + return $""" + {builtMessage} + --- BEGIN IL2CPP STACK TRACE --- + {il2cppException.ToString(false, true)} + --- END IL2CPP STACK TRACE --- + + """; + } + + public static void RaiseExceptionIfNecessary(IntPtr returnedException) + { + if (returnedException == IntPtr.Zero) + return; + + var il2cppException = (IIl2CppException?)Il2CppObjectPool.Get(returnedException); + Debug.Assert(il2cppException is not null); + + throw il2cppException.CreateSystemException(); + } + + [return: NotNullIfNotNull(nameof(exception))] + public static Il2CppException? FromNativeObject(IIl2CppException? exception) + { + return exception?.CreateSystemException(); + } +} diff --git a/Il2CppInterop.Runtime/ObjectCollectedException.cs b/Il2CppInterop.Runtime/Exceptions/ObjectCollectedException.cs similarity index 77% rename from Il2CppInterop.Runtime/ObjectCollectedException.cs rename to Il2CppInterop.Runtime/Exceptions/ObjectCollectedException.cs index 9dbbc0e1..8ae419d9 100644 --- a/Il2CppInterop.Runtime/ObjectCollectedException.cs +++ b/Il2CppInterop.Runtime/Exceptions/ObjectCollectedException.cs @@ -1,6 +1,6 @@ using System; -namespace Il2CppInterop.Runtime; +namespace Il2CppInterop.Runtime.Exceptions; public class ObjectCollectedException : Exception { diff --git a/Il2CppInterop.Common/Extensions/AssemblyExtensions.cs b/Il2CppInterop.Runtime/Extensions/AssemblyExtensions.cs similarity index 51% rename from Il2CppInterop.Common/Extensions/AssemblyExtensions.cs rename to Il2CppInterop.Runtime/Extensions/AssemblyExtensions.cs index a783e6da..4d51ea11 100644 --- a/Il2CppInterop.Common/Extensions/AssemblyExtensions.cs +++ b/Il2CppInterop.Runtime/Extensions/AssemblyExtensions.cs @@ -1,9 +1,13 @@ -using System.Reflection; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; -namespace Il2CppInterop.Common.Extensions; +namespace Il2CppInterop.Runtime.Extensions; internal static class AssemblyExtensions { + [RequiresUnreferencedCode("")] public static Type[] GetTypesSafe(this Assembly assembly) { try @@ -12,7 +16,7 @@ public static Type[] GetTypesSafe(this Assembly assembly) } catch (ReflectionTypeLoadException ex) { - return ex.Types.Where(t => t != null).ToArray(); + return ex.Types.Where(t => t != null).ToArray()!; } } } diff --git a/Il2CppInterop.Runtime/Extensions/INativeMethodInfoStructExtensions.cs b/Il2CppInterop.Runtime/Extensions/INativeMethodInfoStructExtensions.cs new file mode 100644 index 00000000..b6ac2a47 --- /dev/null +++ b/Il2CppInterop.Runtime/Extensions/INativeMethodInfoStructExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; + +namespace Il2CppInterop.Runtime.Extensions; + +internal static class INativeMethodInfoStructExtensions +{ + extension(INativeMethodInfoStruct methodInfo) + { + public unsafe ReadOnlySpan GetNameSpan() + { + var namePtr = methodInfo.Name; + if (namePtr == IntPtr.Zero) + return default; + + // Find null terminator + var length = 0; + while (Marshal.ReadByte(namePtr, length) > 0) + { + length++; + } + return new ReadOnlySpan((void*)namePtr, length); + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + [return: NotNullIfNotNull(nameof(generatedMethod))] + public static unsafe INativeMethodInfoStruct? FromGeneratedMethod(MethodBase? generatedMethod) + { + if (generatedMethod is null) + return null; + + var methodInfoPointerField = Il2CppInternalsAccess.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(generatedMethod) + ?? throw new ArgumentException($"Couldn't find the generated method info pointer for {generatedMethod.Name}"); + + // Il2CppClassPointerStore calls the static constructor for the type + Il2CppType.GetClassPointer(generatedMethod.DeclaringType!); + + var methodInfoPointer = (IntPtr)methodInfoPointerField.GetValue(null)!; + if (methodInfoPointer == IntPtr.Zero) + throw new ArgumentException($"Generated method info pointer for {generatedMethod.Name} doesn't point to any il2cpp method info"); + + return UnityVersionHandler.Wrap((Il2CppMethodInfo*)methodInfoPointer); + } + } +} diff --git a/Il2CppInterop.Runtime/Extensions/Il2CppSystemTypeExtensions.cs b/Il2CppInterop.Runtime/Extensions/Il2CppSystemTypeExtensions.cs new file mode 100644 index 00000000..6f527ab1 --- /dev/null +++ b/Il2CppInterop.Runtime/Extensions/Il2CppSystemTypeExtensions.cs @@ -0,0 +1,220 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppSystem; + +namespace Il2CppInterop.Runtime.Extensions; + +internal static class Il2CppSystemTypeExtensions +{ + extension(Type type) + { + public static Type FromTypePointer(nint typePointer) + { + // Ensure Il2CppSystem.RuntimeType is initialized before we call Il2CppSystem.Type.internal_from_handle + RuntimeHelpers.RunClassConstructor(typeof(RuntimeType).TypeHandle); + + return Type.internal_from_handle(typePointer); + } + + // Note: This method is referenced via UnsafeAccessor in Il2CppObjectPool + public static Type FromClassPointer(nint classPointer) + { + var il2CppType = IL2CPP.il2cpp_class_get_type(classPointer); + if (il2CppType == default) + { + throw new System.ArgumentException($"Class pointer {classPointer} does not have a corresponding IL2CPP type pointer", nameof(classPointer)); + } + return Type.FromTypePointer(il2CppType); + } + + [RequiresDynamicCode("")] + public static Type FromSystemType(System.Type systemType) + { + var classPointer = Il2CppType.GetClassPointer(systemType); + if (classPointer == default) + { + throw new System.ArgumentException($"{systemType} does not have a corresponding IL2CPP class pointer", nameof(systemType)); + } + + return FromClassPointer(classPointer); + } + + // Note: This method is referenced via UnsafeAccessor in Il2CppObjectPool + [RequiresDynamicCode("")] + [RequiresUnreferencedCode("")] + public System.Type ToSystemType() + { + if (type.IsTypeDefinition) + { + return GetSystemTypeDefinition(type); + } + else if (type.ContainsGenericParameters) + { + throw new System.NotSupportedException($"Cannot convert type {type.FullName} to a system type because it contains generic parameters."); + } + else if (type.IsByRef) + { + return typeof(ByReference<>).MakeGenericType(type.GetElementType().ToSystemType()); + } + else if (type.IsPointer) + { + return typeof(Pointer<>).MakeGenericType(type.GetElementType().ToSystemType()); + } + else if (type.IsSZArray) + { + return typeof(Il2CppArrayRank1<>).MakeGenericType(type.GetElementType().ToSystemType()); + } + else if (type.IsArray) + { + int rank = type.GetArrayRank(); + var arrayGenericType = rank switch + { + 1 => typeof(Il2CppArrayRank1<>), + 2 => typeof(Il2CppArrayRank2<>), + 3 => typeof(Il2CppArrayRank3<>), + 4 => typeof(Il2CppArrayRank4<>), + 5 => typeof(Il2CppArrayRank5<>), + _ => throw new System.NotSupportedException($"Cannot convert type {type.FullName} to a system type because it is an array with rank {rank}, which is not supported.") + }; + return arrayGenericType.MakeGenericType(type.GetElementType().ToSystemType()); + } + else if (type.IsGenericType) + { + var genericTypeDefinition = type.GetGenericTypeDefinition().ToSystemType(); + var genericArguments = type.GetGenericArguments().Select(t => t.ToSystemType()).ToArray(); + return genericTypeDefinition.MakeGenericType(genericArguments); + } + else + { + throw new System.NotSupportedException($"Cannot convert type {type.FullName} to a system type."); + } + } + + public nint ToTypePointer() + { + return type.TypeHandle.value; + } + + /// + /// Get the class pointer for this type + /// + /// + /// This can be null if the type doesn't have a corresponding class, such as generic type instance not used in the game. + /// + /// The class pointer for this type + public nint ToClassPointer() + { + return IL2CPP.il2cpp_class_from_type(type.ToTypePointer()); + } + } + + [RequiresDynamicCode("")] + [RequiresUnreferencedCode("")] + private static System.Type GetSystemTypeDefinition(Type type) + { + if (type.IsNested) + { + var declaringType = GetSystemTypeDefinition(type.DeclaringType); + var il2CppTypeName = type.NameOrDefault ?? ""; + return TryGetNestedSystemType(declaringType, il2CppTypeName) + ?? throw new System.NullReferenceException($"Could not find system type for nested type {il2CppTypeName} in declaring type {declaringType.FullName}"); + } + else + { + return TryGetTopLevelSystemType(type.Assembly.GetName().Name ?? "", type.Namespace ?? "", type.NameOrDefault ?? "") + ?? throw new System.NullReferenceException($"Could not find system type for top-level type {type.FullName}"); + } + } + + [RequiresDynamicCode("")] + private static System.Type? TryGetNestedSystemType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] System.Type declaringType, string il2CppTypeName) + { + foreach (var nestedType in declaringType.GetNestedTypes(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)) + { + if (!nestedType.IsAssignableTo(typeof(IIl2CppType))) + { + continue; + } + + var name = typeof(Il2CppType).GetMethod(nameof(Il2CppType.GetName))!.MakeGenericMethod(nestedType).Invoke(null, null) as string ?? string.Empty; + if (string.Equals(name, il2CppTypeName, System.StringComparison.Ordinal)) + { + return nestedType; + } + } + return null; + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static System.Type? TryGetTopLevelSystemType(string il2CppAssemblyName, string il2CppTypeNamespace, string il2CppTypeName) + { + var systemAssembly = TryGetSystemAssembly(il2CppAssemblyName); + + if (systemAssembly == null) + { + return null; + } + + foreach (var type in systemAssembly.GetTypes()) + { + if (type.IsNested || !type.IsAssignableTo(typeof(IIl2CppType))) + { + continue; + } + var (typeNamespace, typeName) = GetIl2CppTypeNamespaceAndName(type); + if (string.Equals(typeNamespace, il2CppTypeNamespace, System.StringComparison.Ordinal) && + string.Equals(typeName, il2CppTypeName, System.StringComparison.Ordinal)) + { + return type; + } + } + + return null; + + [RequiresDynamicCode("")] + static (string Namespace, string Name) GetIl2CppTypeNamespaceAndName(System.Type systemType) + { + var @namespace = typeof(Il2CppType).GetMethod(nameof(Il2CppType.GetNamespace))!.MakeGenericMethod(systemType).Invoke(null, null) as string ?? string.Empty; + var name = typeof(Il2CppType).GetMethod(nameof(Il2CppType.GetName))!.MakeGenericMethod(systemType).Invoke(null, null) as string ?? string.Empty; + return (@namespace, name); + } + } + + private static System.Reflection.Assembly? TryGetSystemAssembly(string il2CppAssemblyName) + { + return TryGetSystemAssemblyImplementation(il2CppAssemblyName) ?? TryGetSystemAssemblyImplementation("Il2Cpp" + il2CppAssemblyName); + + static System.Reflection.Assembly? TryGetSystemAssemblyImplementation(string exactName) + { + var alternateName = exactName.EndsWith(".dll", System.StringComparison.OrdinalIgnoreCase) + ? exactName.Substring(0, exactName.Length - 4) + : exactName + ".dll"; + + foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies()) + { + var name = assembly.GetName().Name; + if (string.Equals(name, exactName, System.StringComparison.OrdinalIgnoreCase) || + string.Equals(name, alternateName, System.StringComparison.OrdinalIgnoreCase)) + { + return assembly; + } + } + return null; + } + } + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetGenericArguments")] + [return: UnsafeAccessorType($"Il2CppInterop.Runtime.InteropTypes.Arrays.{nameof(Il2CppArrayRank1<>)}`1[[Il2CppSystem.Type, Il2Cppmscorlib]]")] + private static extern object GetGenericArgumentsInternal(Type type); + + private static IReadOnlyList GetGenericArguments(this Type type) + { + return (IReadOnlyList)GetGenericArgumentsInternal(type); + } +} diff --git a/Il2CppInterop.Runtime/Extensions/XxHash128Extensions.cs b/Il2CppInterop.Runtime/Extensions/XxHash128Extensions.cs new file mode 100644 index 00000000..3069b44f --- /dev/null +++ b/Il2CppInterop.Runtime/Extensions/XxHash128Extensions.cs @@ -0,0 +1,52 @@ +using System; +using System.Buffers; +using System.IO.Hashing; +using System.Runtime.InteropServices; +using System.Text; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.Extensions; + +internal static class XxHash128Extensions +{ + public static void Append(this XxHash128 hash, byte value) + { + ReadOnlySpan data = new ReadOnlySpan(ref value); + hash.Append(data); + } + + public static void Append(this XxHash128 hash, bool value) + { + hash.Append((byte)(value ? 1 : 0)); + } + + public static void Append(this XxHash128 hash, ReadOnlySpan characters) + { + var data = MemoryMarshal.AsBytes(characters); + hash.Append(data); + } + + public static void AppendUtf8(this XxHash128 hash, ReadOnlySpan characters) + { + var buffer = ArrayPool.Shared.Rent(Encoding.UTF8.GetMaxByteCount(characters.Length)); + var bytesWritten = Encoding.UTF8.GetBytes(characters, buffer); + hash.Append(buffer.AsSpan(0, bytesWritten)); + ArrayPool.Shared.Return(buffer); + } + + public static void Append(this XxHash128 hash, Il2CppSystem.String? str) + { + hash.Append(GetSpan(str)); + } + + private static unsafe ReadOnlySpan GetSpan(Il2CppSystem.String? str) + { + if (str is null) + return default; + + var pointer = str.Pointer; + var length = IL2CPP.il2cpp_string_length(pointer); + var characters = IL2CPP.il2cpp_string_chars(pointer); + return new ReadOnlySpan(characters, length); + } +} diff --git a/Il2CppInterop.Runtime/FieldAccess.cs b/Il2CppInterop.Runtime/FieldAccess.cs new file mode 100644 index 00000000..821cd598 --- /dev/null +++ b/Il2CppInterop.Runtime/FieldAccess.cs @@ -0,0 +1,113 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime; + +public static unsafe class FieldAccess +{ + public static T? GetStaticFieldValue(nint fieldInfo) where T : IIl2CppType + { + ArgumentNullException.ThrowIfNull(fieldInfo.ToPointer(), nameof(fieldInfo)); + var data = stackalloc byte[T.Size]; + IL2CPP.il2cpp_field_static_get_value(fieldInfo, data); + return T.ReadFromSpan(new ReadOnlySpan(data, T.Size)); + } + + public static void SetStaticFieldValue(nint fieldInfo, T? value) where T : IIl2CppType + { + ArgumentNullException.ThrowIfNull(fieldInfo.ToPointer(), nameof(fieldInfo)); + if (typeof(T).IsValueType) + { + var data = stackalloc byte[T.Size]; + value.WriteToPointer(data); + IL2CPP.il2cpp_field_static_set_value(fieldInfo, data); + } + else + { + IL2CPP.il2cpp_field_static_set_value(fieldInfo, (void*)NativeBoxing.Box(value)); + } + } + + public static T? GetInstanceFieldValue(Object instance, int fieldOffset) where T : IIl2CppType + { + ArgumentOutOfRangeException.ThrowIfNegative(fieldOffset); + var data = (byte*)instance.Pointer + fieldOffset; + return Il2CppType.ReadFromPointer(data); + } + + public static void SetInstanceFieldValue(Object instance, int fieldOffset, T? value) where T : IIl2CppType + { + FunctionPointerCache.SetInstanceFieldValue(instance, fieldOffset, value); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetInstanceFieldValue_WriteBarrier(Object instance, int fieldOffset, T? value) where T : IIl2CppType + { + ArgumentOutOfRangeException.ThrowIfNegative(fieldOffset); + var data = (byte*)instance.Pointer + fieldOffset; + if (typeof(T).IsValueType) + { + value.WriteToPointer(data); + } + else + { + IL2CPP.il2cpp_gc_wbarrier_set_field(instance.Pointer, (nint)data, (nint)NativeBoxing.Box(value)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetInstanceFieldValue_Pointer(Object instance, int fieldOffset, T? value) where T : IIl2CppType + { + ArgumentOutOfRangeException.ThrowIfNegative(fieldOffset); + var data = (byte*)instance.Pointer + fieldOffset; + if (typeof(T).IsValueType) + { + value.WriteToPointer(data); + } + else + { + *(nint*)data = (nint)NativeBoxing.Box(value); + } + } + + public static nint GetFieldInfo(nint classPointer, string fieldName) + { + if (classPointer == nint.Zero) + return nint.Zero; + + var field = IL2CPP.il2cpp_class_get_field_from_name(classPointer, fieldName); + if (field == nint.Zero) + Logger.Instance.LogError("Field {FieldName} was not found on class {ClassName}", fieldName, IL2CPP.il2cpp_class_get_name(classPointer)); + return field; + } + + public static int GetFieldOffset(nint fieldInfo) + { + if (fieldInfo == nint.Zero) + return -1; + return (int)IL2CPP.il2cpp_field_get_offset(fieldInfo); + } + + private static bool HasWriteBarrierSupport() + { + if (NativeLibrary.TryLoad("GameAssembly", out var handle)) + { + var result = NativeLibrary.TryGetExport(handle, "il2cpp_gc_wbarrier_set_field", out _); + NativeLibrary.Free(handle); + return result; + } + return false; + } + + private static bool WriteBarrierSupport { get; } = HasWriteBarrierSupport(); + + private static class FunctionPointerCache where T : IIl2CppType + { + public static readonly delegate* SetInstanceFieldValue = WriteBarrierSupport + ? &FieldAccess.SetInstanceFieldValue_WriteBarrier + : &FieldAccess.SetInstanceFieldValue_Pointer; + } +} diff --git a/Il2CppInterop.Runtime/GenerationInternals.cs b/Il2CppInterop.Runtime/GenerationInternals.cs new file mode 100644 index 00000000..8f8c6fc1 --- /dev/null +++ b/Il2CppInterop.Runtime/GenerationInternals.cs @@ -0,0 +1,377 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Exceptions; +using Il2CppInterop.Runtime.Extensions; +using Il2CppInterop.Runtime.Injection; +using Il2CppInterop.Runtime.Structs; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime; + +/// +/// Do not reference this class. Everything in it is an implementation detail of the generator. Breaking changes may occur at any time without warning. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class GenerationInternals +{ + public static unsafe string? Il2CppStringToManaged(Il2CppSystem.String? il2CppString) + { + if (il2CppString == null) + return null; + + var il2CppStringPtr = il2CppString.Pointer; + + var length = IL2CPP.il2cpp_string_length(il2CppStringPtr); + var chars = IL2CPP.il2cpp_string_chars(il2CppStringPtr); + + return new string(chars, 0, length); + } + + public static unsafe Il2CppSystem.String? ManagedStringToIl2Cpp(string? str) + { + if (str == null) + return null; + + fixed (char* chars = str) + { + return new Il2CppSystem.String((ObjectPointer)IL2CPP.il2cpp_string_new_utf16(chars, str.Length)); + } + } + + [RequiresDynamicCode("")] + public static Il2CppSystem.Type ManagedTypeToIl2CppType(Type type) + { + return Il2CppSystem.Type.FromSystemType(type); + } + + [RequiresDynamicCode("")] + [RequiresUnreferencedCode("")] + public static Type Il2CppTypeToManagedType(Il2CppSystem.Type type) + { + return type.ToSystemType(); + } + + [RequiresDynamicCode("")] + public static unsafe Il2CppSystem.TypedReference MakeRefAny(void* value) where T : IIl2CppType + { + Il2CppSystem.TypedReference result = default; + result.Value = (IntPtr)value; + result.Type = Il2CppTypePointerStore.NativeTypePointer; + result.type = Il2CppSystem.Type.FromTypePointer(result.Type).TypeHandle; + return result; + } + + public static unsafe T? RefAnyValue(Il2CppSystem.TypedReference typedReference) where T : IIl2CppType + { + return Il2CppType.ReadFromPointer((void*)(nint)typedReference.Value); + } + + public static Il2CppSystem.RuntimeTypeHandle RefAnyType(Il2CppSystem.TypedReference typedReference) + { + if (typedReference.type.value != nint.Zero) + return typedReference.type; + + if (typedReference.Type != nint.Zero) + return Il2CppSystem.Type.FromTypePointer(typedReference.Type).TypeHandle; + + throw new InvalidOperationException("TypedReference does not have a type"); + } + + public static unsafe IIl2CppType? LoadIndirectReference(void* address) + { + ArgumentNullException.ThrowIfNull(address); + var objectPointer = *(nint*)address; + return (IIl2CppType?)Il2CppObjectPool.Get(objectPointer); + } + + public static unsafe void StoreIndirectReference(void* address, IIl2CppType? obj) + { + ArgumentNullException.ThrowIfNull(address); + var objectPointer = obj?.BoxNative() ?? ObjectPointer.Null; + *(nint*)address = (nint)objectPointer; + } + + // For unstripping the box instruction + public static object? Box(T? value) where T : IIl2CppType + { + return value?.Box(); + } + + // For unstripping the unbox.any instruction + public static T? Unbox(object? obj) where T : IIl2CppType + { + return T.Unbox(obj); + } + + public static object? BoxNullableValueType(in Il2CppSystem.Nullable nullable) where T : struct, IIl2CppType, Il2CppSystem.IValueType + { + return nullable.hasValue + ? nullable.value.Box() + : null; + } + + public static Il2CppSystem.Nullable UnboxNullableValueType(object? obj) where T : struct, IIl2CppType, Il2CppSystem.IValueType + { + return obj switch + { + null => default, + T value => Constructor(true, value), + Il2CppSystem.Nullable nullable => nullable, + _ => throw new InvalidCastException(), + }; + + static Il2CppSystem.Nullable Constructor(bool hasValue, T value) + { + Il2CppSystem.Nullable result = default; + result.hasValue = hasValue; + result.value = value; + return result; + } + } + + public static nint Il2CppGCHandleGetTargetOrThrow(nint gchandle) + { + var obj = IL2CPP.il2cpp_gchandle_get_target(gchandle); + if (obj == nint.Zero) + throw new ObjectCollectedException("Object was garbage collected in IL2CPP domain"); + return obj; + } + + public static bool Il2CppGCHandleGetTargetWasCollected(nint gchandle) + { + var obj = IL2CPP.il2cpp_gchandle_get_target(gchandle); + return obj == nint.Zero; + } + + /// + /// Get the class pointer for a generic instance type + /// + /// The class pointer for the generic type definition + /// The class pointers for the generic type arguments + /// The class pointer for the generic instance type + public static nint GetIl2CppGenericInstanceType(nint typeClassPointer, params nint[] genericTypeArguments) + { + var types = new Il2CppSystem.Type[genericTypeArguments.Length]; + for (var i = 0; i < genericTypeArguments.Length; i++) + { + types[i] = Il2CppSystem.Type.FromClassPointer(genericTypeArguments[i]); + } + var genericTypeInstance = Il2CppSystem.Type.FromClassPointer(typeClassPointer).MakeGenericType(types); + var result = genericTypeInstance.ToClassPointer(); + if (result == nint.Zero) + { + var className = IL2CPP.il2cpp_class_get_name(typeClassPointer); + Logger.Instance.LogTrace("Could not get class pointer for generic instance type {ClassName}`{TypeArgumentCount}. Creating one...", className, genericTypeArguments.Length); + result = GenericTypeInflater.InflateGenericType(typeClassPointer, genericTypeArguments); + } + return result; + } + + public static nint GetIl2CppMethodByToken(nint clazz, int token) + { + if (clazz == nint.Zero) + return GetMethodInfoForMissingMethod(token.ToString()); + + var iter = nint.Zero; + nint method; + while ((method = IL2CPP.il2cpp_class_get_methods(clazz, ref iter)) != nint.Zero) + if (IL2CPP.il2cpp_method_get_token(method) == token) + return method; + + var className = IL2CPP.il2cpp_class_get_name(clazz); + Logger.Instance.LogTrace("Unable to find method {ClassName}::{Token}", className, token); + + return GetMethodInfoForMissingMethod($"{className}::{token}"); + } + + public static nint GetIl2CppMethod(nint clazz, bool isGeneric, string methodName, string returnTypeName, params string[] argTypes) + { + if (clazz == nint.Zero) + return GetMethodInfoForMissingMethod($"{methodName}({string.Join(", ", argTypes)})"); + + returnTypeName = GenericArityRegex.Replace(returnTypeName, "").Replace('/', '.').Replace('+', '.'); + for (var index = 0; index < argTypes.Length; index++) + { + var argType = argTypes[index]; + argTypes[index] = GenericArityRegex.Replace(argType, "").Replace('/', '.').Replace('+', '.'); + } + + var methodsSeen = 0; + var lastMethod = nint.Zero; + var iter = nint.Zero; + nint method; + while ((method = IL2CPP.il2cpp_class_get_methods(clazz, ref iter)) != nint.Zero) + { + if (IL2CPP.il2cpp_method_get_name(method) != methodName) + continue; + + if (IL2CPP.il2cpp_method_get_param_count(method) != argTypes.Length) + continue; + + if (IL2CPP.il2cpp_method_is_generic(method) != isGeneric) + continue; + + var returnType = IL2CPP.il2cpp_method_get_return_type(method); + var returnTypeNameActual = IL2CPP.il2cpp_type_get_name(returnType); + if (returnTypeNameActual != returnTypeName) + continue; + + methodsSeen++; + lastMethod = method; + + var badType = false; + for (var i = 0; i < argTypes.Length; i++) + { + var paramType = IL2CPP.il2cpp_method_get_param(method, (uint)i); + var typeName = IL2CPP.il2cpp_type_get_name(paramType); + if (typeName != argTypes[i]) + { + badType = true; + break; + } + } + + if (badType) continue; + + return method; + } + + var className = IL2CPP.il2cpp_class_get_name(clazz); + + if (methodsSeen == 1) + { + Logger.Instance.LogTrace( + "Method {ClassName}::{MethodName} was stubbed with a random matching method of the same name", className, methodName); + Logger.Instance.LogTrace( + "Stubby return type/target: {LastMethod} / {ReturnTypeName}", IL2CPP.il2cpp_type_get_name(IL2CPP.il2cpp_method_get_return_type(lastMethod)), returnTypeName); + Logger.Instance.LogTrace("Stubby parameter types/targets follow:"); + for (var i = 0; i < argTypes.Length; i++) + { + var paramType = IL2CPP.il2cpp_method_get_param(lastMethod, (uint)i); + var typeName = IL2CPP.il2cpp_type_get_name(paramType); + Logger.Instance.LogTrace(" {TypeName} / {ArgType}", typeName, argTypes[i]); + } + + return lastMethod; + } + + Logger.Instance.LogTrace("Unable to find method {ClassName}::{MethodName}; signature follows", className, methodName); + Logger.Instance.LogTrace(" return {ReturnTypeName}", returnTypeName); + foreach (var argType in argTypes) + Logger.Instance.LogTrace(" {ArgType}", argType); + Logger.Instance.LogTrace("Available methods of this name follow:"); + iter = nint.Zero; + while ((method = IL2CPP.il2cpp_class_get_methods(clazz, ref iter)) != nint.Zero) + { + if (IL2CPP.il2cpp_method_get_name(method) != methodName) + continue; + + var nParams = IL2CPP.il2cpp_method_get_param_count(method); + Logger.Instance.LogTrace("Method starts"); + Logger.Instance.LogTrace(" return {MethodTypeName}", IL2CPP.il2cpp_type_get_name(IL2CPP.il2cpp_method_get_return_type(method))); + for (var i = 0; i < nParams; i++) + { + var paramType = IL2CPP.il2cpp_method_get_param(method, (uint)i); + var typeName = IL2CPP.il2cpp_type_get_name(paramType); + Logger.Instance.LogTrace(" {TypeName}", typeName); + } + + return method; + } + + return GetMethodInfoForMissingMethod($"{className}::{methodName}({string.Join(", ", argTypes)})"); + } + + private static IntPtr GetMethodInfoForMissingMethod(string methodName) + { + var methodInfo = UnityVersionHandler.NewMethod(); + methodInfo.Name = Marshal.StringToCoTaskMemUTF8(methodName); + methodInfo.Slot = ushort.MaxValue; + return methodInfo.Pointer; + } + public static void Il2CppRuntimeClassInit(nint @class) + { + try + { + if (@class != nint.Zero) + { + IL2CPP.il2cpp_runtime_class_init(@class); + } + } + catch (Exception ex) + { + Logger.Instance.LogError(ex, "Error initializing class {ClassName}", IL2CPP.il2cpp_class_get_name(@class)); + } + } + + public static nint GetIl2CppClass(string assemblyName, string namespaze, string className) + { + return IL2CPP.il2cpp_class_from_name(AssemblyInjector.GetOrCreateImage(assemblyName).Pointer, namespaze, className); + } + + public static int GetIl2CppValueSize(nint klass) + { + if (klass == nint.Zero) + return 0; + return IL2CPP.il2cpp_class_value_size(klass, out _); + } + + public static nint GetIl2CppGenericInstanceMethod(nint methodInfoPointer, nint declaringTypeClassPointer, params nint[] genericMethodArguments) + { + var types = new Il2CppSystem.Type[genericMethodArguments.Length]; + for (var i = 0; i < genericMethodArguments.Length; i++) + { + types[i] = Il2CppSystem.Type.FromClassPointer(genericMethodArguments[i]); + } + var methodInfoObject = (Il2CppSystem.Reflection.MethodInfo)Il2CppObjectPool.Get(IL2CPP.il2cpp_method_get_object(methodInfoPointer, declaringTypeClassPointer))!; + var methodInfoGeneric = methodInfoObject.MakeGenericMethod(types); + return il2cpp_method_get_from_reflection(methodInfoGeneric?.Pointer ?? throw new NullReferenceException()); + + static unsafe nint il2cpp_method_get_from_reflection(nint method) + { + if (UnityVersionHandler.HasGetMethodFromReflection) + return IL2CPP.il2cpp_method_get_from_reflection(method); + Il2CppReflectionMethod* reflectionMethod = (Il2CppReflectionMethod*)method; + return (nint)reflectionMethod->method; + } + } + + public static nint GetIl2CppNestedType(nint enclosingType, string nestedTypeName) + { + if (enclosingType == nint.Zero) + return nint.Zero; + + var iter = nint.Zero; + nint nestedTypePtr; + if (IL2CPP.il2cpp_class_is_inflated(enclosingType)) + { + Logger.Instance.LogTrace("Original class was inflated, falling back to reflection"); + + return GetNestedTypeViaReflection(enclosingType, nestedTypeName); + } + + while ((nestedTypePtr = IL2CPP.il2cpp_class_get_nested_types(enclosingType, ref iter)) != nint.Zero) + if (IL2CPP.il2cpp_class_get_name(nestedTypePtr) == nestedTypeName) + return nestedTypePtr; + + Logger.Instance.LogError( + "Nested type {NestedTypeName} on {EnclosingTypeName} not found!", nestedTypeName, IL2CPP.il2cpp_class_get_name(enclosingType)); + + return nint.Zero; + + static IntPtr GetNestedTypeViaReflection(nint enclosingClass, string nestedTypeName) + { + var reflectionType = Il2CppSystem.Type.FromClassPointer(enclosingClass); + var nestedType = reflectionType.GetNestedType(nestedTypeName, Il2CppSystem.Reflection.BindingFlags.Public | Il2CppSystem.Reflection.BindingFlags.NonPublic); + + return nestedType?.ToClassPointer() ?? nint.Zero; + } + } + + [GeneratedRegex(@"\`\d+")] + private static partial Regex GenericArityRegex { get; } +} diff --git a/Il2CppInterop.Runtime/IL2CPP.cs b/Il2CppInterop.Runtime/IL2CPP.cs deleted file mode 100644 index 0cb5acac..00000000 --- a/Il2CppInterop.Runtime/IL2CPP.cs +++ /dev/null @@ -1,1047 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using Il2CppInterop.Common; -using Il2CppInterop.Common.Attributes; -using Il2CppInterop.Runtime.InteropTypes; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using Il2CppInterop.Runtime.Runtime; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Runtime; - -public static unsafe class IL2CPP -{ - private static readonly Dictionary ourImagesMap = new(); - - static IL2CPP() - { - var domain = il2cpp_domain_get(); - if (domain == IntPtr.Zero) - { - Logger.Instance.LogError("No il2cpp domain found; sad!"); - return; - } - - uint assembliesCount = 0; - var assemblies = il2cpp_domain_get_assemblies(domain, ref assembliesCount); - for (var i = 0; i < assembliesCount; i++) - { - var image = il2cpp_assembly_get_image(assemblies[i]); - var name = il2cpp_image_get_name_(image)!; - ourImagesMap[name] = image; - } - } - - internal static IntPtr GetIl2CppImage(string name) - { - if (ourImagesMap.ContainsKey(name)) return ourImagesMap[name]; - return IntPtr.Zero; - } - - internal static IntPtr[] GetIl2CppImages() - { - return ourImagesMap.Values.ToArray(); - } - - public static IntPtr GetIl2CppClass(string assemblyName, string namespaze, string className) - { - if (!ourImagesMap.TryGetValue(assemblyName, out var image)) - { - Logger.Instance.LogError("Assembly {AssemblyName} is not registered in il2cpp", assemblyName); - return IntPtr.Zero; - } - - var clazz = il2cpp_class_from_name(image, namespaze, className); - return clazz; - } - - public static IntPtr GetIl2CppField(IntPtr clazz, string fieldName) - { - if (clazz == IntPtr.Zero) return IntPtr.Zero; - - var field = il2cpp_class_get_field_from_name(clazz, fieldName); - if (field == IntPtr.Zero) - Logger.Instance.LogError( - "Field {FieldName} was not found on class {ClassName}", fieldName, il2cpp_class_get_name_(clazz)); - return field; - } - - public static IntPtr GetIl2CppMethodByToken(IntPtr clazz, int token) - { - if (clazz == IntPtr.Zero) - return NativeStructUtils.GetMethodInfoForMissingMethod(token.ToString()); - - var iter = IntPtr.Zero; - IntPtr method; - while ((method = il2cpp_class_get_methods(clazz, ref iter)) != IntPtr.Zero) - if (il2cpp_method_get_token(method) == token) - return method; - - var className = il2cpp_class_get_name_(clazz); - Logger.Instance.LogTrace("Unable to find method {ClassName}::{Token}", className, token); - - return NativeStructUtils.GetMethodInfoForMissingMethod(className + "::" + token); - } - - public static IntPtr GetIl2CppMethod(IntPtr clazz, bool isGeneric, string methodName, string returnTypeName, - params string[] argTypes) - { - if (clazz == IntPtr.Zero) - return NativeStructUtils.GetMethodInfoForMissingMethod(methodName + "(" + string.Join(", ", argTypes) + - ")"); - - returnTypeName = Regex.Replace(returnTypeName, "\\`\\d+", "").Replace('/', '.').Replace('+', '.'); - for (var index = 0; index < argTypes.Length; index++) - { - var argType = argTypes[index]; - argTypes[index] = Regex.Replace(argType, "\\`\\d+", "").Replace('/', '.').Replace('+', '.'); - } - - var methodsSeen = 0; - var lastMethod = IntPtr.Zero; - var iter = IntPtr.Zero; - IntPtr method; - while ((method = il2cpp_class_get_methods(clazz, ref iter)) != IntPtr.Zero) - { - if (il2cpp_method_get_name_(method) != methodName) - continue; - - if (il2cpp_method_get_param_count(method) != argTypes.Length) - continue; - - if (il2cpp_method_is_generic(method) != isGeneric) - continue; - - var returnType = il2cpp_method_get_return_type(method); - var returnTypeNameActual = il2cpp_type_get_name_(returnType); - if (returnTypeNameActual != returnTypeName) - continue; - - methodsSeen++; - lastMethod = method; - - var badType = false; - for (var i = 0; i < argTypes.Length; i++) - { - var paramType = il2cpp_method_get_param(method, (uint)i); - var typeName = il2cpp_type_get_name_(paramType); - if (typeName != argTypes[i]) - { - badType = true; - break; - } - } - - if (badType) continue; - - return method; - } - - var className = il2cpp_class_get_name_(clazz); - - if (methodsSeen == 1) - { - Logger.Instance.LogTrace( - "Method {ClassName}::{MethodName} was stubbed with a random matching method of the same name", className, methodName); - Logger.Instance.LogTrace( - "Stubby return type/target: {LastMethod} / {ReturnTypeName}", il2cpp_type_get_name_(il2cpp_method_get_return_type(lastMethod)), returnTypeName); - Logger.Instance.LogTrace("Stubby parameter types/targets follow:"); - for (var i = 0; i < argTypes.Length; i++) - { - var paramType = il2cpp_method_get_param(lastMethod, (uint)i); - var typeName = il2cpp_type_get_name_(paramType); - Logger.Instance.LogTrace(" {TypeName} / {ArgType}", typeName, argTypes[i]); - } - - return lastMethod; - } - - Logger.Instance.LogTrace("Unable to find method {ClassName}::{MethodName}; signature follows", className, methodName); - Logger.Instance.LogTrace(" return {ReturnTypeName}", returnTypeName); - foreach (var argType in argTypes) - Logger.Instance.LogTrace(" {ArgType}", argType); - Logger.Instance.LogTrace("Available methods of this name follow:"); - iter = IntPtr.Zero; - while ((method = il2cpp_class_get_methods(clazz, ref iter)) != IntPtr.Zero) - { - if (il2cpp_method_get_name_(method) != methodName) - continue; - - var nParams = il2cpp_method_get_param_count(method); - Logger.Instance.LogTrace("Method starts"); - Logger.Instance.LogTrace( - " return {MethodTypeName}", il2cpp_type_get_name_(il2cpp_method_get_return_type(method))); - for (var i = 0; i < nParams; i++) - { - var paramType = il2cpp_method_get_param(method, (uint)i); - var typeName = il2cpp_type_get_name_(paramType); - Logger.Instance.LogTrace(" {TypeName}", typeName); - } - - return method; - } - - return NativeStructUtils.GetMethodInfoForMissingMethod(className + "::" + methodName + "(" + - string.Join(", ", argTypes) + ")"); - } - - public static string? Il2CppStringToManaged(IntPtr il2CppString) - { - if (il2CppString == IntPtr.Zero) return null; - - var length = il2cpp_string_length(il2CppString); - var chars = il2cpp_string_chars(il2CppString); - - return new string(chars, 0, length); - } - - public static IntPtr ManagedStringToIl2Cpp(string? str) - { - if (str == null) return IntPtr.Zero; - - fixed (char* chars = str) - { - return il2cpp_string_new_utf16(chars, str.Length); - } - } - - public static IntPtr Il2CppObjectBaseToPtr(Il2CppObjectBase obj) - { - return obj?.Pointer ?? IntPtr.Zero; - } - - public static IntPtr Il2CppObjectBaseToPtrNotNull(Il2CppObjectBase obj) - { - return obj?.Pointer ?? throw new NullReferenceException(); - } - - public static IntPtr GetIl2CppNestedType(IntPtr enclosingType, string nestedTypeName) - { - if (enclosingType == IntPtr.Zero) return IntPtr.Zero; - - var iter = IntPtr.Zero; - IntPtr nestedTypePtr; - if (il2cpp_class_is_inflated(enclosingType)) - { - Logger.Instance.LogTrace("Original class was inflated, falling back to reflection"); - - return RuntimeReflectionHelper.GetNestedTypeViaReflection(enclosingType, nestedTypeName); - } - - while ((nestedTypePtr = il2cpp_class_get_nested_types(enclosingType, ref iter)) != IntPtr.Zero) - if (il2cpp_class_get_name_(nestedTypePtr) == nestedTypeName) - return nestedTypePtr; - - Logger.Instance.LogError( - "Nested type {NestedTypeName} on {EnclosingTypeName} not found!", nestedTypeName, il2cpp_class_get_name_(enclosingType)); - - return IntPtr.Zero; - } - - public static void ThrowIfNull(object arg) - { - if (arg == null) - throw new NullReferenceException(); - } - - public static T ResolveICall(string signature) where T : Delegate - { - var icallPtr = il2cpp_resolve_icall(signature); - if (icallPtr == IntPtr.Zero) - { - Logger.Instance.LogTrace("ICall {Signature} not resolved", signature); - return GenerateDelegateForMissingICall(signature); - } - - return Marshal.GetDelegateForFunctionPointer(icallPtr); - } - - private static T GenerateDelegateForMissingICall(string signature) where T : Delegate - { - var invoke = typeof(T).GetMethod("Invoke")!; - - var trampoline = new DynamicMethod("(missing icall delegate) " + typeof(T).FullName, - invoke.ReturnType, invoke.GetParameters().Select(it => it.ParameterType).ToArray(), typeof(IL2CPP), true); - var bodyBuilder = trampoline.GetILGenerator(); - - bodyBuilder.Emit(OpCodes.Ldstr, $"ICall with signature {signature} was not resolved"); - bodyBuilder.Emit(OpCodes.Newobj, typeof(Exception).GetConstructor(new[] { typeof(string) })!); - bodyBuilder.Emit(OpCodes.Throw); - - return (T)trampoline.CreateDelegate(typeof(T)); - } - - public static T? PointerToValueGeneric(IntPtr objectPointer, bool isFieldPointer, bool valueTypeWouldBeBoxed) - { - if (isFieldPointer) - { - if (il2cpp_class_is_valuetype(Il2CppClassPointerStore.NativeClassPtr)) - objectPointer = il2cpp_value_box(Il2CppClassPointerStore.NativeClassPtr, objectPointer); - else - objectPointer = *(IntPtr*)objectPointer; - } - - if (!valueTypeWouldBeBoxed && il2cpp_class_is_valuetype(Il2CppClassPointerStore.NativeClassPtr)) - objectPointer = il2cpp_value_box(Il2CppClassPointerStore.NativeClassPtr, objectPointer); - - if (typeof(T) == typeof(string)) - return (T)(object)Il2CppStringToManaged(objectPointer); - - if (objectPointer == IntPtr.Zero) - return default; - - if (typeof(T).IsValueType) - return Il2CppObjectBase.UnboxUnsafe(objectPointer); - - return Il2CppObjectPool.Get(objectPointer); - } - - public static string RenderTypeName(bool addRefMarker = false) - { - return RenderTypeName(typeof(T), addRefMarker); - } - - public static string RenderTypeName(Type t, bool addRefMarker = false) - { - if (addRefMarker) return RenderTypeName(t) + "&"; - if (t.IsArray) return RenderTypeName(t.GetElementType()) + "[]"; - if (t.IsByRef) return RenderTypeName(t.GetElementType()) + "&"; - if (t.IsPointer) return RenderTypeName(t.GetElementType()) + "*"; - if (t.IsGenericParameter) return t.Name; - - if (t.IsGenericType) - { - if (t.TypeHasIl2CppArrayBase()) - return RenderTypeName(t.GetGenericArguments()[0]) + "[]"; - - var builder = new StringBuilder(); - builder.Append(t.GetGenericTypeDefinition().FullNameObfuscated().TrimIl2CppPrefix()); - builder.Append('<'); - var genericArguments = t.GetGenericArguments(); - for (var i = 0; i < genericArguments.Length; i++) - { - if (i != 0) builder.Append(','); - builder.Append(RenderTypeName(genericArguments[i])); - } - - builder.Append('>'); - return builder.ToString(); - } - - if (t == typeof(Il2CppStringArray)) - return "System.String[]"; - - return t.FullNameObfuscated().TrimIl2CppPrefix(); - } - - private static string FullNameObfuscated(this Type t) - { - var obfuscatedNameAnnotations = t.GetCustomAttribute(); - if (obfuscatedNameAnnotations == null) return t.FullName; - return obfuscatedNameAnnotations.ObfuscatedName; - } - - private static string TrimIl2CppPrefix(this string s) - { - return s.StartsWith("Il2Cpp") ? s.Substring("Il2Cpp".Length) : s; - } - - private static bool TypeHasIl2CppArrayBase(this Type type) - { - if (type == null) return false; - if (type.IsConstructedGenericType) type = type.GetGenericTypeDefinition(); - if (type == typeof(Il2CppArrayBase<>)) return true; - return TypeHasIl2CppArrayBase(type.BaseType); - } - - // this is called if there's no actual il2cpp_gc_wbarrier_set_field() - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FieldWriteWbarrierStub(IntPtr obj, IntPtr targetAddress, IntPtr value) - { - // ignore obj - *(IntPtr*)targetAddress = value; - } - - // IL2CPP Functions - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_init(IntPtr domain_name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_init_utf16(IntPtr domain_name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_shutdown(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_config_dir(IntPtr config_path); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_data_dir(IntPtr data_path); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_temp_dir(IntPtr temp_path); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_commandline_arguments(int argc, IntPtr argv, IntPtr basedir); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_commandline_arguments_utf16(int argc, IntPtr argv, IntPtr basedir); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_config_utf16(IntPtr executablePath); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_config(IntPtr executablePath); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_memory_callbacks(IntPtr callbacks); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_get_corlib(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_add_internal_call(IntPtr name, IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_alloc(uint size); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_free(IntPtr ptr); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_array_class_get(IntPtr element_class, uint rank); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_array_length(IntPtr array); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_array_get_byte_length(IntPtr array); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_array_new(IntPtr elementTypeInfo, ulong length); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_array_new_specific(IntPtr arrayTypeInfo, ulong length); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_array_new_full(IntPtr array_class, ref ulong lengths, ref ulong lower_bounds); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_bounded_array_class_get(IntPtr element_class, uint rank, - [MarshalAs(UnmanagedType.I1)] bool bounded); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_array_element_size(IntPtr array_class); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_assembly_get_image(IntPtr assembly); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_enum_basetype(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_generic(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_inflated(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_subclass_of(IntPtr klass, IntPtr klassc, - [MarshalAs(UnmanagedType.I1)] bool check_interfaces); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_has_parent(IntPtr klass, IntPtr klassc); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_from_il2cpp_type(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_from_name(IntPtr image, [MarshalAs(UnmanagedType.LPUTF8Str)] string namespaze, - [MarshalAs(UnmanagedType.LPUTF8Str)] string name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_from_system_type(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_element_class(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_events(IntPtr klass, ref IntPtr iter); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_fields(IntPtr klass, ref IntPtr iter); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_nested_types(IntPtr klass, ref IntPtr iter); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_interfaces(IntPtr klass, ref IntPtr iter); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_properties(IntPtr klass, ref IntPtr iter); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_property_from_name(IntPtr klass, IntPtr name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_field_from_name(IntPtr klass, - [MarshalAs(UnmanagedType.LPUTF8Str)] string name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_methods(IntPtr klass, ref IntPtr iter); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_method_from_name(IntPtr klass, - [MarshalAs(UnmanagedType.LPUTF8Str)] string name, int argsCount); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_class_get_name(IntPtr klass); - - public static string? il2cpp_class_get_name_(IntPtr klass) - => Marshal.PtrToStringUTF8(il2cpp_class_get_name(klass)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_class_get_namespace(IntPtr klass); - - public static string? il2cpp_class_get_namespace_(IntPtr klass) - => Marshal.PtrToStringUTF8(il2cpp_class_get_namespace(klass)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_parent(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_declaring_type(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_class_instance_size(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_class_num_fields(IntPtr enumKlass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_valuetype(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_class_value_size(IntPtr klass, ref uint align); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_blittable(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_class_get_flags(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_abstract(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_interface(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_class_array_element_size(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_from_type(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_type(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_class_get_type_token(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_has_attribute(IntPtr klass, IntPtr attr_class); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_has_references(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_class_is_enum(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_image(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_class_get_assemblyname(IntPtr klass); - - public static string? il2cpp_class_get_assemblyname_(IntPtr klass) - => Marshal.PtrToStringUTF8(il2cpp_class_get_assemblyname(klass)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_class_get_rank(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_class_get_bitmap_size(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_class_get_bitmap(IntPtr klass, ref uint bitmap); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_stats_dump_to_file(IntPtr path); - - //[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - //public extern static ulong il2cpp_stats_get_value(IL2CPP_Stat stat); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_domain_get(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_domain_assembly_open(IntPtr domain, IntPtr name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr* il2cpp_domain_get_assemblies(IntPtr domain, ref uint size); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr - il2cpp_exception_from_name_msg(IntPtr image, IntPtr name_space, IntPtr name, IntPtr msg); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_get_exception_argument_null(IntPtr arg); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_format_exception(IntPtr ex, void* message, int message_size); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_format_stack_trace(IntPtr ex, void* output, int output_size); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_unhandled_exception(IntPtr ex); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_field_get_flags(IntPtr field); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_field_get_name(IntPtr field); - - public static string? il2cpp_field_get_name_(IntPtr field) - => Marshal.PtrToStringUTF8(il2cpp_field_get_name(field)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_field_get_parent(IntPtr field); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_field_get_offset(IntPtr field); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_field_get_type(IntPtr field); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_field_get_value(IntPtr obj, IntPtr field, void* value); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_field_get_value_object(IntPtr field, IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_field_has_attribute(IntPtr field, IntPtr attr_class); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_field_set_value(IntPtr obj, IntPtr field, void* value); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_field_static_get_value(IntPtr field, void* value); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_field_static_set_value(IntPtr field, void* value); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_field_set_value_object(IntPtr instance, IntPtr field, IntPtr value); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_gc_collect(int maxGenerations); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_gc_collect_a_little(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_gc_disable(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_gc_enable(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_gc_is_disabled(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern long il2cpp_gc_get_used_size(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern long il2cpp_gc_get_heap_size(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_gc_wbarrier_set_field(IntPtr obj, IntPtr targetAddress, IntPtr gcObj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_gchandle_new(IntPtr obj, [MarshalAs(UnmanagedType.I1)] bool pinned); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_gchandle_new_weakref(IntPtr obj, - [MarshalAs(UnmanagedType.I1)] bool track_resurrection); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_gchandle_get_target(nint gchandle); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_gchandle_free(nint gchandle); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_unity_liveness_calculation_begin(IntPtr filter, int max_object_count, - IntPtr callback, IntPtr userdata, IntPtr onWorldStarted, IntPtr onWorldStopped); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_unity_liveness_calculation_end(IntPtr state); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_unity_liveness_calculation_from_root(IntPtr root, IntPtr state); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_unity_liveness_calculation_from_statics(IntPtr state); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_method_get_return_type(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_method_get_declaring_type(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_method_get_name(IntPtr method); - - public static string? il2cpp_method_get_name_(IntPtr method) - => Marshal.PtrToStringUTF8(il2cpp_method_get_name(method)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IntPtr il2cpp_method_get_from_reflection(IntPtr method) - { - if (UnityVersionHandler.HasGetMethodFromReflection) return _il2cpp_method_get_from_reflection(method); - Il2CppReflectionMethod* reflectionMethod = (Il2CppReflectionMethod*)method; - return (IntPtr)reflectionMethod->method; - } - - [DllImport("GameAssembly", EntryPoint = nameof(il2cpp_method_get_from_reflection), CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr _il2cpp_method_get_from_reflection(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_method_get_object(IntPtr method, IntPtr refclass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_method_is_generic(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_method_is_inflated(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_method_is_instance(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_method_get_param_count(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_method_get_param(IntPtr method, uint index); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_method_get_class(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_method_has_attribute(IntPtr method, IntPtr attr_class); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_method_get_flags(IntPtr method, ref uint iflags); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_method_get_token(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_method_get_param_name(IntPtr method, uint index); - - public static string? il2cpp_method_get_param_name_(IntPtr method, uint index) - => Marshal.PtrToStringUTF8(il2cpp_method_get_param_name(method, index)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_profiler_install(IntPtr prof, IntPtr shutdown_callback); - - // [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - // public extern static void il2cpp_profiler_set_events(IL2CPP_ProfileFlags events); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_profiler_install_enter_leave(IntPtr enter, IntPtr fleave); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_profiler_install_allocation(IntPtr callback); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_profiler_install_gc(IntPtr callback, IntPtr heap_resize_callback); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_profiler_install_fileio(IntPtr callback); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_profiler_install_thread(IntPtr start, IntPtr end); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_property_get_flags(IntPtr prop); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_property_get_get_method(IntPtr prop); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_property_get_set_method(IntPtr prop); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_property_get_name(IntPtr prop); - - public static string? il2cpp_property_get_name_(IntPtr prop) - => Marshal.PtrToStringUTF8(il2cpp_property_get_name(prop)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_property_get_parent(IntPtr prop); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_object_get_class(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_object_get_size(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_object_get_virtual_method(IntPtr obj, IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_object_new(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_object_unbox(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_value_box(IntPtr klass, IntPtr data); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_monitor_enter(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_monitor_try_enter(IntPtr obj, uint timeout); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_monitor_exit(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_monitor_pulse(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_monitor_pulse_all(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_monitor_wait(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_monitor_try_wait(IntPtr obj, uint timeout); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_runtime_invoke(IntPtr method, IntPtr obj, void** param, ref IntPtr exc); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - // param can be of Il2CppObject* - public static extern IntPtr il2cpp_runtime_invoke_convert_args(IntPtr method, IntPtr obj, void** param, - int paramCount, ref IntPtr exc); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_runtime_class_init(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_runtime_object_init(IntPtr obj); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_runtime_object_init_exception(IntPtr obj, ref IntPtr exc); - - // [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - // public extern static void il2cpp_runtime_unhandled_exception_policy_set(IL2CPP_RuntimeUnhandledExceptionPolicy value); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_string_length(IntPtr str); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern char* il2cpp_string_chars(IntPtr str); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_string_new(string str); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_string_new_len(string str, uint length); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_string_new_utf16(char* text, int len); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_string_new_wrapper(string str); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_string_intern(string str); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_string_is_interned(string str); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_thread_current(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_thread_attach(IntPtr domain); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_thread_detach(IntPtr thread); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void** il2cpp_thread_get_all_attached_threads(ref uint size); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_is_vm_thread(IntPtr thread); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_current_thread_walk_frame_stack(IntPtr func, IntPtr user_data); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_thread_walk_frame_stack(IntPtr thread, IntPtr func, IntPtr user_data); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_current_thread_get_top_frame(IntPtr frame); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_thread_get_top_frame(IntPtr thread, IntPtr frame); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_current_thread_get_frame_at(int offset, IntPtr frame); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_thread_get_frame_at(IntPtr thread, int offset, IntPtr frame); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_current_thread_get_stack_depth(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_thread_get_stack_depth(IntPtr thread); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_type_get_object(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern int il2cpp_type_get_type(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_type_get_class_or_element_class(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_type_get_name(IntPtr type); - - public static string? il2cpp_type_get_name_(IntPtr type) - => Marshal.PtrToStringUTF8(il2cpp_type_get_name(type)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_type_is_byref(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_type_get_attrs(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_type_equals(IntPtr type, IntPtr otherType); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_type_get_assembly_qualified_name(IntPtr type); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_image_get_assembly(IntPtr image); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_image_get_name(IntPtr image); - - public static string? il2cpp_image_get_name_(IntPtr image) - => Marshal.PtrToStringUTF8(il2cpp_image_get_name(image)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern nint il2cpp_image_get_filename(IntPtr image); - - public static string? il2cpp_image_get_filename_(IntPtr image) - => Marshal.PtrToStringUTF8(il2cpp_image_get_filename(image)); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_image_get_entry_point(IntPtr image); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern uint il2cpp_image_get_class_count(IntPtr image); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_image_get_class(IntPtr image, uint index); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_capture_memory_snapshot(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_free_captured_memory_snapshot(IntPtr snapshot); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_set_find_plugin_callback(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_register_log_callback(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_debugger_set_agent_options(IntPtr options); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_is_debugger_attached(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_unity_install_unitytls_interface(void* unitytlsInterfaceStruct); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_custom_attrs_from_class(IntPtr klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_custom_attrs_from_method(IntPtr method); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_custom_attrs_get_attr(IntPtr ainfo, IntPtr attr_klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool il2cpp_custom_attrs_has_attr(IntPtr ainfo, IntPtr attr_klass); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_custom_attrs_construct(IntPtr cinfo); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern void il2cpp_custom_attrs_free(IntPtr ainfo); -} diff --git a/Il2CppInterop.Runtime/Il2CppClassPointerStore.cs b/Il2CppInterop.Runtime/Il2CppClassPointerStore.cs deleted file mode 100644 index b75da661..00000000 --- a/Il2CppInterop.Runtime/Il2CppClassPointerStore.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; -using Il2CppInterop.Common.Attributes; -using Il2CppInterop.Runtime.Attributes; -using String = Il2CppSystem.String; -using Void = Il2CppSystem.Void; - -namespace Il2CppInterop.Runtime; - -public static class Il2CppClassPointerStore -{ - public static IntPtr GetNativeClassPointer(Type type) - { - if (type == typeof(void)) return Il2CppClassPointerStore.NativeClassPtr; - if (type == typeof(String)) return Il2CppClassPointerStore.NativeClassPtr; - return (IntPtr)typeof(Il2CppClassPointerStore<>) - .MakeGenericType(type) - .GetField(nameof(Il2CppClassPointerStore.NativeClassPtr)) - .GetValue(null); - } - - internal static void SetNativeClassPointer(Type type, IntPtr value) - { - typeof(Il2CppClassPointerStore<>) - .MakeGenericType(type) - .GetField(nameof(Il2CppClassPointerStore.NativeClassPtr)) - .SetValue(null, value); - } -} - -public static class Il2CppClassPointerStore -{ - public static IntPtr NativeClassPtr; - public static Type CreatedTypeRedirect; - - static Il2CppClassPointerStore() - { - var targetType = typeof(T); - if (!targetType.IsEnum) - { - RuntimeHelpers.RunClassConstructor(targetType.TypeHandle); - } - else - { - var assemblyName = targetType.Module.Name; - var @namespace = targetType.Namespace ?? ""; - var name = targetType.Name; - foreach (var customAttribute in targetType.CustomAttributes) - { - if (customAttribute.AttributeType != typeof(OriginalNameAttribute)) continue; - assemblyName = (string)customAttribute.ConstructorArguments[0].Value; - @namespace = (string)customAttribute.ConstructorArguments[1].Value; - name = (string)customAttribute.ConstructorArguments[2].Value; - } - - if (targetType.IsNested) - NativeClassPtr = - IL2CPP.GetIl2CppNestedType(Il2CppClassPointerStore.GetNativeClassPointer(targetType.DeclaringType), - name); - else - NativeClassPtr = - IL2CPP.GetIl2CppClass(assemblyName, @namespace, name); - } - - if (targetType.IsPrimitive || targetType == typeof(string)) - RuntimeHelpers.RunClassConstructor(AppDomain.CurrentDomain.GetAssemblies() - .Single(it => it.GetName().Name == "Il2Cppmscorlib").GetType("Il2Cpp" + targetType.FullName) - .TypeHandle); - - foreach (var customAttribute in targetType.CustomAttributes) - { - if (customAttribute.AttributeType != typeof(AlsoInitializeAttribute)) continue; - - var linkedType = (Type)customAttribute.ConstructorArguments[0].Value; - RuntimeHelpers.RunClassConstructor(linkedType.TypeHandle); - } - } -} diff --git a/Il2CppInterop.Runtime/Il2CppException.cs b/Il2CppInterop.Runtime/Il2CppException.cs deleted file mode 100644 index 3411ae98..00000000 --- a/Il2CppInterop.Runtime/Il2CppException.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Text; - -namespace Il2CppInterop.Runtime; - -public class Il2CppException : Exception -{ - [ThreadStatic] private static byte[] ourMessageBytes; - - public static Func ParseMessageHook; - - public Il2CppException(IntPtr exception) : base(BuildMessage(exception)) - { - } - - private static unsafe string BuildMessage(IntPtr exception) - { - if (ParseMessageHook != null) return ParseMessageHook(exception); - ourMessageBytes ??= new byte[65536]; - fixed (byte* message = ourMessageBytes) - { - IL2CPP.il2cpp_format_exception(exception, message, ourMessageBytes.Length); - } - - var builtMessage = Encoding.UTF8.GetString(ourMessageBytes, 0, Array.IndexOf(ourMessageBytes, (byte)0)); - Il2CppSystem.Exception il2cppException = new(exception); - return builtMessage + "\n" + - "--- BEGIN IL2CPP STACK TRACE ---\n" + - $"{il2cppException.ToString(false, true)}\n" + - "--- END IL2CPP STACK TRACE ---\n"; - } - - public static void RaiseExceptionIfNecessary(IntPtr returnedException) - { - if (returnedException == IntPtr.Zero) return; - throw new Il2CppException(returnedException); - } -} diff --git a/Il2CppInterop.Runtime/Il2CppInterop.Runtime.csproj b/Il2CppInterop.Runtime/Il2CppInterop.Runtime.csproj index 6fda3574..9defc930 100644 --- a/Il2CppInterop.Runtime/Il2CppInterop.Runtime.csproj +++ b/Il2CppInterop.Runtime/Il2CppInterop.Runtime.csproj @@ -1,45 +1,40 @@ - + Il2CppInterop.Runtime - knah, BepInEx et al. Runtime tools for bridging .NET and Il2Cpp together - net6.0 + net10.0 true - Il2CppInterop.Runtime - Debug;Release - AnyCPU false - Latest - enable + true - - - <_Parameter1>Il2CppInterop.HarmonySupport - - + + + $(NoWarn);0649 + - - false - Libs\Il2Cppmscorlib.dll - + + - + + + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + - - - + + + + + + + diff --git a/Il2CppInterop.Runtime/Il2CppType.cs b/Il2CppInterop.Runtime/Il2CppType.cs deleted file mode 100644 index 481b3a1b..00000000 --- a/Il2CppInterop.Runtime/Il2CppType.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Il2CppSystem; -using ArgumentException = System.ArgumentException; -using IntPtr = System.IntPtr; - -namespace Il2CppInterop.Runtime; - -public static class Il2CppType -{ - public static Type TypeFromPointer(IntPtr classPointer, string typeName = "") - { - return TypeFromPointerInternal(classPointer, typeName, true); - } - - private static Type? TypeFromPointerInternal(IntPtr classPointer, string typeName, bool throwOnFailure) - { - if (classPointer == IntPtr.Zero) - { - if (throwOnFailure) - throw new ArgumentException($"{typeName} does not have a corresponding IL2CPP class pointer"); - return null; - } - - var il2CppType = IL2CPP.il2cpp_class_get_type(classPointer); - if (il2CppType == IntPtr.Zero) - { - if (throwOnFailure) - throw new ArgumentException($"{typeName} does not have a corresponding IL2CPP type pointer"); - return null; - } - - return Type.internal_from_handle(il2CppType); - } - - public static Type From(System.Type type) - { - return From(type, true); - } - - public static Type From(System.Type type, bool throwOnFailure) - { - var pointer = Il2CppClassPointerStore.GetNativeClassPointer(type); - return TypeFromPointerInternal(pointer, type.Name, throwOnFailure); - } - - public static Type Of() - { - return Of(true); - } - - public static Type Of(bool throwOnFailure) - { - var classPointer = Il2CppClassPointerStore.NativeClassPtr; - return TypeFromPointerInternal(classPointer, typeof(T).Name, throwOnFailure); - } -} diff --git a/Il2CppInterop.Runtime/Il2CppTypePointerStore.cs b/Il2CppInterop.Runtime/Il2CppTypePointerStore.cs new file mode 100644 index 00000000..a953a9a7 --- /dev/null +++ b/Il2CppInterop.Runtime/Il2CppTypePointerStore.cs @@ -0,0 +1,58 @@ +using System.Diagnostics.CodeAnalysis; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime.Structs; + +namespace Il2CppInterop.Runtime; + +internal static class Il2CppTypePointerStore +{ + [RequiresDynamicCode("")] + public static nint GetNativeTypePointer(System.Type type) + { + if (type == typeof(void)) + return Il2CppTypePointerStore.NativeTypePointer; + + return (nint)typeof(Il2CppTypePointerStore<>) + .MakeGenericType(type) + .GetProperty(nameof(Il2CppTypePointerStore<>.NativeTypePointer))! + .GetValue(null)!; + } +} + +internal static unsafe class Il2CppTypePointerStore where T : IIl2CppType +{ + public static nint NativeTypePointer + { + [RequiresDynamicCode("")] + get + { + if (field == default) + { + var classPointer = Il2CppType.GetClassPointer(); + if (classPointer != default) + { + field = IL2CPP.il2cpp_class_get_type(classPointer); + } + else if (typeof(IByReference).IsAssignableFrom(typeof(T))) + { + // For ByReference, we can still get the type pointer even if T isn't an Il2Cpp type, as long as T is a valid ByReference element type + var elementType = typeof(T).GetGenericArguments()[0]; + var elementTypePointer = Il2CppTypePointerStore.GetNativeTypePointer(elementType); + if (elementTypePointer != default) + { + var elemType = UnityVersionHandler.Wrap((Il2CppTypeStruct*)elementTypePointer); + var refType = UnityVersionHandler.NewType(); + refType.Data = elemType.Data; + refType.Attrs = elemType.Attrs; + refType.Type = elemType.Type; + refType.ByRef = true; + refType.Pinned = elemType.Pinned; + field = refType.Pointer; + } + } + } + return field; + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/AssemblyInjector.cs b/Il2CppInterop.Runtime/Injection/AssemblyInjector.cs new file mode 100644 index 00000000..ca3ca591 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/AssemblyInjector.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Image; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime.Injection; + +internal static unsafe class AssemblyInjector +{ + private static readonly Dictionary images = []; + + internal static INativeImageStruct GetOrCreateImage(string name) + { + lock (images) + { + if (images.Count == 0) + { + var domain = IL2CPP.il2cpp_domain_get(); + if (domain == nint.Zero) + { + Logger.Instance.LogError("No il2cpp domain found; sad!"); + } + else + { + var assemblies = IL2CPP.il2cpp_domain_get_assemblies(domain, out var assembliesCount); + for (var i = 0; i < assembliesCount; i++) + { + var image = UnityVersionHandler.Wrap((Il2CppImage*)IL2CPP.il2cpp_assembly_get_image(assemblies[i])); + var imageName = Marshal.PtrToStringUTF8(image.Name)!; + images[imageName] = image; + } + } + } + if (!images.TryGetValue(name, out var result)) + { + var assembly = UnityVersionHandler.NewAssembly(); + assembly.Name.Name = Marshal.StringToCoTaskMemUTF8(name); + result = UnityVersionHandler.NewImage(); + result.Assembly = assembly.AssemblyPointer; + result.Dynamic = 1; + result.Name = assembly.Name.Name; + if (result.HasNameNoExt) + { + if (name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + { + result.NameNoExt = Marshal.StringToCoTaskMemUTF8(name.Substring(0, name.Length - 4)); + } + else + { + result.NameNoExt = assembly.Name.Name; + } + } + images[name] = result; + } + return result; + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/ClassInitializer.cs b/Il2CppInterop.Runtime/Injection/ClassInitializer.cs new file mode 100644 index 00000000..9e1cc652 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/ClassInitializer.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Injection.Hooks; +using Il2CppInterop.Runtime.Structs; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime.Injection; + +internal static unsafe class ClassInitializer +{ + private delegate void d_ClassInit(Il2CppClass* klass); + private static readonly d_ClassInit ClassInit = FindClassInit(); + + public static void Invoke(Il2CppClass* klass) + { + ClassInit.Invoke(klass); + } + + private static IEnumerable s_ClassInitSignatures => + [ + new SignatureDefinition + { + pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x28\x83", + mask = "x????xxxxx", + xref = true + }, + new SignatureDefinition + { + pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x48\x48", + mask = "x????xxxxx", + xref = true + } + ]; + + private static d_ClassInit FindClassInit() + { + static nint GetClassInitSubstitute() + { + if (Il2CppModule.TryGetExport("mono_class_instance_size", out var classInit)) + { + Logger.Instance.LogTrace("Picked mono_class_instance_size as a Class::Init substitute"); + return classInit; + } + if (Il2CppModule.TryGetExport("mono_class_setup_vtable", out classInit)) + { + Logger.Instance.LogTrace("Picked mono_class_setup_vtable as a Class::Init substitute"); + return classInit; + } + if (Il2CppModule.TryGetExport(nameof(IL2CPP.il2cpp_class_has_references), out classInit)) + { + Logger.Instance.LogTrace("Picked il2cpp_class_has_references as a Class::Init substitute"); + return classInit; + } + + Logger.Instance.LogTrace("GameAssembly.dll: 0x{Il2CppModuleAddress}", Il2CppModule.Module.BaseAddress.ToInt64().ToString("X2")); + throw new NotSupportedException("Failed to use signature for Class::Init and a substitute cannot be found, please create an issue and report your unity version & game"); + } + var pClassInit = s_ClassInitSignatures + .Select(s => SignatureDefinition.FindSignatureInModule(Il2CppModule.Module, s)) + .FirstOrDefault(p => p != 0); + + if (pClassInit == 0) + { + Logger.Instance.LogWarning("Class::Init signatures have been exhausted, using a substitute!"); + pClassInit = GetClassInitSubstitute(); + } + + Logger.Instance.LogTrace("Class::Init: 0x{PClassInitAddress}", pClassInit.ToString("X2")); + + return Marshal.GetDelegateForFunctionPointer(pClassInit); + } +} diff --git a/Il2CppInterop.Runtime/Injection/ClassInjector.Debug.cs b/Il2CppInterop.Runtime/Injection/ClassInjector.Debug.cs deleted file mode 100644 index 47f67966..00000000 --- a/Il2CppInterop.Runtime/Injection/ClassInjector.Debug.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Il2CppInterop.Common; -using Il2CppInterop.Runtime.Runtime; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Runtime.Injection; - -public unsafe partial class ClassInjector -{ - public static void Dump() - { - Dump((Il2CppClass*)Il2CppClassPointerStore.NativeClassPtr); - } - - private static string ToString(Il2CppClass* il2CppClass) - { - if (il2CppClass == default) return "null"; - var classStruct = UnityVersionHandler.Wrap(il2CppClass); - return $"{Marshal.PtrToStringUTF8(classStruct.Namespace)}.{Marshal.PtrToStringUTF8(classStruct.Name)}"; - } - - private static string ToString(Il2CppTypeStruct* il2CppType) - { - if (il2CppType == default) return "null"; - return IL2CPP.il2cpp_type_get_name_((IntPtr)il2CppType)!; - } - - public static void Dump(Il2CppClass* il2CppClass) - { - if (il2CppClass == default) throw new ArgumentNullException(nameof(il2CppClass)); - - InjectorHelpers.Setup(); - InjectorHelpers.ClassInit(il2CppClass); - - var classStruct = UnityVersionHandler.Wrap(il2CppClass); - - Logger.Instance.LogDebug("Dumping {Pointer:X}", classStruct.Pointer); - - Logger.Instance.LogDebug(" Namespace = {Namespace}", Marshal.PtrToStringUTF8(classStruct.Namespace)); - Logger.Instance.LogDebug(" Name = {Name}", Marshal.PtrToStringUTF8(classStruct.Name)); - - Logger.Instance.LogDebug(" Parent = {Parent}", ToString(classStruct.Parent)); - Logger.Instance.LogDebug(" InstanceSize = {InstanceSize}", classStruct.InstanceSize); - Logger.Instance.LogDebug(" NativeSize = {NativeSize}", classStruct.NativeSize); - Logger.Instance.LogDebug(" ActualSize = {ActualSize}", classStruct.ActualSize); - Logger.Instance.LogDebug(" Flags = {Flags}", classStruct.Flags); - Logger.Instance.LogDebug(" ValueType = {ValueType}", classStruct.ValueType); - Logger.Instance.LogDebug(" EnumType = {EnumType}", classStruct.EnumType); - Logger.Instance.LogDebug(" IsGeneric = {IsGeneric}", classStruct.IsGeneric); - Logger.Instance.LogDebug(" Initialized = {Initialized}", classStruct.Initialized); - Logger.Instance.LogDebug(" InitializedAndNoError = {InitializedAndNoError}", classStruct.InitializedAndNoError); - Logger.Instance.LogDebug(" SizeInited = {SizeInited}", classStruct.SizeInited); - Logger.Instance.LogDebug(" HasFinalize = {HasFinalize}", classStruct.HasFinalize); - Logger.Instance.LogDebug(" IsVtableInitialized = {IsVtableInitialized}", classStruct.IsVtableInitialized); - - var vtable = (VirtualInvokeData*)classStruct.VTable; - Logger.Instance.LogDebug(" VTable ({VtableCount}):", classStruct.VtableCount); - for (var i = 0; i < classStruct.VtableCount; i++) - { - var virtualInvokeData = vtable![i]; - var methodName = virtualInvokeData.method == default ? "" : Marshal.PtrToStringUTF8(UnityVersionHandler.Wrap(virtualInvokeData.method).Name); - - Logger.Instance.LogDebug(" [{I}] {MethodName} - {MethodPtr}", i, methodName, (virtualInvokeData.methodPtr == default ? "" : virtualInvokeData.methodPtr)); - } - - Logger.Instance.LogDebug(" Fields ({FieldCount}):", classStruct.FieldCount); - for (var i = 0; i < classStruct.FieldCount; i++) - { - var field = UnityVersionHandler.Wrap(classStruct.Fields + i * UnityVersionHandler.FieldInfoSize()); - - Logger.Instance.LogDebug($" [{i}] {ToString(field.Type)} {Marshal.PtrToStringUTF8(field.Name)} - {field.Offset}"); - } - - Logger.Instance.LogDebug(" Methods ({MethodCount}):", classStruct.MethodCount); - for (var i = 0; i < classStruct.MethodCount; i++) - { - var method = UnityVersionHandler.Wrap(classStruct.Methods[i]); - - Logger.Instance.LogDebug(" [{I}] {ReturnType} {Name}({ParametersCount}), {Flags}, {Slot}", i, ToString(method.ReturnType), Marshal.PtrToStringUTF8(method.Name), method.ParametersCount, method.Flags, method.Slot); - } - - Logger.Instance.LogDebug(" ImplementedInterfaces ({InterfaceCount}):", classStruct.InterfaceCount); - for (var i = 0; i < classStruct.InterfaceCount; i++) - { - var @interface = UnityVersionHandler.Wrap(classStruct.ImplementedInterfaces[i]); - - Logger.Instance.LogDebug(" [{I}] {Name}", i, Marshal.PtrToStringUTF8(@interface.Name)); - } - - Logger.Instance.LogDebug(" InterfaceOffsets ({InterfaceOffsetsCount}):", classStruct.InterfaceOffsetsCount); - for (var i = 0; i < classStruct.InterfaceOffsetsCount; i++) - { - var pair = classStruct.InterfaceOffsets[i]; - var @interface = UnityVersionHandler.Wrap(pair.interfaceType); - - Logger.Instance.LogDebug(" [{I}] {Offset} - {Name}", i, pair.offset, Marshal.PtrToStringUTF8(@interface.Name)); - } - - Logger.Instance.LogDebug(" TypeHierarchy ({TypeHierarchyDepth}):", classStruct.TypeHierarchyDepth); - for (var i = 0; i < classStruct.TypeHierarchyDepth; i++) - { - var @interface = UnityVersionHandler.Wrap(classStruct.TypeHierarchy[i]); - - Logger.Instance.LogDebug(" [{I}] {Name}", i, Marshal.PtrToStringUTF8(@interface.Name)); - } - } -} diff --git a/Il2CppInterop.Runtime/Injection/ClassInjector.cs b/Il2CppInterop.Runtime/Injection/ClassInjector.cs deleted file mode 100644 index b9fb6486..00000000 --- a/Il2CppInterop.Runtime/Injection/ClassInjector.cs +++ /dev/null @@ -1,1179 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using System.Text; -using Il2CppInterop.Common; -using Il2CppInterop.Runtime.Attributes; -using Il2CppInterop.Runtime.InteropTypes; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using Il2CppInterop.Runtime.InteropTypes.Fields; -using Il2CppInterop.Runtime.Runtime; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -using Microsoft.Extensions.Logging; -using ValueType = Il2CppSystem.ValueType; -using Void = Il2CppSystem.Void; - -namespace Il2CppInterop.Runtime.Injection; - -public unsafe class Il2CppInterfaceCollection : List -{ - public Il2CppInterfaceCollection(IEnumerable interfaces) : base(interfaces) - { - } - - public Il2CppInterfaceCollection(IEnumerable interfaces) : base(ResolveNativeInterfaces(interfaces)) - { - } - - private static IEnumerable ResolveNativeInterfaces(IEnumerable interfaces) - { - return interfaces.Select(it => - { - var classPointer = Il2CppClassPointerStore.GetNativeClassPointer(it); - if (classPointer == IntPtr.Zero) - throw new ArgumentException( - $"Type {it} doesn't have an IL2CPP class pointer, which means it's not an IL2CPP interface"); - return UnityVersionHandler.Wrap((Il2CppClass*)classPointer); - }); - } - - public static implicit operator Il2CppInterfaceCollection(INativeClassStruct[] interfaces) - { - return new(interfaces); - } - - public static implicit operator Il2CppInterfaceCollection(Type[] interfaces) - { - return new(interfaces); - } -} - -public class RegisterTypeOptions -{ - public static readonly RegisterTypeOptions Default = new(); - - public bool LogSuccess { get; init; } = true; - public Func? InterfacesResolver { get; init; } = null; - public Il2CppInterfaceCollection? Interfaces { get; init; } = null; -} - -public static unsafe partial class ClassInjector -{ - /// type.FullName - private static readonly HashSet InjectedTypes = new(); - - /// (method) : (method_inst, method) - internal static readonly Dictionary)> - InflatedMethodFromContextDictionary = new(); - - private static readonly ConcurrentDictionary InvokerCache = new(); - - private static readonly ConcurrentDictionary<(Type type, FieldAttributes attrs), IntPtr> - _injectedFieldTypes = new(); - - private static readonly VoidCtorDelegate FinalizeDelegate = Finalize; - - public static void ProcessNewObject(Il2CppObjectBase obj) - { - var pointer = obj.Pointer; - var handle = GCHandle.Alloc(obj, GCHandleType.Normal); - AssignGcHandle(pointer, handle); - } - - public static IntPtr DerivedConstructorPointer() - { - return IL2CPP.il2cpp_object_new(Il2CppClassPointerStore - .NativeClassPtr); // todo: consider calling base constructor - } - - public static void DerivedConstructorBody(Il2CppObjectBase objectBase) - { - if (objectBase.isWrapped) - return; - var fields = objectBase.GetType() - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) - .Where(IsFieldEligible) - .ToArray(); - foreach (var field in fields) - field.SetValue(objectBase, field.FieldType.GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new[] { typeof(Il2CppObjectBase), typeof(string) }, Array.Empty()) - .Invoke(new object[] { objectBase, field.Name }) - ); - var ownGcHandle = GCHandle.Alloc(objectBase, GCHandleType.Normal); - AssignGcHandle(objectBase.Pointer, ownGcHandle); - } - - public static void AssignGcHandle(IntPtr pointer, GCHandle gcHandle) - { - var handleAsPointer = GCHandle.ToIntPtr(gcHandle); - if (pointer == IntPtr.Zero) throw new NullReferenceException(nameof(pointer)); - ClassInjectorBase.GetInjectedData(pointer)->managedGcHandle = GCHandle.ToIntPtr(gcHandle); - } - - - public static bool IsTypeRegisteredInIl2Cpp() where T : class - { - return IsTypeRegisteredInIl2Cpp(typeof(T)); - } - - public static bool IsTypeRegisteredInIl2Cpp(Type type) - { - var currentPointer = Il2CppClassPointerStore.GetNativeClassPointer(type); - if (currentPointer != IntPtr.Zero) - return true; - if (IsManagedTypeInjected(type)) return true; - - return false; - } - - internal static bool IsManagedTypeInjected(Type type) - { - lock (InjectedTypes) - { - if (InjectedTypes.Contains(type.FullName)) - return true; - } - - return false; - } - - public static void RegisterTypeInIl2Cpp() where T : class - { - RegisterTypeInIl2Cpp(typeof(T)); - } - - public static void RegisterTypeInIl2Cpp(Type type) - { - RegisterTypeInIl2Cpp(type, RegisterTypeOptions.Default); - } - - public static void RegisterTypeInIl2Cpp(RegisterTypeOptions options) where T : class - { - RegisterTypeInIl2Cpp(typeof(T), options); - } - - public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) - { - var interfaces = options.Interfaces; - if (interfaces == null) - { - var interfacesAttribute = type.GetCustomAttribute(); - interfaces = interfacesAttribute?.Interfaces ?? - options.InterfacesResolver?.Invoke(type) ?? Array.Empty(); - } - - if (type == null) - throw new ArgumentException("Type argument cannot be null"); - - if (type.IsGenericType || type.IsGenericTypeDefinition) - throw new ArgumentException($"Type {type} is generic and can't be used in il2cpp"); - - var currentPointer = Il2CppClassPointerStore.GetNativeClassPointer(type); - if (currentPointer != IntPtr.Zero) - return; //already registered in il2cpp - - var baseType = type.BaseType; - if (baseType == null) - throw new ArgumentException($"Class {type} does not inherit from a class registered in il2cpp"); - - var baseClassPointer = - UnityVersionHandler.Wrap((Il2CppClass*)Il2CppClassPointerStore.GetNativeClassPointer(baseType)); - if (baseClassPointer == null) - { - RegisterTypeInIl2Cpp(baseType, new RegisterTypeOptions { LogSuccess = options.LogSuccess }); - baseClassPointer = - UnityVersionHandler.Wrap((Il2CppClass*)Il2CppClassPointerStore.GetNativeClassPointer(baseType)); - } - - InjectorHelpers.Setup(); - - // Initialize the vtable of all base types (Class::Init is recursive internally) - InjectorHelpers.ClassInit(baseClassPointer.ClassPointer); - - if (baseClassPointer.ValueType || baseClassPointer.EnumType) - throw new ArgumentException($"Base class {baseType} is value type and can't be inherited from"); - - if (baseClassPointer.IsGeneric) - throw new ArgumentException($"Base class {baseType} is generic and can't be inherited from"); - - if ((baseClassPointer.Flags & Il2CppClassAttributes.TYPE_ATTRIBUTE_SEALED) != 0) - throw new ArgumentException($"Base class {baseType} is sealed and can't be inherited from"); - - if ((baseClassPointer.Flags & Il2CppClassAttributes.TYPE_ATTRIBUTE_INTERFACE) != 0) - throw new ArgumentException($"Base class {baseType} is an interface and can't be inherited from"); - - if (interfaces.Any(i => (i.Flags & Il2CppClassAttributes.TYPE_ATTRIBUTE_INTERFACE) == 0)) - throw new ArgumentException($"Some of the interfaces in {interfaces} are not interfaces"); - - lock (InjectedTypes) - { - if (!InjectedTypes.Add(type.FullName)) - throw new ArgumentException( - $"Type with FullName {type.FullName} is already injected. Don't inject the same type twice, or use a different namespace"); - } - - var interfaceFunctionCount = interfaces.Sum(i => i.MethodCount); - var classPointer = UnityVersionHandler.NewClass(baseClassPointer.VtableCount + interfaceFunctionCount); - - classPointer.Image = InjectorHelpers.InjectedImage.ImagePointer; - classPointer.Parent = baseClassPointer.ClassPointer; - classPointer.ElementClass = classPointer.Class = classPointer.CastClass = classPointer.ClassPointer; - classPointer.NativeSize = -1; - classPointer.ActualSize = classPointer.InstanceSize = baseClassPointer.InstanceSize; - - classPointer.Initialized = true; - classPointer.InitializedAndNoError = true; - classPointer.SizeInited = true; - classPointer.HasFinalize = true; - classPointer.IsVtableInitialized = true; - - classPointer.Name = Marshal.StringToCoTaskMemUTF8(type.Name); - classPointer.Namespace = Marshal.StringToCoTaskMemUTF8(type.Namespace ?? string.Empty); - - classPointer.ThisArg.Type = classPointer.ByValArg.Type = Il2CppTypeEnum.IL2CPP_TYPE_CLASS; - classPointer.ThisArg.ByRef = true; - - classPointer.Flags = baseClassPointer.Flags; // todo: adjust flags? - - if (!type.IsAbstract) classPointer.Flags &= ~Il2CppClassAttributes.TYPE_ATTRIBUTE_ABSTRACT; - - var fieldsToInject = type - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) - .Where(IsFieldEligible) - .ToArray(); - classPointer.FieldCount = (ushort)fieldsToInject.Length; - - var il2cppFields = - (Il2CppFieldInfo*)Marshal.AllocHGlobal(classPointer.FieldCount * UnityVersionHandler.FieldInfoSize()); - var fieldOffset = (int)classPointer.InstanceSize; - for (var i = 0; i < classPointer.FieldCount; i++) - { - var fieldInfo = UnityVersionHandler.Wrap(il2cppFields + i * UnityVersionHandler.FieldInfoSize()); - fieldInfo.Name = Marshal.StringToCoTaskMemUTF8(fieldsToInject[i].Name); - fieldInfo.Parent = classPointer.ClassPointer; - fieldInfo.Offset = fieldOffset; - - var fieldType = fieldsToInject[i].FieldType == typeof(Il2CppStringField) - ? typeof(string) - : fieldsToInject[i].FieldType.GenericTypeArguments[0]; - var fieldAttributes = fieldsToInject[i].Attributes; - var fieldInfoClass = Il2CppClassPointerStore.GetNativeClassPointer(fieldType); - if (!_injectedFieldTypes.TryGetValue((fieldType, fieldAttributes), out var fieldTypePtr)) - { - var classType = - UnityVersionHandler.Wrap((Il2CppTypeStruct*)IL2CPP.il2cpp_class_get_type(fieldInfoClass)); - - var duplicatedType = UnityVersionHandler.NewType(); - duplicatedType.Data = classType.Data; - duplicatedType.Attrs = (ushort)fieldAttributes; - duplicatedType.Type = classType.Type; - duplicatedType.ByRef = classType.ByRef; - duplicatedType.Pinned = classType.Pinned; - - _injectedFieldTypes[(fieldType, fieldAttributes)] = duplicatedType.Pointer; - fieldTypePtr = duplicatedType.Pointer; - } - - fieldInfo.Type = (Il2CppTypeStruct*)fieldTypePtr; - if (fieldInfoClass == IntPtr.Zero) - throw new Exception($"Type {fieldType} in {type}.{fieldsToInject[i].Name} doesn't exist in Il2Cpp"); - - if (IL2CPP.il2cpp_class_is_valuetype(fieldInfoClass)) - { - uint _align = 0; - var fieldSize = IL2CPP.il2cpp_class_value_size(fieldInfoClass, ref _align); - fieldOffset += fieldSize; - } - else - { - fieldOffset += sizeof(Il2CppObject*); - } - } - - classPointer.Fields = il2cppFields; - - classPointer.InstanceSize = (uint)(fieldOffset + sizeof(InjectedClassData)); - classPointer.ActualSize = classPointer.InstanceSize; - - var eligibleMethods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly).Where(IsMethodEligible).ToArray(); - var methodsOffset = type.IsAbstract ? 1 : 2; // 1 is the finalizer, 1 is empty ctor - var methodCount = methodsOffset + eligibleMethods.Length; - - classPointer.MethodCount = (ushort)methodCount; - var methodPointerArray = (Il2CppMethodInfo**)Marshal.AllocHGlobal(methodCount * IntPtr.Size); - classPointer.Methods = methodPointerArray; - - methodPointerArray[0] = ConvertStaticMethod(FinalizeDelegate, "Finalize", classPointer); - var finalizeMethod = UnityVersionHandler.Wrap(methodPointerArray[0]); - var fieldsToInitialize = type - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Where(IsFieldEligible) - .ToArray(); - - if (!type.IsAbstract) methodPointerArray[1] = ConvertStaticMethod(CreateEmptyCtor(type, fieldsToInitialize), ".ctor", classPointer); - var infos = new Dictionary<(string, int, bool), int>(eligibleMethods.Length); - for (var i = 0; i < eligibleMethods.Length; i++) - { - var methodInfo = eligibleMethods[i]; - var methodInfoPointer = methodPointerArray[i + methodsOffset] = ConvertMethodInfo(methodInfo, classPointer); - if (methodInfo.IsGenericMethod && !methodInfo.IsAbstract) - InflatedMethodFromContextDictionary.Add((IntPtr)methodInfoPointer, (methodInfo, new Dictionary())); - infos[(methodInfo.Name, methodInfo.GetParameters().Length, methodInfo.IsGenericMethod)] = i + methodsOffset; - } - - var abstractMethods = eligibleMethods.Where(x => x.IsAbstract).ToArray(); - - var vTablePointer = (VirtualInvokeData*)classPointer.VTable; - var baseVTablePointer = (VirtualInvokeData*)baseClassPointer.VTable; - classPointer.VtableCount = (ushort)(baseClassPointer.VtableCount + interfaceFunctionCount + abstractMethods.Length); - - var extendsAbstract = baseClassPointer.Flags.HasFlag(Il2CppClassAttributes.TYPE_ATTRIBUTE_ABSTRACT); - var abstractBaseMethods = new List(); - - if (extendsAbstract) - { - static void FindAbstractMethods(List list, INativeClassStruct klass) - { - if (klass.Parent != default) FindAbstractMethods(list, UnityVersionHandler.Wrap(klass.Parent)); - - for (var i = 0; i < klass.MethodCount; i++) - { - var baseMethod = UnityVersionHandler.Wrap(klass.Methods[i]); - var name = Marshal.PtrToStringUTF8(baseMethod.Name)!; - - if (baseMethod.Flags.HasFlag(Il2CppMethodFlags.METHOD_ATTRIBUTE_ABSTRACT)) - { - list.Add(baseMethod); - } - else - { - var existing = list.SingleOrDefault(m => - { - if (Marshal.PtrToStringUTF8(m.Name) != name) return false; - if (m.ParametersCount != baseMethod.ParametersCount) return false; - if (GetIl2CppTypeFullName(m.ReturnType) != GetIl2CppTypeFullName(baseMethod.ReturnType)) return false; - - for (var i = 0; i < m.ParametersCount; i++) - { - var parameterInfo = UnityVersionHandler.Wrap(baseMethod.Parameters, i); - var otherParameterInfo = UnityVersionHandler.Wrap(m.Parameters, i); - - if (GetIl2CppTypeFullName(parameterInfo.ParameterType) != GetIl2CppTypeFullName(otherParameterInfo.ParameterType)) return false; - } - - return true; - }); - - if (existing != null) - { - list.Remove(existing); - } - } - } - } - - FindAbstractMethods(abstractBaseMethods, baseClassPointer); - } - - var abstractV = 0; - - INativeMethodInfoStruct HandleAbstractMethod(int position) - { - if (!extendsAbstract) throw new NullReferenceException("VTable method was null even though base type isn't abstract"); - - var nativeMethodInfoStruct = abstractBaseMethods[abstractV++]; - - vTablePointer[position].method = nativeMethodInfoStruct.MethodInfoPointer; - vTablePointer[position].methodPtr = nativeMethodInfoStruct.MethodPointer; - return nativeMethodInfoStruct; - } - - for (var i = 0; i < baseClassPointer.VtableCount; i++) - { - vTablePointer[i] = baseVTablePointer[i]; - - INativeMethodInfoStruct baseMethod; - - if (baseVTablePointer[i].method == default) - { - baseMethod = HandleAbstractMethod(i); - } - else - { - baseMethod = UnityVersionHandler.Wrap(vTablePointer[i].method); - } - - if (baseMethod.Name == IntPtr.Zero) - { - baseMethod = HandleAbstractMethod(i); - } - - var methodName = Marshal.PtrToStringUTF8(baseMethod.Name); - - if (methodName == "Finalize") // slot number is not static - { - vTablePointer[i].method = methodPointerArray[0]; - vTablePointer[i].methodPtr = finalizeMethod.MethodPointer; - continue; - } - - var parameters = new Type[baseMethod.ParametersCount]; - - for (var j = 0; j < baseMethod.ParametersCount; j++) - { - var parameterInfo = UnityVersionHandler.Wrap(baseMethod.Parameters, j); - var parameterType = SystemTypeFromIl2CppType(parameterInfo.ParameterType); - - parameters[j] = parameterType; - } - - var monoMethodImplementation = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, parameters); - - if (monoMethodImplementation != null && monoMethodImplementation.IsAbstract) - { - continue; - } - - var methodPointerArrayIndex = Array.IndexOf(eligibleMethods, monoMethodImplementation); - if (methodPointerArrayIndex >= 0) - { - var method = UnityVersionHandler.Wrap(methodPointerArray[methodPointerArrayIndex + methodsOffset]); - vTablePointer[i].method = methodPointerArray[methodPointerArrayIndex + methodsOffset]; - vTablePointer[i].methodPtr = method.MethodPointer; - } - - if (vTablePointer[i].method == default || vTablePointer[i].methodPtr == IntPtr.Zero) - { - throw new Exception("No method found for vtable entry " + methodName); - } - } - - var offsets = new int[interfaces.Count]; - - var index = baseClassPointer.VtableCount; - for (var i = 0; i < interfaces.Count; i++) - { - offsets[i] = index; - for (var j = 0; j < interfaces[i].MethodCount; j++) - { - var vTableMethod = UnityVersionHandler.Wrap(interfaces[i].Methods[j]); - var methodName = Marshal.PtrToStringUTF8(vTableMethod.Name); - if (!infos.TryGetValue((methodName, vTableMethod.ParametersCount, vTableMethod.IsGeneric), - out var methodIndex)) - { - ++index; - continue; - } - - var method = methodPointerArray[methodIndex]; - vTablePointer[index].method = method; - vTablePointer[index].methodPtr = UnityVersionHandler.Wrap(method).MethodPointer; - ++index; - } - } - - var interfaceCount = baseClassPointer.InterfaceCount + interfaces.Count; - classPointer.InterfaceCount = (ushort)interfaceCount; - classPointer.ImplementedInterfaces = (Il2CppClass**)Marshal.AllocHGlobal(interfaceCount * IntPtr.Size); - for (var i = 0; i < baseClassPointer.InterfaceCount; i++) - classPointer.ImplementedInterfaces[i] = baseClassPointer.ImplementedInterfaces[i]; - for (int i = baseClassPointer.InterfaceCount; i < interfaceCount; i++) - classPointer.ImplementedInterfaces[i] = interfaces[i - baseClassPointer.InterfaceCount].ClassPointer; - - var interfaceOffsetsCount = baseClassPointer.InterfaceOffsetsCount + interfaces.Count; - classPointer.InterfaceOffsetsCount = (ushort)interfaceOffsetsCount; - classPointer.InterfaceOffsets = - (Il2CppRuntimeInterfaceOffsetPair*)Marshal.AllocHGlobal(interfaceOffsetsCount * - Marshal - .SizeOf()); - for (var i = 0; i < baseClassPointer.InterfaceOffsetsCount; i++) - classPointer.InterfaceOffsets[i] = baseClassPointer.InterfaceOffsets[i]; - for (int i = baseClassPointer.InterfaceOffsetsCount; i < interfaceOffsetsCount; i++) - classPointer.InterfaceOffsets[i] = new Il2CppRuntimeInterfaceOffsetPair - { - interfaceType = interfaces[i - baseClassPointer.InterfaceOffsetsCount].ClassPointer, - offset = offsets[i - baseClassPointer.InterfaceOffsetsCount] - }; - - for (var i = 0; i < abstractMethods.Length; i++) - { - vTablePointer[index++] = default; - } - - var TypeHierarchyDepth = 1 + baseClassPointer.TypeHierarchyDepth; - classPointer.TypeHierarchyDepth = (byte)TypeHierarchyDepth; - classPointer.TypeHierarchy = (Il2CppClass**)Marshal.AllocHGlobal(TypeHierarchyDepth * IntPtr.Size); - for (var i = 0; i < TypeHierarchyDepth; i++) - classPointer.TypeHierarchy[i] = baseClassPointer.TypeHierarchy[i]; - classPointer.TypeHierarchy[TypeHierarchyDepth - 1] = classPointer.ClassPointer; - - classPointer.ByValArg.Data = - classPointer.ThisArg.Data = (IntPtr)InjectorHelpers.CreateClassToken(classPointer.Pointer); - - RuntimeSpecificsStore.SetClassInfo(classPointer.Pointer, true); - Il2CppClassPointerStore.SetNativeClassPointer(type, classPointer.Pointer); - - InjectorHelpers.AddTypeToLookup(type, classPointer.Pointer); - - if (options.LogSuccess) - Logger.Instance.LogInformation("Registered mono type {Type} in il2cpp domain", type); - } - - private static bool IsTypeSupported(Type type) - { - if (type.IsValueType || - type == typeof(string) || - type.IsGenericParameter) return true; - if (type.IsByRef) return IsTypeSupported(type.GetElementType()); - - return typeof(Il2CppObjectBase).IsAssignableFrom(type); - } - - private static bool IsFieldEligible(FieldInfo field) - { - if (!field.FieldType.IsGenericType) return field.FieldType == typeof(Il2CppStringField); - var genericTypeDef = field.FieldType.GetGenericTypeDefinition(); - if (genericTypeDef != typeof(Il2CppReferenceField<>) && genericTypeDef != typeof(Il2CppValueField<>)) - return false; - - return IsTypeSupported(field.FieldType.GenericTypeArguments[0]); - } - - private static bool IsMethodEligible(MethodInfo method) - { - if (method.Name == "Finalize") return false; - if (method.IsStatic) return false; - if (method.CustomAttributes.Any(it => typeof(HideFromIl2CppAttribute).IsAssignableFrom(it.AttributeType))) - return false; - - if (method.DeclaringType != null) - { - if (method.DeclaringType.GetProperties(BindingFlags.Instance | BindingFlags.Public | - BindingFlags.NonPublic | BindingFlags.DeclaredOnly) - .Where(property => property.GetAccessors(true).Contains(method)) - .Any(property => - property.CustomAttributes.Any(it => - typeof(HideFromIl2CppAttribute).IsAssignableFrom(it.AttributeType))) - ) - return false; - - foreach (var eventInfo in method.DeclaringType.GetEvents(BindingFlags.Instance | BindingFlags.Public | - BindingFlags.NonPublic | - BindingFlags.DeclaredOnly)) - if ((eventInfo.GetAddMethod(true) == method || eventInfo.GetRemoveMethod(true) == method) && - eventInfo.GetCustomAttribute() != null) - return false; - } - - if (!IsTypeSupported(method.ReturnType)) - { - Logger.Instance.LogWarning( - "Method {Method} on type {DeclaringType} has unsupported return type {ReturnType}", method.ToString(), method.DeclaringType, method.ReturnType); - return false; - } - - foreach (var parameter in method.GetParameters()) - { - var parameterType = parameter.ParameterType; - if (!IsTypeSupported(parameterType)) - { - Logger.Instance.LogWarning( - "Method {Method} on type {DeclaringType} has unsupported parameter {Parameter} of type {ParameterType}", method.ToString(), method.DeclaringType, parameter, parameterType); - return false; - } - } - - return true; - } - - private static Il2CppMethodInfo* ConvertStaticMethod(VoidCtorDelegate voidCtor, string methodName, - INativeClassStruct declaringClass) - { - var converted = UnityVersionHandler.NewMethod(); - converted.Name = Marshal.StringToCoTaskMemUTF8(methodName); - converted.Class = declaringClass.ClassPointer; - - Delegate invoker; - if (UnityVersionHandler.IsMetadataV29OrHigher) - { - invoker = new InvokerDelegateMetadataV29(StaticVoidIntPtrInvoker_MetadataV29); - } - else - { - invoker = new InvokerDelegate(StaticVoidIntPtrInvoker); - } - - GCHandle.Alloc(invoker); - converted.InvokerMethod = Marshal.GetFunctionPointerForDelegate(invoker); - - converted.MethodPointer = Marshal.GetFunctionPointerForDelegate(voidCtor); - converted.Slot = ushort.MaxValue; - converted.ReturnType = - (Il2CppTypeStruct*)IL2CPP.il2cpp_class_get_type(Il2CppClassPointerStore.NativeClassPtr); - - converted.Flags = Il2CppMethodFlags.METHOD_ATTRIBUTE_PUBLIC | - Il2CppMethodFlags.METHOD_ATTRIBUTE_HIDE_BY_SIG | - Il2CppMethodFlags.METHOD_ATTRIBUTE_SPECIAL_NAME | - Il2CppMethodFlags.METHOD_ATTRIBUTE_RT_SPECIAL_NAME; - - return converted.MethodInfoPointer; - } - - internal static Il2CppMethodInfo* ConvertMethodInfo(MethodInfo monoMethod, INativeClassStruct declaringClass) - { - var converted = UnityVersionHandler.NewMethod(); - converted.Name = Marshal.StringToCoTaskMemUTF8(monoMethod.Name); - converted.Class = declaringClass.ClassPointer; - - var parameters = monoMethod.GetParameters(); - if (parameters.Length > 0) - { - converted.ParametersCount = (byte)parameters.Length; - var paramsArray = UnityVersionHandler.NewMethodParameterArray(parameters.Length); - converted.Parameters = paramsArray[0]; - for (var i = 0; i < parameters.Length; i++) - { - var parameterInfo = parameters[i]; - var param = UnityVersionHandler.Wrap(paramsArray[i]); - if (UnityVersionHandler.ParameterInfoHasNamePosToken()) - { - param.Name = Marshal.StringToCoTaskMemUTF8(parameterInfo.Name); - param.Position = i; - param.Token = 0; - } - - var parameterType = parameterInfo.ParameterType; - if (!parameterType.IsGenericParameter) - { - if (parameterType.IsByRef) - { - var elementType = parameterType.GetElementType(); - if (!elementType.IsGenericParameter) - { - var elemType = UnityVersionHandler.Wrap( - (Il2CppTypeStruct*)IL2CPP.il2cpp_class_get_type( - Il2CppClassPointerStore.GetNativeClassPointer(elementType))); - var refType = UnityVersionHandler.NewType(); - refType.Data = elemType.Data; - refType.Attrs = elemType.Attrs; - refType.Type = elemType.Type; - refType.ByRef = true; - refType.Pinned = elemType.Pinned; - param.ParameterType = refType.TypePointer; - } - else - { - var type = UnityVersionHandler.NewType(); - type.Type = Il2CppTypeEnum.IL2CPP_TYPE_MVAR; - type.ByRef = true; - param.ParameterType = type.TypePointer; - } - } - else - { - param.ParameterType = - (Il2CppTypeStruct*)IL2CPP.il2cpp_class_get_type( - Il2CppClassPointerStore.GetNativeClassPointer(parameterType)); - } - } - else - { - var type = UnityVersionHandler.NewType(); - type.Type = Il2CppTypeEnum.IL2CPP_TYPE_MVAR; - param.ParameterType = type.TypePointer; - } - } - } - - if (monoMethod.IsGenericMethod) - { - if (monoMethod.ContainsGenericParameters) - converted.IsGeneric = true; - else - converted.IsInflated = true; - } - - if (!monoMethod.ContainsGenericParameters && !monoMethod.IsAbstract) - { - converted.InvokerMethod = Marshal.GetFunctionPointerForDelegate(GetOrCreateInvoker(monoMethod)); - converted.MethodPointer = Marshal.GetFunctionPointerForDelegate(GetOrCreateTrampoline(monoMethod)); - converted.VirtualMethodPointer = converted.MethodPointer; - } - - converted.Slot = ushort.MaxValue; - - if (!monoMethod.ReturnType.IsGenericParameter) - { - converted.ReturnType = - (Il2CppTypeStruct*)IL2CPP.il2cpp_class_get_type( - Il2CppClassPointerStore.GetNativeClassPointer(monoMethod.ReturnType)); - } - else - { - var type = UnityVersionHandler.NewType(); - type.Type = Il2CppTypeEnum.IL2CPP_TYPE_MVAR; - converted.ReturnType = type.TypePointer; - } - - converted.Flags = Il2CppMethodFlags.METHOD_ATTRIBUTE_PUBLIC | - Il2CppMethodFlags.METHOD_ATTRIBUTE_HIDE_BY_SIG; - - if (monoMethod.IsAbstract) - { - converted.Flags |= Il2CppMethodFlags.METHOD_ATTRIBUTE_ABSTRACT; - } - - return converted.MethodInfoPointer; - } - - private static VoidCtorDelegate CreateEmptyCtor(Type targetType, FieldInfo[] fieldsToInitialize) - { - var method = new DynamicMethod("FromIl2CppCtorDelegate", MethodAttributes.Public | MethodAttributes.Static, - CallingConventions.Standard, typeof(void), new[] { typeof(IntPtr) }, targetType, true); - - var body = method.GetILGenerator(); - - var monoCtor = targetType.GetConstructor(new[] { typeof(IntPtr) }); - if (monoCtor != null) - { - body.Emit(OpCodes.Ldarg_0); - body.Emit(OpCodes.Newobj, monoCtor); - } - else - { - var local = body.DeclareLocal(targetType); - body.Emit(OpCodes.Ldtoken, targetType); - body.Emit(OpCodes.Call, - typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle), BindingFlags.Public | BindingFlags.Static)!); - body.Emit(OpCodes.Call, - typeof(FormatterServices).GetMethod(nameof(FormatterServices.GetUninitializedObject), - BindingFlags.Public | BindingFlags.Static)!); - body.Emit(OpCodes.Stloc, local); - body.Emit(OpCodes.Ldloc, local); - body.Emit(OpCodes.Ldarg_0); - body.Emit(OpCodes.Call, - typeof(Il2CppObjectBase).GetMethod(nameof(Il2CppObjectBase.CreateGCHandle), - BindingFlags.NonPublic | BindingFlags.Instance)!); - body.Emit(OpCodes.Ldloc, local); - body.Emit(OpCodes.Ldc_I4_1); - body.Emit(OpCodes.Stfld, - typeof(Il2CppObjectBase).GetField(nameof(Il2CppObjectBase.isWrapped), - BindingFlags.NonPublic | BindingFlags.Instance)!); - body.Emit(OpCodes.Ldloc, local); - body.Emit(OpCodes.Call, - targetType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - Type.EmptyTypes, Array.Empty())!); - body.Emit(OpCodes.Ldloc, local); - } - - foreach (var field in fieldsToInitialize) - { - body.Emit(OpCodes.Dup); - body.Emit(OpCodes.Dup); - body.Emit(OpCodes.Ldstr, field.Name); - body.Emit(OpCodes.Newobj, field.FieldType.GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, - new[] { typeof(Il2CppObjectBase), typeof(string) }, Array.Empty()) - ); - body.Emit(OpCodes.Stfld, field); - } - - body.Emit(OpCodes.Call, typeof(ClassInjector).GetMethod(nameof(ProcessNewObject))!); - - body.Emit(OpCodes.Ret); - - var @delegate = (VoidCtorDelegate)method.CreateDelegate(typeof(VoidCtorDelegate)); - GCHandle.Alloc(@delegate); // pin it forever - return @delegate; - } - - public static void Finalize(IntPtr ptr) - { - var gcHandle = ClassInjectorBase.GetGcHandlePtrFromIl2CppObject(ptr); - GCHandle.FromIntPtr(gcHandle).Free(); - } - - private static Delegate GetOrCreateInvoker(MethodInfo monoMethod) - { - return InvokerCache.GetOrAdd(ExtractSignature(monoMethod), - static (_, monoMethodInner) => CreateInvoker(monoMethodInner), monoMethod); - } - - private static Delegate GetOrCreateTrampoline(MethodInfo monoMethod) - { - return CreateTrampoline(monoMethod); - } - - private static Delegate CreateInvoker(MethodInfo monoMethod) - { - DynamicMethod method; - if (UnityVersionHandler.IsMetadataV29OrHigher) - { - var parameterTypes = new[] { typeof(IntPtr), typeof(Il2CppMethodInfo*), typeof(IntPtr), typeof(IntPtr*), typeof(IntPtr*) }; - - method = new DynamicMethod("Invoker_" + ExtractSignature(monoMethod), - MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(void), - parameterTypes, monoMethod.DeclaringType, true); - } - else - { - var parameterTypes = new[] { typeof(IntPtr), typeof(Il2CppMethodInfo*), typeof(IntPtr), typeof(IntPtr*) }; - - method = new DynamicMethod("Invoker_" + ExtractSignature(monoMethod), - MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(IntPtr), - parameterTypes, monoMethod.DeclaringType, true); - } - - var body = method.GetILGenerator(); - - body.Emit(OpCodes.Ldarg_2); - for (var i = 0; i < monoMethod.GetParameters().Length; i++) - { - var parameterInfo = monoMethod.GetParameters()[i]; - body.Emit(OpCodes.Ldarg_3); - body.Emit(OpCodes.Ldc_I4, i * IntPtr.Size); - body.Emit(OpCodes.Add_Ovf_Un); - var nativeType = parameterInfo.ParameterType.NativeType(); - body.Emit(OpCodes.Ldobj, typeof(IntPtr)); - if (nativeType != typeof(IntPtr)) - body.Emit(OpCodes.Ldobj, nativeType); - } - - body.Emit(OpCodes.Ldarg_0); - body.EmitCalli(OpCodes.Calli, CallingConvention.Cdecl, monoMethod.ReturnType.NativeType(), - new[] { typeof(IntPtr) }.Concat(monoMethod.GetParameters().Select(it => it.ParameterType.NativeType())) - .ToArray()); - - if (UnityVersionHandler.IsMetadataV29OrHigher) - { - if (monoMethod.ReturnType != typeof(void)) - { - var returnValue = body.DeclareLocal(monoMethod.ReturnType.NativeType()); - body.Emit(OpCodes.Stloc, returnValue); - body.Emit(OpCodes.Ldarg_S, (byte)4); - body.Emit(OpCodes.Ldloc, returnValue); - body.Emit(OpCodes.Stobj, returnValue.LocalType); - } - } - else - { - if (monoMethod.ReturnType == typeof(void)) - { - body.Emit(OpCodes.Ldc_I4_0); - body.Emit(OpCodes.Conv_I); - } - else if (monoMethod.ReturnType.IsValueType) - { - var returnValue = body.DeclareLocal(monoMethod.ReturnType); - body.Emit(OpCodes.Stloc, returnValue); - var classField = typeof(Il2CppClassPointerStore<>).MakeGenericType(monoMethod.ReturnType) - .GetField(nameof(Il2CppClassPointerStore.NativeClassPtr)); - body.Emit(OpCodes.Ldsfld, classField); - body.Emit(OpCodes.Ldloca, returnValue); - body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.il2cpp_value_box))!); - } - } - - body.Emit(OpCodes.Ret); - - GCHandle.Alloc(method); - - var @delegate = method.CreateDelegate(GetInvokerDelegateType()); - GCHandle.Alloc(@delegate); - return @delegate; - } - - private static Type GetInvokerDelegateType() - { - if (UnityVersionHandler.IsMetadataV29OrHigher) - { - return typeof(InvokerDelegateMetadataV29); - } - - return typeof(InvokerDelegate); - } - - private static IntPtr StaticVoidIntPtrInvoker(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, - IntPtr* args) - { - Marshal.GetDelegateForFunctionPointer(methodPointer)(obj); - return IntPtr.Zero; - } - - private static void StaticVoidIntPtrInvoker_MetadataV29(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, - IntPtr* args, IntPtr* returnValue) - { - Marshal.GetDelegateForFunctionPointer(methodPointer)(obj); - } - - private static Delegate CreateTrampoline(MethodInfo monoMethod) - { - var nativeParameterTypes = new[] { typeof(IntPtr) }.Concat(monoMethod.GetParameters() - .Select(it => it.ParameterType.NativeType()).Concat(new[] { typeof(Il2CppMethodInfo*) })).ToArray(); - - var managedParameters = new[] { monoMethod.DeclaringType } - .Concat(monoMethod.GetParameters().Select(it => it.ParameterType)).ToArray(); - - var method = new DynamicMethod( - "Trampoline_" + ExtractSignature(monoMethod) + monoMethod.DeclaringType + monoMethod.Name, - MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, - monoMethod.ReturnType.NativeType(), nativeParameterTypes, - monoMethod.DeclaringType, true); - - var signature = new DelegateSupport.MethodSignature(monoMethod, true); - var delegateType = DelegateSupport.GetOrCreateDelegateType(signature, monoMethod); - - var body = method.GetILGenerator(); - - body.BeginExceptionBlock(); - - body.Emit(OpCodes.Ldarg_0); - body.Emit(OpCodes.Call, - typeof(ClassInjectorBase).GetMethod(nameof(ClassInjectorBase.GetMonoObjectFromIl2CppPointer))!); - body.Emit(OpCodes.Castclass, monoMethod.DeclaringType); - - var indirectVariables = new LocalBuilder[managedParameters.Length]; - - for (var i = 1; i < managedParameters.Length; i++) - { - var parameter = managedParameters[i]; - if (parameter.IsSubclassOf(typeof(ValueType))) - { - body.Emit(OpCodes.Ldc_I8, Il2CppClassPointerStore.GetNativeClassPointer(parameter).ToInt64()); - body.Emit(OpCodes.Conv_I); - body.Emit(Environment.Is64BitProcess ? OpCodes.Ldarg : OpCodes.Ldarga_S, i); - body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.il2cpp_value_box))); - } - else - { - body.Emit(OpCodes.Ldarg, i); - } - - if (parameter.IsValueType) continue; - - void HandleTypeConversion(Type type) - { - if (type == typeof(string)) - { - body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.Il2CppStringToManaged))!); - } - else if (type.IsSubclassOf(typeof(Il2CppObjectBase))) - { - var labelNull = body.DefineLabel(); - var labelNotNull = body.DefineLabel(); - body.Emit(OpCodes.Dup); - body.Emit(OpCodes.Brfalse, labelNull); - // We need to directly resolve from all constructors because on mono GetConstructor can cause the following issue: - // `Missing field layout info for ...` - // This is caused by GetConstructor calling RuntimeTypeHandle.CanCastTo which can fail since right now unhollower emits ALL fields which appear to now work properly - body.Emit(OpCodes.Newobj, type.GetConstructors().FirstOrDefault(ci => - { - var ps = ci.GetParameters(); - return ps.Length == 1 && ps[0].ParameterType == typeof(IntPtr); - })!); - body.Emit(OpCodes.Br, labelNotNull); - body.MarkLabel(labelNull); - body.Emit(OpCodes.Pop); - body.Emit(OpCodes.Ldnull); - body.MarkLabel(labelNotNull); - } - } - - if (parameter.IsByRef) - { - var elemType = parameter.GetElementType(); - - indirectVariables[i] = body.DeclareLocal(elemType); - - body.Emit(OpCodes.Ldind_I); - HandleTypeConversion(elemType); - body.Emit(OpCodes.Stloc, indirectVariables[i]); - body.Emit(OpCodes.Ldloca, indirectVariables[i]); - } - else - { - HandleTypeConversion(parameter); - } - } - - body.Emit(OpCodes.Call, monoMethod); - LocalBuilder managedReturnVariable = null; - if (monoMethod.ReturnType != typeof(void)) - { - managedReturnVariable = body.DeclareLocal(monoMethod.ReturnType); - body.Emit(OpCodes.Stloc, managedReturnVariable); - } - - for (var i = 1; i < managedParameters.Length; i++) - { - var variable = indirectVariables[i]; - if (variable == null) - continue; - body.Emit(OpCodes.Ldarg_S, i); - body.Emit(OpCodes.Ldloc, variable); - var directType = managedParameters[i].GetElementType(); - if (directType == typeof(string)) - body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.ManagedStringToIl2Cpp))!); - else if (!directType.IsValueType) - body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.Il2CppObjectBaseToPtr))!); - body.Emit(InjectorHelpers.StIndOpcodes.TryGetValue(directType, out var stindOpCodde) - ? stindOpCodde - : OpCodes.Stind_I); - } - // body.Emit(OpCodes.Ret); // breaks coreclr - - var exceptionLocal = body.DeclareLocal(typeof(Exception)); - body.BeginCatchBlock(typeof(Exception)); - body.Emit(OpCodes.Stloc, exceptionLocal); - body.Emit(OpCodes.Ldstr, "Exception in IL2CPP-to-Managed trampoline, not passing it to il2cpp: "); - body.Emit(OpCodes.Ldloc, exceptionLocal); - body.Emit(OpCodes.Callvirt, typeof(object).GetMethod(nameof(ToString))!); - body.Emit(OpCodes.Call, - typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) })!); - body.Emit(OpCodes.Call, typeof(ClassInjector).GetMethod(nameof(LogError), BindingFlags.Static | BindingFlags.NonPublic)!); - - body.EndExceptionBlock(); - - if (managedReturnVariable != null) - { - body.Emit(OpCodes.Ldloc, managedReturnVariable); - if (monoMethod.ReturnType == typeof(string)) - body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.ManagedStringToIl2Cpp))!); - else if (!monoMethod.ReturnType.IsValueType) - body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.Il2CppObjectBaseToPtr))!); - } - - body.Emit(OpCodes.Ret); - - var @delegate = method.CreateDelegate(delegateType); - GCHandle.Alloc(@delegate); // pin it forever - return @delegate; - } - - private static void LogError(string message) - { - Logger.Instance.LogError("{Message}", message); - } - - private static string ExtractSignature(MethodInfo monoMethod) - { - var builder = new StringBuilder(); - builder.Append(monoMethod.ReturnType.NativeType().Name); - builder.Append(monoMethod.IsStatic ? "" : "This"); - foreach (var parameterInfo in monoMethod.GetParameters()) - builder.Append(parameterInfo.ParameterType.NativeType().Name); - return builder.ToString(); - } - - private static Type RewriteType(Type type) - { - if (type.IsByRef) - return RewriteType(type.GetElementType()).MakeByRefType(); - - if (type.IsValueType && !type.IsEnum) - return type; - - if (type == typeof(string)) - return type; - - if (type.IsArray) - { - var elementType = type.GetElementType(); - if (elementType!.FullName == "System.String") return typeof(Il2CppStringArray); - - var convertedElementType = RewriteType(elementType); - if (elementType.IsGenericParameter) return typeof(Il2CppArrayBase<>).MakeGenericType(convertedElementType); - - return (convertedElementType.IsValueType ? typeof(Il2CppStructArray<>) : typeof(Il2CppReferenceArray<>)) - .MakeGenericType(convertedElementType); - } - - if (type.FullName!.StartsWith("System")) - { - var fullName = $"Il2Cpp{type.FullName}"; - var resolvedType = Type.GetType($"{fullName}, Il2Cpp{type.Assembly.GetName().Name}", false); - if (resolvedType != null) - return resolvedType; - - return AppDomain.CurrentDomain.GetAssemblies() - .Select(a => a.GetType(fullName, false)) - .First(t => t != null); - } - - return type; - } - - private static string GetIl2CppTypeFullName(Il2CppTypeStruct* typePointer) - { - var klass = UnityVersionHandler.Wrap((Il2CppClass*)IL2CPP.il2cpp_class_from_type((IntPtr)typePointer)); - var assembly = UnityVersionHandler.Wrap(UnityVersionHandler.Wrap(klass.Image).Assembly); - var fullName = new StringBuilder(); - var names = new Stack(); - var declaringType = klass; - var outerType = klass; - do - { - names.Push(Marshal.PtrToStringUTF8(declaringType.Name) ?? ""); - outerType = declaringType; - } - while ((declaringType = UnityVersionHandler.Wrap(declaringType.DeclaringType)) != default); - var namespaceName = outerType.Namespace != IntPtr.Zero ? Marshal.PtrToStringUTF8(outerType.Namespace) ?? "" : ""; - - fullName.Append(namespaceName); - if (namespaceName.Length > 0) - fullName.Append('.'); - fullName.Append(string.Join("+", names)); - - var assemblyName = Marshal.PtrToStringUTF8(assembly.Name.Name); - if (assemblyName != "mscorlib") - { - fullName.Append(", "); - fullName.Append(assemblyName); - } - - return fullName.ToString(); - } - - internal static Type SystemTypeFromIl2CppType(Il2CppTypeStruct* typePointer) - { - var fullName = GetIl2CppTypeFullName(typePointer); - var type = Type.GetType(fullName) - ?? Type.GetType(fullName.Contains('.') ? "Il2Cpp" + fullName : "Il2Cpp." + fullName) - ?? throw new NullReferenceException($"Couldn't find System.Type for Il2Cpp type: {fullName}"); - - INativeTypeStruct wrappedType = UnityVersionHandler.Wrap(typePointer); - if (wrappedType.Type == Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) - { - Il2CppGenericClass* genericClass = (Il2CppGenericClass*)wrappedType.Data; - uint argc = genericClass->context.class_inst->type_argc; - Il2CppTypeStruct** argv = genericClass->context.class_inst->type_argv; - Type[] genericArguments = new Type[argc]; - - for (int i = 0; i < argc; i++) - { - genericArguments[i] = SystemTypeFromIl2CppType(argv[i]); - } - type = type.MakeGenericType(genericArguments); - } - if (wrappedType.ByRef) - type = type.MakeByRefType(); - return RewriteType(type); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void InvokerDelegateMetadataV29(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, IntPtr* args, IntPtr* returnValue); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate IntPtr InvokerDelegate(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, IntPtr* args); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void VoidCtorDelegate(IntPtr objectPointer); -} diff --git a/Il2CppInterop.Runtime/Injection/DetourProvider.cs b/Il2CppInterop.Runtime/Injection/DetourProvider.cs deleted file mode 100644 index 24ce64ba..00000000 --- a/Il2CppInterop.Runtime/Injection/DetourProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Il2CppInterop.Runtime.Startup; - -namespace Il2CppInterop.Runtime.Injection; - -public interface IDetour : IDisposable -{ - nint Target { get; } - nint Detour { get; } - nint OriginalTrampoline { get; } - - void Apply(); - T GenerateTrampoline() where T : Delegate; -} - -public interface IDetourProvider -{ - IDetour Create(nint original, TDelegate target) where TDelegate : Delegate; -} - -internal static class Detour -{ - public static IDetour Apply(nint original, T target, out T trampoline) where T : Delegate - { - var detour = Il2CppInteropRuntime.Instance.DetourProvider.Create(original, target); - trampoline = detour.GenerateTrampoline(); - detour.Apply(); - return detour; - } -} diff --git a/Il2CppInterop.Runtime/Injection/EnumInjector.cs b/Il2CppInterop.Runtime/Injection/EnumInjector.cs deleted file mode 100644 index 4dd71e17..00000000 --- a/Il2CppInterop.Runtime/Injection/EnumInjector.cs +++ /dev/null @@ -1,298 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.InteropServices; -using Il2CppInterop.Common; -using Il2CppInterop.Runtime.Runtime; -using Il2CppSystem; -using Microsoft.Extensions.Logging; -using ArgumentException = System.ArgumentException; -using Convert = System.Convert; -using Enum = System.Enum; -using IntPtr = System.IntPtr; -using Type = System.Type; - -namespace Il2CppInterop.Runtime.Injection; - -public static unsafe class EnumInjector -{ - // fieldInfo : defaultValueBlob - private static readonly ConcurrentDictionary s_DefaultValueOverrides = new(); - - private static readonly IntPtr value__Cached = Marshal.StringToCoTaskMemUTF8("value__"); - - internal static bool GetDefaultValueOverride(Il2CppFieldInfo* fieldInfo, out IntPtr defaultValueBlob) - { - return s_DefaultValueOverrides.TryGetValue((IntPtr)fieldInfo, out defaultValueBlob); - } - - public static void InjectEnumValues(Dictionary valuesToAdd) where TEnum : Enum - { - InjectEnumValues(typeof(TEnum), valuesToAdd); - } - - public static void InjectEnumValues(Type type, Dictionary valuesToAdd) - { - if (type == null) - throw new ArgumentException("Type argument cannot be null"); - if (!type.IsEnum) - throw new ArgumentException("Type argument needs to be an enum"); - - var enumPtr = Il2CppClassPointerStore.GetNativeClassPointer(type); - if (enumPtr == IntPtr.Zero) - throw new ArgumentException("Type needs to be an Il2Cpp enum"); - - InjectorHelpers.Setup(); - - InjectorHelpers.ClassInit((Il2CppClass*)enumPtr); - - var il2cppEnum = UnityVersionHandler.Wrap((Il2CppClass*)enumPtr); - var newFieldCount = il2cppEnum.FieldCount + valuesToAdd.Count; - var newFields = (Il2CppFieldInfo*)Marshal.AllocHGlobal(newFieldCount * UnityVersionHandler.FieldInfoSize()); - - int fieldIdx; - for (fieldIdx = 0; fieldIdx < il2cppEnum.FieldCount; ++fieldIdx) - { - var offset = fieldIdx * UnityVersionHandler.FieldInfoSize(); - var oldField = UnityVersionHandler.Wrap(il2cppEnum.Fields + offset); - var newField = UnityVersionHandler.Wrap(newFields + offset); - - newField.Name = oldField.Name; - newField.Type = oldField.Type; - newField.Parent = oldField.Parent; - newField.Offset = oldField.Offset; - - // Move the default value blob from the old field to the new one - if (s_DefaultValueOverrides.TryRemove((IntPtr)oldField.FieldInfoPointer, out var blob)) - s_DefaultValueOverrides[(IntPtr)newField.FieldInfoPointer] = blob; - } - - var enumElementType = UnityVersionHandler.Wrap(il2cppEnum.ElementClass).ByValArg; - - foreach (var newData in valuesToAdd) - { - var offset = fieldIdx * UnityVersionHandler.FieldInfoSize(); - var newField = UnityVersionHandler.Wrap(newFields + offset); - newField.Name = Marshal.StringToCoTaskMemUTF8(newData.Key); - newField.Type = enumElementType.TypePointer; - newField.Parent = il2cppEnum.ClassPointer; - newField.Offset = 0; - - CreateOrUpdateFieldDefaultValue(newField.FieldInfoPointer, enumElementType.TypePointer, newData.Value); - - ++fieldIdx; - } - - il2cppEnum.FieldCount = (ushort)newFieldCount; - il2cppEnum.Fields = newFields; - - var runtimeEnumType = Il2CppType.TypeFromPointer(enumPtr).TryCast(); - if (runtimeEnumType != null) - // The mono runtime caches the enum names and values the first time they are requested, so we reset this cache - runtimeEnumType.GenericCache = null; - } - - private static int GetEnumElementSize(Il2CppTypeEnum type) - { - return type switch - { - Il2CppTypeEnum.IL2CPP_TYPE_I1 => sizeof(sbyte), - Il2CppTypeEnum.IL2CPP_TYPE_U1 => sizeof(byte), - - Il2CppTypeEnum.IL2CPP_TYPE_CHAR => sizeof(char), - - Il2CppTypeEnum.IL2CPP_TYPE_I2 => sizeof(short), - Il2CppTypeEnum.IL2CPP_TYPE_U2 => sizeof(ushort), - - Il2CppTypeEnum.IL2CPP_TYPE_I4 => sizeof(int), - Il2CppTypeEnum.IL2CPP_TYPE_U4 => sizeof(uint), - - Il2CppTypeEnum.IL2CPP_TYPE_I8 => sizeof(long), - Il2CppTypeEnum.IL2CPP_TYPE_U8 => sizeof(ulong), - - _ => throw new ArgumentException($"The type provided {type} is invalid") - }; - } - - private static IntPtr AllocateNewDefaultValueBlob(Il2CppTypeEnum type) - { - var size = GetEnumElementSize(type); - var blob = Marshal.AllocHGlobal(size); - Logger.Instance.LogTrace("Allocated default value blob at 0x{Blob} of {Size} for {Type}", blob.ToInt64().ToString("X2"), size, type); - return blob; - } - - private static IntPtr CreateOrUpdateFieldDefaultValue(Il2CppFieldInfo* field, Il2CppTypeStruct* type, object value) - { - var typeEnum = UnityVersionHandler.Wrap(type).Type; - - if (!GetDefaultValueOverride(field, out var newBlob)) - { - newBlob = AllocateNewDefaultValueBlob(typeEnum); - s_DefaultValueOverrides[(IntPtr)field] = newBlob; - } - - SetFieldDefaultValue(newBlob, typeEnum, value); - return newBlob; - } - - private static void SetFieldDefaultValue(IntPtr blob, Il2CppTypeEnum type, object value) - { - var valueData = Convert.ToInt64(value); - switch (type) - { - case Il2CppTypeEnum.IL2CPP_TYPE_I1: - *(sbyte*)blob = (sbyte)valueData; - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U1: - *(byte*)blob = (byte)valueData; - break; - - case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: - *(char*)blob = (char)valueData; - break; - - case Il2CppTypeEnum.IL2CPP_TYPE_I2: - *(short*)blob = (short)valueData; - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U2: - *(ushort*)blob = (ushort)valueData; - break; - - case Il2CppTypeEnum.IL2CPP_TYPE_I4: - *(int*)blob = (int)valueData; - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U4: - *(uint*)blob = (uint)valueData; - break; - - case Il2CppTypeEnum.IL2CPP_TYPE_I8: - *(long*)blob = valueData; - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U8: - *(ulong*)blob = (ulong)valueData; - break; - - default: throw new ArgumentException($"The type provided {type} is invalid"); - } - } - - public static void RegisterEnumInIl2Cpp(bool logSuccess = true) where TEnum : Enum - { - RegisterEnumInIl2Cpp(typeof(TEnum), logSuccess); - } - - public static void RegisterEnumInIl2Cpp(Type type, bool logSuccess = true) - { - if (type == null) - throw new ArgumentException("Type argument cannot be null"); - - if (!type.IsEnum) - throw new ArgumentException("Type argument needs to be an enum"); - - var enumPtr = Il2CppClassPointerStore.GetNativeClassPointer(type); - if (enumPtr != IntPtr.Zero) - return; - - InjectorHelpers.Setup(); - - var baseEnum = - UnityVersionHandler.Wrap((Il2CppClass*)Il2CppClassPointerStore.NativeClassPtr); - - InjectorHelpers.ClassInit(baseEnum.ClassPointer); - - var il2cppEnum = UnityVersionHandler.NewClass(baseEnum.VtableCount); - var elementClass = - UnityVersionHandler.Wrap( - (Il2CppClass*)Il2CppClassPointerStore.GetNativeClassPointer(Enum.GetUnderlyingType(type))); - - il2cppEnum.Image = InjectorHelpers.InjectedImage.ImagePointer; - il2cppEnum.Class = il2cppEnum.CastClass = il2cppEnum.ElementClass = elementClass.ClassPointer; - il2cppEnum.Parent = baseEnum.ClassPointer; - il2cppEnum.ActualSize = il2cppEnum.InstanceSize = - (uint)(baseEnum.InstanceSize + GetEnumElementSize(elementClass.ByValArg.Type)); - il2cppEnum.NativeSize = -1; - - il2cppEnum.ValueType = true; - il2cppEnum.EnumType = true; - il2cppEnum.Initialized = true; - il2cppEnum.InitializedAndNoError = true; - il2cppEnum.SizeInited = true; - il2cppEnum.HasFinalize = true; - il2cppEnum.IsVtableInitialized = true; - - il2cppEnum.Name = Marshal.StringToCoTaskMemUTF8(type.Name); - il2cppEnum.Namespace = Marshal.StringToCoTaskMemUTF8(type.Namespace ?? string.Empty); - - var token = InjectorHelpers.CreateClassToken(il2cppEnum.Pointer); - il2cppEnum.ThisArg.Data = il2cppEnum.ByValArg.Data = (IntPtr)token; - - // Has to be IL2CPP_TYPE_VALUETYPE because IL2CPP_TYPE_ENUM isn't used - il2cppEnum.ThisArg.Type = il2cppEnum.ByValArg.Type = Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE; - - il2cppEnum.Flags = (Il2CppClassAttributes)type.Attributes; - - il2cppEnum.VtableCount = baseEnum.VtableCount; - var vtable = (VirtualInvokeData*)il2cppEnum.VTable; - var baseVTable = (VirtualInvokeData*)baseEnum.VTable; - for (var i = 0; i < baseEnum.VtableCount; i++) - vtable[i] = baseVTable[i]; - - var enumValues = Enum.GetValues(type); - var enumNames = Enum.GetNames(type); - il2cppEnum.FieldCount = (ushort)(enumValues.Length + 1); // value__ - - var il2cppFields = - (Il2CppFieldInfo*)Marshal.AllocHGlobal(il2cppEnum.FieldCount * UnityVersionHandler.FieldInfoSize()); - var valueField = UnityVersionHandler.Wrap(il2cppFields); - valueField.Name = value__Cached; - valueField.Parent = il2cppEnum.ClassPointer; - valueField.Offset = (int)baseEnum.InstanceSize; - - var enumValueType = UnityVersionHandler.NewType(); - enumValueType.Data = elementClass.ThisArg.Data; - enumValueType.Attrs = (ushort)(FieldAttributes.Private | FieldAttributes.Family | FieldAttributes.SpecialName | - FieldAttributes.RTSpecialName); - enumValueType.Type = elementClass.ThisArg.Type; - enumValueType.ByRef = elementClass.ThisArg.ByRef; - enumValueType.Pinned = elementClass.ThisArg.Pinned; - - valueField.Type = enumValueType.TypePointer; - - var enumConstType = UnityVersionHandler.NewType(); - enumConstType.Data = il2cppEnum.ThisArg.Data; - enumConstType.Attrs = (ushort)(FieldAttributes.Private | FieldAttributes.Family | FieldAttributes.InitOnly | - FieldAttributes.Literal | FieldAttributes.HasDefault); - enumConstType.Type = Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE; - enumConstType.ByRef = false; - enumConstType.Pinned = false; - - for (var i = 1; i < il2cppEnum.FieldCount; i++) - { - var fieldValue = enumValues.GetValue(i - 1); - var il2cppField = UnityVersionHandler.Wrap(il2cppFields + i * UnityVersionHandler.FieldInfoSize()); - il2cppField.Name = Marshal.StringToCoTaskMemUTF8(enumNames[i - 1]); - il2cppField.Type = enumConstType.TypePointer; - il2cppField.Parent = il2cppEnum.ClassPointer; - il2cppField.Offset = 0; - - CreateOrUpdateFieldDefaultValue(il2cppField.FieldInfoPointer, elementClass.ThisArg.TypePointer, fieldValue); - } - - il2cppEnum.Fields = il2cppFields; - - il2cppEnum.TypeHierarchyDepth = (byte)(1 + baseEnum.TypeHierarchyDepth); - il2cppEnum.TypeHierarchy = (Il2CppClass**)Marshal.AllocHGlobal(il2cppEnum.TypeHierarchyDepth * sizeof(void*)); - for (var i = 0; i < il2cppEnum.TypeHierarchyDepth; i++) - il2cppEnum.TypeHierarchy[i] = baseEnum.TypeHierarchy[i]; - il2cppEnum.TypeHierarchy[il2cppEnum.TypeHierarchyDepth - 1] = il2cppEnum.ClassPointer; - - RuntimeSpecificsStore.SetClassInfo(il2cppEnum.Pointer, true); - Il2CppClassPointerStore.SetNativeClassPointer(type, il2cppEnum.Pointer); - - InjectorHelpers.AddTypeToLookup(type, il2cppEnum.Pointer); - - if (logSuccess) - Logger.Instance.LogInformation("Registered managed enum {Type} in il2cpp domain", type); - } -} diff --git a/Il2CppInterop.Runtime/Injection/GenericTypeInflater.cs b/Il2CppInterop.Runtime/Injection/GenericTypeInflater.cs new file mode 100644 index 00000000..a44e5cb5 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/GenericTypeInflater.cs @@ -0,0 +1,72 @@ +using System; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Class; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; + +namespace Il2CppInterop.Runtime.Injection; + +internal static unsafe class GenericTypeInflater +{ + internal static nint InflateGenericType(nint genericClassPointer, nint[] genericArgClassPtrs) + { + return InflateGenericType(UnityVersionHandler.Wrap((Il2CppClass*)genericClassPointer), genericArgClassPtrs); + } + + private static nint InflateGenericType(INativeClassStruct genericClassPointer, nint[] genericArgClassPtrs) + { + // This is an extremely naive implementation of generic type inflation and likely breaks the moment someone touches an inflated type. + + var inflatedClassPointer = UnityVersionHandler.NewClass(genericClassPointer.VTableCount); + + CopyFrom(inflatedClassPointer.ByValArg, genericClassPointer.ByValArg); + CopyFrom(inflatedClassPointer.ThisArg, genericClassPointer.ThisArg); + + inflatedClassPointer.InstanceSize = genericClassPointer.InstanceSize; + inflatedClassPointer.VTableCount = genericClassPointer.VTableCount; + inflatedClassPointer.InterfaceCount = genericClassPointer.InterfaceCount; + inflatedClassPointer.InterfaceOffsetsCount = genericClassPointer.InterfaceOffsetsCount; + inflatedClassPointer.TypeHierarchyDepth = genericClassPointer.TypeHierarchyDepth; + inflatedClassPointer.NativeSize = genericClassPointer.NativeSize; + inflatedClassPointer.ActualSize = genericClassPointer.ActualSize; + inflatedClassPointer.MethodCount = genericClassPointer.MethodCount; + inflatedClassPointer.FieldCount = genericClassPointer.FieldCount; + inflatedClassPointer.Flags = genericClassPointer.Flags; + inflatedClassPointer.Name = genericClassPointer.Name; + inflatedClassPointer.Namespace = genericClassPointer.Namespace; + inflatedClassPointer.Image = genericClassPointer.Image; + inflatedClassPointer.Parent = genericClassPointer.Parent; + inflatedClassPointer.ElementClass = genericClassPointer.ElementClass; + inflatedClassPointer.CastClass = genericClassPointer.CastClass; + inflatedClassPointer.DeclaringType = genericClassPointer.DeclaringType; + inflatedClassPointer.Class = inflatedClassPointer.Class; + inflatedClassPointer.Fields = genericClassPointer.Fields; + inflatedClassPointer.Methods = genericClassPointer.Methods; + inflatedClassPointer.ImplementedInterfaces = genericClassPointer.ImplementedInterfaces; + inflatedClassPointer.InterfaceOffsets = genericClassPointer.InterfaceOffsets; + inflatedClassPointer.TypeHierarchy = genericClassPointer.TypeHierarchy; + inflatedClassPointer.ValueType = genericClassPointer.ValueType; + inflatedClassPointer.Initialized = true; + inflatedClassPointer.EnumType = genericClassPointer.EnumType; + inflatedClassPointer.IsGeneric = true; + inflatedClassPointer.HasReferences = genericClassPointer.HasReferences; + inflatedClassPointer.SizeInited = true; + inflatedClassPointer.HasFinalize = genericClassPointer.HasFinalize; + inflatedClassPointer.IsVtableInitialized = true; + inflatedClassPointer.InitializedAndNoError = true; + + new ReadOnlySpan(genericClassPointer.VTable, genericClassPointer.VTableCount) + .CopyTo(new Span(inflatedClassPointer.VTable, inflatedClassPointer.VTableCount)); + + return inflatedClassPointer.Pointer; + } + + private static void CopyFrom(INativeTypeStruct destination, INativeTypeStruct source) + { + destination.Data = source.Data; + destination.Attrs = source.Attrs; + destination.Type = source.Type; + destination.ByRef = source.ByRef; + destination.Pinned = source.Pinned; + destination.ValueType = source.ValueType; + } +} diff --git a/Il2CppInterop.Runtime/Injection/Hook.cs b/Il2CppInterop.Runtime/Injection/Hook.cs index 24bb7bd1..324facac 100644 --- a/Il2CppInterop.Runtime/Injection/Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hook.cs @@ -1,46 +1,78 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Injection.Hooks; +using Il2CppInterop.Runtime.Startup; using Microsoft.Extensions.Logging; -namespace Il2CppInterop.Runtime.Injection +namespace Il2CppInterop.Runtime.Injection; + +internal static class Hook { - internal abstract class Hook where T : Delegate + private static readonly MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook GetTypeInfoFromTypeDefinitionIndexHook = new(); + private static readonly Class_GetFieldDefaultValue_Hook GetFieldDefaultValueHook = new(); + private static readonly Class_FromIl2CppType_Hook FromIl2CppTypeHook = new(); + private static readonly Class_FromName_Hook FromNameHook = new(); + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + internal static void ApplyInjectionHooks() { - private bool _isApplied; - private T _detour; - private T _method; - private T _original; + GetTypeInfoFromTypeDefinitionIndexHook.ApplyHook(); + GetFieldDefaultValueHook.ApplyHook(); + FromIl2CppTypeHook.ApplyHook(); + FromNameHook.ApplyHook(); + } - public T Original => _original; + public static IDisposable ApplyDetour(nint original, T target, out T trampoline) where T : Delegate + { + return Il2CppInteropRuntime.Instance.DetourProvider.Create(original, target, out trampoline); + } +} +internal abstract class Hook where T : Delegate +{ +#nullable disable + private bool _isApplied; + private T _detour; + private T _method; + private T _original; +#nullable restore - public abstract string TargetMethodName { get; } - public abstract T GetDetour(); - public abstract IntPtr FindTargetMethod(); + public T Original => _original; - public virtual void TargetMethodNotFound() - { - throw new Exception($"Required target method {TargetMethodName} not found"); - } + public abstract string TargetMethodName { get; } - public void ApplyHook() - { - if (_isApplied) return; + public abstract T GetDetour(); + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public abstract IntPtr FindTargetMethod(); - var methodPtr = FindTargetMethod(); + public virtual void TargetMethodNotFound() + { + throw new Exception($"Required target method {TargetMethodName} not found"); + } - if (methodPtr == IntPtr.Zero) - { - TargetMethodNotFound(); - return; - } + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public void ApplyHook() + { + if (_isApplied) return; - Logger.Instance.LogTrace("{MethodName} found: 0x{MethodPtr}", TargetMethodName, methodPtr.ToInt64().ToString("X2")); + var methodPtr = FindTargetMethod(); - _detour = GetDetour(); - Detour.Apply(methodPtr, _detour, out _original); - _method = Marshal.GetDelegateForFunctionPointer(methodPtr); - _isApplied = true; + if (methodPtr == IntPtr.Zero) + { + TargetMethodNotFound(); + return; } + + Logger.Instance.LogTrace("{MethodName} found: 0x{MethodPtr}", TargetMethodName, methodPtr.ToInt64().ToString("X2")); + + _detour = GetDetour(); + Hook.ApplyDetour(methodPtr, _detour, out _original); + _method = Marshal.GetDelegateForFunctionPointer(methodPtr); + _isApplied = true; } } diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Class_FromIl2CppType_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Class_FromIl2CppType_Hook.cs index bac0354c..382e4f01 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Class_FromIl2CppType_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Class_FromIl2CppType_Hook.cs @@ -1,47 +1,51 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using Il2CppInterop.Common; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.Runtime; +using Il2CppInterop.Runtime.Structs; using Microsoft.Extensions.Logging; -namespace Il2CppInterop.Runtime.Injection.Hooks +namespace Il2CppInterop.Runtime.Injection.Hooks; + +internal unsafe class Class_FromIl2CppType_Hook : Hook { - internal unsafe class Class_FromIl2CppType_Hook : Hook + public override string TargetMethodName => "Class::FromIl2CppType"; + public override MethodDelegate GetDetour() => Hook; + + /// Common version of the Il2CppType, the only thing that changed between unity version are the bitfields values that we don't use + internal readonly struct Il2CppType { - public override string TargetMethodName => "Class::FromIl2CppType"; - public override MethodDelegate GetDetour() => Hook; + public readonly void* data; + public readonly ushort attrs; + public readonly Il2CppTypeEnum type; + private readonly byte _bitfield; + } - /// Common version of the Il2CppType, the only thing that changed between unity version are the bitfields values that we don't use - internal readonly struct Il2CppType - { - public readonly void* data; - public readonly ushort attrs; - public readonly Il2CppTypeEnum type; - private readonly byte _bitfield; - } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate Il2CppClass* MethodDelegate(Il2CppType* type, bool throwOnError); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate Il2CppClass* MethodDelegate(Il2CppType* type, bool throwOnError); + private Il2CppClass* Hook(Il2CppType* type, bool throwOnError) + { + if (type == null) + return Original(type, throwOnError); - private Il2CppClass* Hook(Il2CppType* type, bool throwOnError) + if ((nint)type->data < 0 && (type->type is Il2CppTypeEnum.IL2CPP_TYPE_CLASS or Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE)) { - if ((nint)type->data < 0 && (type->type == Il2CppTypeEnum.IL2CPP_TYPE_CLASS || type->type == Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE)) - { - InjectorHelpers.s_InjectedClasses.TryGetValue((nint)type->data, out var classPointer); - return (Il2CppClass*)classPointer; - } - - return Original(type, throwOnError); + TokenAllocator.TryGetClassPointer((nint)type->data, out var classPointer); + return (Il2CppClass*)classPointer; } - public override IntPtr FindTargetMethod() - { - var classFromTypeAPI = InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_class_from_il2cpp_type)); - Logger.Instance.LogTrace("il2cpp_class_from_il2cpp_type: 0x{ClassFromTypeApiAddress}", classFromTypeAPI.ToInt64().ToString("X2")); + return Original(type, throwOnError); + } - return XrefScannerLowLevel.JumpTargets(classFromTypeAPI).Single(); - } + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public override IntPtr FindTargetMethod() + { + var classFromTypeAPI = Il2CppModule.GetExport(nameof(IL2CPP.il2cpp_class_from_il2cpp_type)); + Logger.Instance.LogTrace("il2cpp_class_from_il2cpp_type: 0x{ClassFromTypeApiAddress}", classFromTypeAPI.ToInt64().ToString("X2")); + + return XrefScanner.JumpTargets(classFromTypeAPI).Single(); } } diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Class_FromName_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Class_FromName_Hook.cs index 0c09a488..87d1ee8e 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Class_FromName_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Class_FromName_Hook.cs @@ -1,42 +1,52 @@ using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using Il2CppInterop.Common; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.Runtime; +using Il2CppInterop.Runtime.Structs; using Microsoft.Extensions.Logging; -namespace Il2CppInterop.Runtime.Injection.Hooks +namespace Il2CppInterop.Runtime.Injection.Hooks; + +internal unsafe class Class_FromName_Hook : Hook { - internal unsafe class Class_FromName_Hook : Hook - { - public override string TargetMethodName => "Class::FromName"; - public override MethodDelegate GetDetour() => Hook; + private static readonly Dictionary<(IntPtr ImagePointer, string Namespace, string Class), IntPtr> s_ClassNameLookup = new(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate Il2CppClass* MethodDelegate(Il2CppImage* image, IntPtr _namespace, IntPtr name); + public override string TargetMethodName => "Class::FromName"; + public override MethodDelegate GetDetour() => Hook; - private Il2CppClass* Hook(Il2CppImage* image, IntPtr _namespace, IntPtr name) - { - Il2CppClass* classPtr = Original(image, _namespace, name); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate Il2CppClass* MethodDelegate(Il2CppImage* image, IntPtr _namespace, IntPtr name); - if (classPtr == null) - { - string namespaze = Marshal.PtrToStringUTF8(_namespace); - string className = Marshal.PtrToStringUTF8(name); - InjectorHelpers.s_ClassNameLookup.TryGetValue((namespaze, className, (IntPtr)image), out IntPtr injectedClass); - classPtr = (Il2CppClass*)injectedClass; - } + private Il2CppClass* Hook(Il2CppImage* image, IntPtr _namespace, IntPtr name) + { + Il2CppClass* classPtr = Original(image, _namespace, name); - return classPtr; + if (classPtr == null) + { + var namespaze = Marshal.PtrToStringUTF8(_namespace) ?? ""; + var className = Marshal.PtrToStringUTF8(name) ?? ""; + s_ClassNameLookup.TryGetValue(((IntPtr)image, namespaze, className), out IntPtr injectedClass); + classPtr = (Il2CppClass*)injectedClass; } - public override IntPtr FindTargetMethod() - { - var classFromNameAPI = InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_class_from_name)); - Logger.Instance.LogTrace("il2cpp_class_from_name: 0x{ClassFromNameApiAddress}", classFromNameAPI.ToInt64().ToString("X2")); + return classPtr; + } - return XrefScannerLowLevel.JumpTargets(classFromNameAPI).Single(); - } + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public override IntPtr FindTargetMethod() + { + var classFromNameAPI = Il2CppModule.GetExport(nameof(IL2CPP.il2cpp_class_from_name)); + Logger.Instance.LogTrace("il2cpp_class_from_name: 0x{ClassFromNameApiAddress}", classFromNameAPI.ToInt64().ToString("X2")); + + return XrefScanner.JumpTargets(classFromNameAPI).Single(); + } + + internal static void AddTypeToLookup(string assemblyName, string namespaze, string klass, IntPtr typePointer) + { + var image = AssemblyInjector.GetOrCreateImage(assemblyName).ImagePointer; + s_ClassNameLookup.Add(((IntPtr)image, namespaze, klass), typePointer); } } diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Class_GetFieldDefaultValue_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Class_GetFieldDefaultValue_Hook.cs index a0a42582..c35e9b17 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Class_GetFieldDefaultValue_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Class_GetFieldDefaultValue_Hook.cs @@ -1,155 +1,160 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using Il2CppInterop.Common; -using Il2CppInterop.Common.Extensions; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.Runtime; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.FieldInfo; +using Il2CppInterop.Runtime.Extensions; using Il2CppInterop.Runtime.Startup; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Class; +using Il2CppInterop.Runtime.Structs.VersionSpecific.FieldInfo; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; using Microsoft.Extensions.Logging; -namespace Il2CppInterop.Runtime.Injection.Hooks +namespace Il2CppInterop.Runtime.Injection.Hooks; + +internal unsafe class Class_GetFieldDefaultValue_Hook : Hook { - internal unsafe class Class_GetFieldDefaultValue_Hook : Hook - { - public override string TargetMethodName => "Class::GetDefaultFieldValue"; - public override MethodDelegate GetDetour() => Hook; + public override string TargetMethodName => "Class::GetDefaultFieldValue"; + public override MethodDelegate GetDetour() => Hook; - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate byte* MethodDelegate(Il2CppFieldInfo* field, out Il2CppTypeStruct* type); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate byte* MethodDelegate(Il2CppFieldInfo* field, out Il2CppTypeStruct* type); - private byte* Hook(Il2CppFieldInfo* field, out Il2CppTypeStruct* type) + private byte* Hook(Il2CppFieldInfo* field, out Il2CppTypeStruct* type) + { + if (TypeInjector.GetFieldDefaultValueOverride(field, out IntPtr newDefaultPtr)) { - if (EnumInjector.GetDefaultValueOverride(field, out IntPtr newDefaultPtr)) - { - INativeFieldInfoStruct wrappedField = UnityVersionHandler.Wrap(field); - INativeClassStruct wrappedParent = UnityVersionHandler.Wrap(wrappedField.Parent); - INativeClassStruct wrappedElementClass = UnityVersionHandler.Wrap(wrappedParent.ElementClass); - type = wrappedElementClass.ByValArg.TypePointer; - return (byte*)newDefaultPtr; - } - return Original(field, out type); + INativeFieldInfoStruct wrappedField = UnityVersionHandler.Wrap(field); + INativeClassStruct wrappedParent = UnityVersionHandler.Wrap(wrappedField.Parent); + INativeClassStruct wrappedElementClass = UnityVersionHandler.Wrap(wrappedParent.ElementClass); + type = wrappedElementClass.ByValArg.TypePointer; + return (byte*)newDefaultPtr; } + return Original(field, out type); + } - private static readonly MemoryUtils.SignatureDefinition[] s_Signatures = + private static readonly SignatureDefinition[] s_Signatures = + [ + // Test Game - Unity 2021.3.4 (x64) + new SignatureDefinition + { + pattern = "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x83\xEC\x20\x48\x8B\x79\x10\x48\x8B\xD9\x48\x8B\xF2\x48\x2B\x9F", + mask = "xxxxxxxxxxxxxx?xxxxxxxxxxxxx", + xref = false + }, + + // V Rising - Unity 2022.3.23 (x64) + new SignatureDefinition + { + pattern = "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x83\xEC\x40\x48\x8B\x41\x10", + mask = "xxxxxxxxxxxxxxxxxxx", + xref = false + }, + // GTFO - Unity 2019.4.21 (x64) + new SignatureDefinition + { + pattern = "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x48\x8B\x41\x10\x48\x8B\xD9\x48\x8B", + mask = "xxxxxxxxxxxxxxxxxxx", + xref = false + }, + // Idle Slayer - Unity 2021.3.17 (x64) + new SignatureDefinition + { + pattern = "\x40\x53\x48\x83\xEC\x20\x48\x8B\xDA\xE8\x00\x00\x00\x00\x4C\x8B\xC8\x48\x85\xC0", + mask = "xxxxxxxxxx????xxxxxx", + xref = false + }, + // Evony - Unity 2018.4.0 (x86) + new SignatureDefinition { - // Test Game - Unity 2021.3.4 (x64) - new MemoryUtils.SignatureDefinition - { - pattern = "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x83\xEC\x20\x48\x8B\x79\x10\x48\x8B\xD9\x48\x8B\xF2\x48\x2B\x9F", - mask = "xxxxxxxxxxxxxx?xxxxxxxxxxxxx", - xref = false - }, - - // V Rising - Unity 2022.3.23 (x64) - new MemoryUtils.SignatureDefinition - { - pattern = "\x48\x89\x5C\x24\x08\x48\x89\x74\x24\x10\x57\x48\x83\xEC\x40\x48\x8B\x41\x10", - mask = "xxxxxxxxxxxxxxxxxxx", - xref = false - }, - // GTFO - Unity 2019.4.21 (x64) - new MemoryUtils.SignatureDefinition - { - pattern = "\x48\x89\x5C\x24\x08\x57\x48\x83\xEC\x20\x48\x8B\x41\x10\x48\x8B\xD9\x48\x8B", - mask = "xxxxxxxxxxxxxxxxxxx", - xref = false - }, - // Idle Slayer - Unity 2021.3.17 (x64) - new MemoryUtils.SignatureDefinition - { - pattern = "\x40\x53\x48\x83\xEC\x20\x48\x8B\xDA\xE8\x00\x00\x00\x00\x4C\x8B\xC8\x48\x85\xC0", - mask = "xxxxxxxxxx????xxxxxx", - xref = false - }, - // Evony - Unity 2018.4.0 (x86) - new MemoryUtils.SignatureDefinition - { - pattern = "\x55\x8B\xEC\x56\xFF\x75\x08\xE8\x00\x00\x00\x00\x8B\xF0\x83\xC4\x04\x85\xF6", - mask = "xxxxxxxx????xxxxxxx", - xref = false - }, - // Idle Slayer - Unity 2021.3.23 (x64) - new MemoryUtils.SignatureDefinition - { - pattern = "\x40\x53\x48\x83\xEC\x20\x48\x8B\xDA\xE8\xCC\xCC\xCC\xCC\x4C", - mask = "xxxxxxxxxx????x", - xref = false - } - }; - - private static nint FindClassGetFieldDefaultValueXref(bool forceICallMethod = false) + pattern = "\x55\x8B\xEC\x56\xFF\x75\x08\xE8\x00\x00\x00\x00\x8B\xF0\x83\xC4\x04\x85\xF6", + mask = "xxxxxxxx????xxxxxxx", + xref = false + }, + // Idle Slayer - Unity 2021.3.23 (x64) + new SignatureDefinition { - nint classGetDefaultFieldValue = 0; - if (forceICallMethod) - { - // MonoField isn't present on 2021.2.0+ - var monoFieldType = InjectorHelpers.Il2CppMscorlib.GetTypesSafe().SingleOrDefault((x) => x.Name is "MonoField"); - if (monoFieldType == null) - throw new Exception($"Unity {Il2CppInteropRuntime.Instance.UnityVersion} is not supported at the moment: MonoField isn't present in Il2Cppmscorlib.dll for unity version, unable to fetch icall"); + pattern = "\x40\x53\x48\x83\xEC\x20\x48\x8B\xDA\xE8\xCC\xCC\xCC\xCC\x4C", + mask = "xxxxxxxxxx????x", + xref = false + } + ]; - var monoFieldGetValueInternalThunk = InjectorHelpers.GetIl2CppMethodPointer(monoFieldType.GetMethod(nameof(Il2CppSystem.Reflection.MonoField.GetValueInternal))); - Logger.Instance.LogTrace("Il2CppSystem.Reflection.MonoField::thunk_GetValueInternal: 0x{MonoFieldGetValueInternalThunkAddress}", monoFieldGetValueInternalThunk.ToInt64().ToString("X2")); + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static nint FindClassGetFieldDefaultValueXref(bool forceICallMethod = false) + { + nint classGetDefaultFieldValue = 0; + if (forceICallMethod) + { + // MonoField isn't present on 2021.2.0+ + var il2Cppmscorlib = typeof(Il2CppSystem.Type).Assembly; + var monoFieldType = il2Cppmscorlib.GetTypesSafe().SingleOrDefault((x) => x.Name is nameof(Il2CppSystem.Reflection.MonoField)); + if (monoFieldType == null) + throw new Exception($"Unity {Il2CppInteropRuntime.Instance.UnityVersion} is not supported at the moment: MonoField isn't present in Il2Cppmscorlib.dll for unity version, unable to fetch icall"); - var monoFieldGetValueInternal = XrefScannerLowLevel.JumpTargets(monoFieldGetValueInternalThunk).Single(); - Logger.Instance.LogTrace("Il2CppSystem.Reflection.MonoField::GetValueInternal: 0x{MonoFieldGetValueInternalAddress}", monoFieldGetValueInternal.ToInt64().ToString("X2")); + var monoFieldGetValueInternalThunk = INativeMethodInfoStruct.FromGeneratedMethod(monoFieldType.GetMethod(nameof(Il2CppSystem.Reflection.MonoField.GetValueInternal)))!.MethodPointer; + Logger.Instance.LogTrace("Il2CppSystem.Reflection.MonoField::thunk_GetValueInternal: 0x{MonoFieldGetValueInternalThunkAddress}", monoFieldGetValueInternalThunk.ToInt64().ToString("X2")); - // Field::GetValueObject could be inlined with Field::GetValueObjectForThread - var fieldGetValueObject = XrefScannerLowLevel.JumpTargets(monoFieldGetValueInternal).Single(); - Logger.Instance.LogTrace("Field::GetValueObject: 0x{FieldGetValueObjectAddress}", fieldGetValueObject.ToInt64().ToString("X2")); + var monoFieldGetValueInternal = XrefScanner.JumpTargets(monoFieldGetValueInternalThunk).Single(); + Logger.Instance.LogTrace("Il2CppSystem.Reflection.MonoField::GetValueInternal: 0x{MonoFieldGetValueInternalAddress}", monoFieldGetValueInternal.ToInt64().ToString("X2")); - var fieldGetValueObjectForThread = XrefScannerLowLevel.JumpTargets(fieldGetValueObject).Last(); - Logger.Instance.LogTrace("Field::GetValueObjectForThread: 0x{FieldGetValueObjectForThreadAddress}", fieldGetValueObjectForThread.ToInt64().ToString("X2")); + // Field::GetValueObject could be inlined with Field::GetValueObjectForThread + var fieldGetValueObject = XrefScanner.JumpTargets(monoFieldGetValueInternal).Single(); + Logger.Instance.LogTrace("Field::GetValueObject: 0x{FieldGetValueObjectAddress}", fieldGetValueObject.ToInt64().ToString("X2")); - classGetDefaultFieldValue = XrefScannerLowLevel.JumpTargets(fieldGetValueObjectForThread).ElementAt(2); - } - else - { - var getStaticFieldValueAPI = InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_field_static_get_value)); - Logger.Instance.LogTrace("il2cpp_field_static_get_value: 0x{GetStaticFieldValueApiAddress}", getStaticFieldValueAPI.ToInt64().ToString("X2")); + var fieldGetValueObjectForThread = XrefScanner.JumpTargets(fieldGetValueObject).Last(); + Logger.Instance.LogTrace("Field::GetValueObjectForThread: 0x{FieldGetValueObjectForThreadAddress}", fieldGetValueObjectForThread.ToInt64().ToString("X2")); - var getStaticFieldValue = XrefScannerLowLevel.JumpTargets(getStaticFieldValueAPI).Single(); - Logger.Instance.LogTrace("Field::StaticGetValue: 0x{GetStaticFieldValueAddress}", getStaticFieldValue.ToInt64().ToString("X2")); + classGetDefaultFieldValue = XrefScanner.JumpTargets(fieldGetValueObjectForThread).ElementAt(2); + } + else + { + var getStaticFieldValueAPI = Il2CppModule.GetExport(nameof(IL2CPP.il2cpp_field_static_get_value)); + Logger.Instance.LogTrace("il2cpp_field_static_get_value: 0x{GetStaticFieldValueApiAddress}", getStaticFieldValueAPI.ToInt64().ToString("X2")); - var getStaticFieldValueTargets = XrefScannerLowLevel.JumpTargets(getStaticFieldValue).ToList(); + var getStaticFieldValue = XrefScanner.JumpTargets(getStaticFieldValueAPI).Single(); + Logger.Instance.LogTrace("Field::StaticGetValue: 0x{GetStaticFieldValueAddress}", getStaticFieldValue.ToInt64().ToString("X2")); - // Sometimes the compiler can do an optimization and omit 'retn' instruction, - // which then causes code following to grab wrong function pointer. A correct match should not contain more than 4 jumps - // This optimization also causes Field::StaticGetValueInternal method to be located right under Field::StaticGetValue method - // Example: https://discord.com/channels/623153565053222947/754333645199900723/1104817647171932283 - if (getStaticFieldValueTargets.Count > 4) - return getStaticFieldValueTargets[^2]; + var getStaticFieldValueTargets = XrefScanner.JumpTargets(getStaticFieldValue).ToList(); - var getStaticFieldValueInternal = getStaticFieldValueTargets[^1]; - Logger.Instance.LogTrace("Field::StaticGetValueInternal: 0x{GetStaticFieldValueInternalAddress}", getStaticFieldValueInternal.ToInt64().ToString("X2")); + // Sometimes the compiler can do an optimization and omit 'retn' instruction, + // which then causes code following to grab wrong function pointer. A correct match should not contain more than 4 jumps + // This optimization also causes Field::StaticGetValueInternal method to be located right under Field::StaticGetValue method + // Example: https://discord.com/channels/623153565053222947/754333645199900723/1104817647171932283 + if (getStaticFieldValueTargets.Count > 4) + return getStaticFieldValueTargets[^2]; - var getStaticFieldValueInternalTargets = XrefScannerLowLevel.JumpTargets(getStaticFieldValueInternal).ToArray(); + var getStaticFieldValueInternal = getStaticFieldValueTargets[^1]; + Logger.Instance.LogTrace("Field::StaticGetValueInternal: 0x{GetStaticFieldValueInternalAddress}", getStaticFieldValueInternal.ToInt64().ToString("X2")); - if (getStaticFieldValueInternalTargets.Length == 0) return FindClassGetFieldDefaultValueXref(true); + var getStaticFieldValueInternalTargets = XrefScanner.JumpTargets(getStaticFieldValueInternal).ToArray(); - classGetDefaultFieldValue = getStaticFieldValueInternalTargets.Length == 3 ? getStaticFieldValueInternalTargets.Last() : getStaticFieldValueInternalTargets.First(); - } - return classGetDefaultFieldValue; + if (getStaticFieldValueInternalTargets.Length == 0) return FindClassGetFieldDefaultValueXref(true); + + classGetDefaultFieldValue = getStaticFieldValueInternalTargets.Length == 3 ? getStaticFieldValueInternalTargets.Last() : getStaticFieldValueInternalTargets.First(); } + return classGetDefaultFieldValue; + } - public override IntPtr FindTargetMethod() + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public override IntPtr FindTargetMethod() + { + // NOTE: In some cases this pointer will be MetadataCache::GetFieldDefaultValueForField due to Field::GetDefaultFieldValue being + // inlined but we'll treat it the same even though it doesn't receive the type parameter the RDX register + // doesn't get cleared so we still get the same parameters + var classGetDefaultFieldValue = s_Signatures + .Select(s => SignatureDefinition.FindSignatureInModule(Il2CppModule.Module, s)) + .FirstOrDefault(p => p != 0); + + if (classGetDefaultFieldValue == 0) { - // NOTE: In some cases this pointer will be MetadataCache::GetFieldDefaultValueForField due to Field::GetDefaultFieldValue being - // inlined but we'll treat it the same even though it doesn't receive the type parameter the RDX register - // doesn't get cleared so we still get the same parameters - var classGetDefaultFieldValue = s_Signatures - .Select(s => MemoryUtils.FindSignatureInModule(InjectorHelpers.Il2CppModule, s)) - .FirstOrDefault(p => p != 0); - - if (classGetDefaultFieldValue == 0) - { - Logger.Instance.LogTrace("Couldn't fetch Class::GetDefaultFieldValue with signatures, using method traversal"); - classGetDefaultFieldValue = FindClassGetFieldDefaultValueXref(); - } - - return classGetDefaultFieldValue; + Logger.Instance.LogTrace("Couldn't fetch Class::GetDefaultFieldValue with signatures, using method traversal"); + classGetDefaultFieldValue = FindClassGetFieldDefaultValueXref(); } + + return classGetDefaultFieldValue; } } diff --git a/Il2CppInterop.Runtime/Injection/Hooks/GenericMethod_GetMethod_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/GenericMethod_GetMethod_Hook.cs deleted file mode 100644 index 9fa480b0..00000000 --- a/Il2CppInterop.Runtime/Injection/Hooks/GenericMethod_GetMethod_Hook.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.InteropServices; -using Il2CppInterop.Common; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.Runtime; -using Il2CppInterop.Runtime.Startup; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Runtime.Injection.Hooks -{ - internal unsafe class GenericMethod_GetMethod_Hook : Hook - { - public override string TargetMethodName => "GenericMethod::GetMethod"; - public override MethodDelegate GetDetour() => Hook; - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate Il2CppMethodInfo* MethodDelegate(Il2CppGenericMethod* gmethod, bool copyMethodPtr); - - private Il2CppMethodInfo* Hook(Il2CppGenericMethod* gmethod, bool copyMethodPtr) - { - if (gmethod == null || gmethod->methodDefinition == null) - return Original(gmethod, copyMethodPtr); - - if (ClassInjector.InflatedMethodFromContextDictionary.TryGetValue((IntPtr)gmethod->methodDefinition, out var methods)) - { - var instancePointer = gmethod->context.method_inst; - if (methods.Item2.TryGetValue((IntPtr)instancePointer, out var inflatedMethodPointer)) - return (Il2CppMethodInfo*)inflatedMethodPointer; - - var typeArguments = new Type[instancePointer->type_argc]; - for (var i = 0; i < instancePointer->type_argc; i++) - typeArguments[i] = ClassInjector.SystemTypeFromIl2CppType(instancePointer->type_argv[i]); - var inflatedMethod = methods.Item1.MakeGenericMethod(typeArguments); - Logger.Instance.LogTrace("Inflated method: {InflatedMethod}", inflatedMethod.Name); - inflatedMethodPointer = (IntPtr)ClassInjector.ConvertMethodInfo(inflatedMethod, - UnityVersionHandler.Wrap(UnityVersionHandler.Wrap(gmethod->methodDefinition).Class)); - methods.Item2.Add((IntPtr)instancePointer, inflatedMethodPointer); - - return (Il2CppMethodInfo*)inflatedMethodPointer; - } - - return Original(gmethod, copyMethodPtr); - } - - private static readonly MemoryUtils.SignatureDefinition[] s_Signatures = - { - // Unity 2021.2.5 (x64) - new MemoryUtils.SignatureDefinition - { - pattern = "\x48\x89\x5C\x24\x08\x48\x89\x6C\x24\x10\x56\x57\x41\x54\x41\x56\x41\x57\x48\x81\xEC\xB0\x00", - mask = "xxxxxxxxxxxxxxxxxxxxxxx", - xref = false - } - }; - - // Compilers might change method location (Unity 2021.2+) - // We do a signature scan to find the correct method - - public override IntPtr FindTargetMethod() - { - var genericMethodGetMethod = s_Signatures - .Select(s => MemoryUtils.FindSignatureInModule(InjectorHelpers.Il2CppModule, s)) - .FirstOrDefault(p => p != 0); - - if (genericMethodGetMethod == 0) - { - var getVirtualMethodAPI = InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_object_get_virtual_method)); - Logger.Instance.LogTrace("il2cpp_object_get_virtual_method: 0x{GetVirtualMethodApiAddress}", getVirtualMethodAPI.ToInt64().ToString("X2")); - - var getVirtualMethod = XrefScannerLowLevel.JumpTargets(getVirtualMethodAPI).Single(); - Logger.Instance.LogTrace("Object::GetVirtualMethod: 0x{GetVirtualMethodAddress}", getVirtualMethod.ToInt64().ToString("X2")); - - var getVirtualMethodXrefs = XrefScannerLowLevel.JumpTargets(getVirtualMethod).ToArray(); - - // If the game is built with IL2CPP Master setting, this will return 0 entries, so we do another xref scan with retn instructions ignored. - if (getVirtualMethodXrefs.Length == 0) - { - genericMethodGetMethod = XrefScannerLowLevel.JumpTargets(getVirtualMethod, true).Last(); - } - else - { - // U2021.2.0+, there's additional shim that takes 3 parameters - // On U2020.3.41+ there is also a shim, which gets inlined with one added in U2021.2.0+ in release builds - if (UnityVersionHandler.HasShimForGetMethod) - { - var shim = getVirtualMethodXrefs.Last(); - - var shimXrefs = XrefScannerLowLevel.JumpTargets(shim).ToArray(); - - // If the xref count is 1, it probably means the target is after ret - if (Il2CppInteropRuntime.Instance.UnityVersion.Major == 2020 && shimXrefs.Length == 1) - { - shimXrefs = XrefScannerLowLevel.JumpTargets(shim, true).ToArray(); - } - - genericMethodGetMethod = shimXrefs.Take(2).Last(); - } - else - { - genericMethodGetMethod = getVirtualMethodXrefs.Last(); - } - } - } - - return genericMethodGetMethod; - } - } -} diff --git a/Il2CppInterop.Runtime/Injection/Hooks/GenericMethod_GetMethod_Unity6_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/GenericMethod_GetMethod_Unity6_Hook.cs deleted file mode 100644 index b1b58f72..00000000 --- a/Il2CppInterop.Runtime/Injection/Hooks/GenericMethod_GetMethod_Unity6_Hook.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.InteropServices; -using Il2CppInterop.Common; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.Runtime; -using Il2CppInterop.Runtime.Startup; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Runtime.Injection.Hooks -{ - /// Unity 6 (6000.x.x): the 1-param GetMethod(const Il2CppGenericMethod&) is inlined into - /// the 3-param GetMethod(const MethodInfo*, const Il2CppGenericInst*, const Il2CppGenericInst*). - /// We use the hook with 3 param correctly - internal unsafe class GenericMethod_GetMethod_Unity6_Hook : Hook - { - public override string TargetMethodName => "GenericMethod::GetMethod"; - public override MethodDelegate GetDetour() => Hook; - - // CRITICAL: Use Cdecl for Linux x64 (System V ABI) - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate Il2CppMethodInfo* MethodDelegate(Il2CppMethodInfo* methodDefinition, Il2CppGenericInst* classInst, Il2CppGenericInst* methodInst); - - private Il2CppMethodInfo* Hook(Il2CppMethodInfo* methodDefinition, Il2CppGenericInst* classInst, Il2CppGenericInst* methodInst) - { - // Direct pass-through if the definition is null for safety - if (methodDefinition == null) - return Original(methodDefinition, classInst, methodInst); - - try - { - // Check if the dictionary even exists before trying to access it - if (ClassInjector.InflatedMethodFromContextDictionary == null) - return Original(methodDefinition, classInst, methodInst); - - if (ClassInjector.InflatedMethodFromContextDictionary.TryGetValue((IntPtr)methodDefinition, out var methods)) - { - // If the dictionary entry is malformed, skip - if (methods.Item1 == null || methods.Item2 == null) - return Original(methodDefinition, classInst, methodInst); - - // If it's a class-level generic without method generics, methodInst is null - if (methodInst == null) - return Original(methodDefinition, classInst, methodInst); - - // Check our cache first - if (methods.Item2.TryGetValue((IntPtr)methodInst, out var inflatedMethodPointer)) - return (Il2CppMethodInfo*)inflatedMethodPointer; - - // Validate the GenericInst structure before reading it - // On Linux, memory alignment is strict. Ensure type_argc is sane. - if (methodInst->type_argc > 256 || methodInst->type_argv == null) - return Original(methodDefinition, classInst, methodInst); - - var typeArguments = new Type[methodInst->type_argc]; - for (var i = 0; i < methodInst->type_argc; i++) - { - var il2cppType = methodInst->type_argv[i]; - if (il2cppType == null) return Original(methodDefinition, classInst, methodInst); - - typeArguments[i] = ClassInjector.SystemTypeFromIl2CppType(il2cppType); - } - - var inflatedMethod = methods.Item1.MakeGenericMethod(typeArguments); - Logger.Instance.LogTrace("Inflated method: {InflatedMethod}", inflatedMethod.Name); - - // Use the specific UnityVersionHandler for Unity 6 - var wrappedMethod = UnityVersionHandler.Wrap(methodDefinition); - if (wrappedMethod == null) return Original(methodDefinition, classInst, methodInst); - - inflatedMethodPointer = (IntPtr)ClassInjector.ConvertMethodInfo( - inflatedMethod, UnityVersionHandler.Wrap(wrappedMethod.Class)); - - // Cache the result to prevent recalculating next time - methods.Item2.Add((IntPtr)methodInst, inflatedMethodPointer); - - return (Il2CppMethodInfo*)inflatedMethodPointer; - } - } - catch (Exception ex) - { - // CRITICAL: On Linux, an unhandled exception in a hook = SIGSEGV. - // We must catch and log, then return original. -#if DEBUG - Logger.Instance.LogError($"[GenericHook] Exception: {ex.Message}"); -#endif - } - - return Original(methodDefinition, classInst, methodInst); - } - - public override IntPtr FindTargetMethod() - { - var getVirtualMethodAPI = InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_object_get_virtual_method)); - if (getVirtualMethodAPI == IntPtr.Zero) return IntPtr.Zero; - - // Follow the jump into the actual function body - var getVirtualMethod = XrefScannerLowLevel.JumpTargets(getVirtualMethodAPI).FirstOrDefault(); - if (getVirtualMethod == IntPtr.Zero) return IntPtr.Zero; - - // We are looking for the call to GetGenericVirtualMethod - var xrefs = XrefScannerLowLevel.JumpTargets(getVirtualMethod).ToArray(); - if (xrefs.Length == 0) return IntPtr.Zero; - - // On Linux Unity 6, it's usually the LAST jump before the end of the function - IntPtr getGenericVirtualMethod = xrefs.Last(); - - // Now, inside GetGenericVirtualMethod, there is a tail-call (jmp) to GenericMethod::GetMethod - var finalXrefs = XrefScannerLowLevel.JumpTargets(getGenericVirtualMethod).ToArray(); - - if (finalXrefs.Length == 0) - return IntPtr.Zero; - - IntPtr candidate = finalXrefs.Last(); - - // VERIFICATION: On Linux, GenericMethod::GetMethod is a large function. - // If the address is too close to the caller, it's probably wrong. -#if DEBUG - Logger.Instance.LogDebug($"[Scanner] Found GenericMethod::GetMethod candidate: 0x{candidate:X}"); -#endif - - return candidate; - } - } -} diff --git a/Il2CppInterop.Runtime/Injection/Hooks/MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook.cs index 943a9ad0..b8a8c143 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook.cs @@ -1,122 +1,127 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using Il2CppInterop.Common; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.Runtime; +using Il2CppInterop.Runtime.Extensions; using Il2CppInterop.Runtime.Startup; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; using Microsoft.Extensions.Logging; -namespace Il2CppInterop.Runtime.Injection.Hooks +namespace Il2CppInterop.Runtime.Injection.Hooks; + +internal unsafe class MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook : + Hook { - internal unsafe class MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook : - Hook + public override string TargetMethodName => "MetadataCache::GetTypeInfoFromTypeDefinitionIndex"; + public override MethodDelegate GetDetour() => Hook; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate Il2CppClass* MethodDelegate(int index); + + private Il2CppClass* Hook(int index) { - public override string TargetMethodName => "MetadataCache::GetTypeInfoFromTypeDefinitionIndex"; - public override MethodDelegate GetDetour() => Hook; + if (TokenAllocator.TryGetClassPointer(index, out var classPtr)) + return (Il2CppClass*)classPtr; - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate Il2CppClass* MethodDelegate(int index); + return Original(index); + } - private Il2CppClass* Hook(int index) + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private IntPtr FindGetTypeInfoFromTypeDefinitionIndex(bool forceICallMethod = false) + { + IntPtr getTypeInfoFromTypeDefinitionIndex = IntPtr.Zero; + + // il2cpp_image_get_class is added in 2018.3.0f1 + if (Il2CppInteropRuntime.Instance.UnityVersion.LessThan(2018, 3, 0) || forceICallMethod) { - if (InjectorHelpers.s_InjectedClasses.TryGetValue(index, out IntPtr classPtr)) - return (Il2CppClass*)classPtr; + // (Kasuromi): RuntimeHelpers.InitializeArray calls an il2cpp icall, proxy function does some magic before it invokes it + // https://github.com/Unity-Technologies/mono/blob/unity-2018.2/mcs/class/corlib/System.Runtime.CompilerServices/RuntimeHelpers.cs#L53-L54 + IntPtr runtimeHelpersInitializeArray = INativeMethodInfoStruct.FromGeneratedMethod( + typeof(Il2CppSystem.Runtime.CompilerServices.RuntimeHelpers) + .GetMethod(nameof(Il2CppSystem.Runtime.CompilerServices.RuntimeHelpers.InitializeArray), new Type[] { typeof(Il2CppSystem.Array), typeof(IntPtr) }) + )!.MethodPointer; + Logger.Instance.LogTrace("Il2CppSystem.Runtime.CompilerServices.RuntimeHelpers::InitializeArray: 0x{RuntimeHelpersInitializeArrayAddress}", runtimeHelpersInitializeArray.ToInt64().ToString("X2")); + + var runtimeHelpersInitializeArrayICall = XrefScanner.JumpTargets(runtimeHelpersInitializeArray).Last(); + if (XrefScanner.JumpTargets(runtimeHelpersInitializeArrayICall).Count() == 1) + { + // is a thunk function + Logger.Instance.LogTrace("RuntimeHelpers::thunk_InitializeArray: 0x{RuntimeHelpersInitializeArrayICallAddress}", runtimeHelpersInitializeArrayICall.ToInt64().ToString("X2")); + runtimeHelpersInitializeArrayICall = XrefScanner.JumpTargets(runtimeHelpersInitializeArrayICall).Single(); + } - return Original(index); - } + Logger.Instance.LogTrace("RuntimeHelpers::InitializeArray: 0x{RuntimeHelpersInitializeArrayICallAddress}", runtimeHelpersInitializeArrayICall.ToInt64().ToString("X2")); - private IntPtr FindGetTypeInfoFromTypeDefinitionIndex(bool forceICallMethod = false) - { - IntPtr getTypeInfoFromTypeDefinitionIndex = IntPtr.Zero; + var typeGetUnderlyingType = XrefScanner.JumpTargets(runtimeHelpersInitializeArrayICall).ElementAt(1); + Logger.Instance.LogTrace("Type::GetUnderlyingType: 0x{TypeGetUnderlyingTypeAddress}", typeGetUnderlyingType.ToInt64().ToString("X2")); - // il2cpp_image_get_class is added in 2018.3.0f1 - if (Il2CppInteropRuntime.Instance.UnityVersion < new Version(2018, 3, 0) || forceICallMethod) - { - // (Kasuromi): RuntimeHelpers.InitializeArray calls an il2cpp icall, proxy function does some magic before it invokes it - // https://github.com/Unity-Technologies/mono/blob/unity-2018.2/mcs/class/corlib/System.Runtime.CompilerServices/RuntimeHelpers.cs#L53-L54 - IntPtr runtimeHelpersInitializeArray = InjectorHelpers.GetIl2CppMethodPointer( - typeof(Il2CppSystem.Runtime.CompilerServices.RuntimeHelpers) - .GetMethod("InitializeArray", new Type[] { typeof(Il2CppSystem.Array), typeof(IntPtr) }) - ); - Logger.Instance.LogTrace("Il2CppSystem.Runtime.CompilerServices.RuntimeHelpers::InitializeArray: 0x{RuntimeHelpersInitializeArrayAddress}", runtimeHelpersInitializeArray.ToInt64().ToString("X2")); - - var runtimeHelpersInitializeArrayICall = XrefScannerLowLevel.JumpTargets(runtimeHelpersInitializeArray).Last(); - if (XrefScannerLowLevel.JumpTargets(runtimeHelpersInitializeArrayICall).Count() == 1) - { - // is a thunk function - Logger.Instance.LogTrace("RuntimeHelpers::thunk_InitializeArray: 0x{RuntimeHelpersInitializeArrayICallAddress}", runtimeHelpersInitializeArrayICall.ToInt64().ToString("X2")); - runtimeHelpersInitializeArrayICall = XrefScannerLowLevel.JumpTargets(runtimeHelpersInitializeArrayICall).Single(); - } + getTypeInfoFromTypeDefinitionIndex = XrefScanner.JumpTargets(typeGetUnderlyingType).First(); + } + else + { + var imageGetClassAPI = Il2CppModule.GetExport(nameof(IL2CPP.il2cpp_image_get_class)); + Logger.Instance.LogTrace("il2cpp_image_get_class: 0x{ImageGetClassApiAddress}", imageGetClassAPI.ToInt64().ToString("X2")); - Logger.Instance.LogTrace("RuntimeHelpers::InitializeArray: 0x{RuntimeHelpersInitializeArrayICallAddress}", runtimeHelpersInitializeArrayICall.ToInt64().ToString("X2")); + var imageGetType = XrefScanner.JumpTargets(imageGetClassAPI).First(); + Logger.Instance.LogTrace("Image::GetType: 0x{ImageGetTypeAddress}", imageGetType.ToInt64().ToString("X2")); - var typeGetUnderlyingType = XrefScannerLowLevel.JumpTargets(runtimeHelpersInitializeArrayICall).ElementAt(1); - Logger.Instance.LogTrace("Type::GetUnderlyingType: 0x{TypeGetUnderlyingTypeAddress}", typeGetUnderlyingType.ToInt64().ToString("X2")); + var imageGetTypeXrefs = XrefScanner.JumpTargets(imageGetType).ToArray(); - getTypeInfoFromTypeDefinitionIndex = XrefScannerLowLevel.JumpTargets(typeGetUnderlyingType).First(); + if (imageGetTypeXrefs.Length == 0) + { + // (Kasuromi): Image::GetType appears to be inlined in il2cpp_image_get_class on some occasions, + // if the unconditional xrefs are 0 then we are in the correct method (seen on unity 2019.3.15) + getTypeInfoFromTypeDefinitionIndex = imageGetType; + } + else getTypeInfoFromTypeDefinitionIndex = imageGetTypeXrefs[0]; + if ((getTypeInfoFromTypeDefinitionIndex.ToInt64() & 0xF) != 0) + { + Logger.Instance.LogTrace("Image::GetType xref wasn't aligned, attempting to resolve from icall"); + return FindGetTypeInfoFromTypeDefinitionIndex(true); } - else + if (imageGetTypeXrefs.Count() > 1 && UnityVersionHandler.IsMetadataV29OrHigher) { - var imageGetClassAPI = InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_image_get_class)); - Logger.Instance.LogTrace("il2cpp_image_get_class: 0x{ImageGetClassApiAddress}", imageGetClassAPI.ToInt64().ToString("X2")); + // (Kasuromi): metadata v29 introduces handles and adds extra calls, a check for unity versions might be necessary in the future - var imageGetType = XrefScannerLowLevel.JumpTargets(imageGetClassAPI).First(); - Logger.Instance.LogTrace("Image::GetType: 0x{ImageGetTypeAddress}", imageGetType.ToInt64().ToString("X2")); + Logger.Instance.LogTrace($"imageGetTypeXrefs.Length: {imageGetTypeXrefs.Length}"); - var imageGetTypeXrefs = XrefScannerLowLevel.JumpTargets(imageGetType).ToArray(); + // If the game is built as IL2CPP Master, GetAssemblyTypeHandle is inlined, xrefs length is 3 and it's the first function call, + // if not, it's the last call. + var getTypeInfoFromHandle = imageGetTypeXrefs.Length == 2 ? imageGetTypeXrefs.Last() : imageGetTypeXrefs.First(); - if (imageGetTypeXrefs.Length == 0) - { - // (Kasuromi): Image::GetType appears to be inlined in il2cpp_image_get_class on some occasions, - // if the unconditional xrefs are 0 then we are in the correct method (seen on unity 2019.3.15) - getTypeInfoFromTypeDefinitionIndex = imageGetType; - } - else getTypeInfoFromTypeDefinitionIndex = imageGetTypeXrefs[0]; - if ((getTypeInfoFromTypeDefinitionIndex.ToInt64() & 0xF) != 0) + Logger.Instance.LogTrace($"getTypeInfoFromHandle: {getTypeInfoFromHandle:X2}"); + + var getTypeInfoFromHandleXrefs = XrefScanner.JumpTargets(getTypeInfoFromHandle).ToArray(); + + // If getTypeInfoFromHandle xrefs is not a single call, it's the function we want, if not, we keep xrefing until we find it + if (getTypeInfoFromHandleXrefs.Length != 1) { - Logger.Instance.LogTrace("Image::GetType xref wasn't aligned, attempting to resolve from icall"); - return FindGetTypeInfoFromTypeDefinitionIndex(true); + getTypeInfoFromTypeDefinitionIndex = getTypeInfoFromHandle; + Logger.Instance.LogTrace($"Xrefs length was not 1, getTypeInfoFromTypeDefinitionIndex: {getTypeInfoFromTypeDefinitionIndex:X2}"); } - if (imageGetTypeXrefs.Count() > 1 && UnityVersionHandler.IsMetadataV29OrHigher) + else { - // (Kasuromi): metadata v29 introduces handles and adds extra calls, a check for unity versions might be necessary in the future - - Logger.Instance.LogTrace($"imageGetTypeXrefs.Length: {imageGetTypeXrefs.Length}"); - - // If the game is built as IL2CPP Master, GetAssemblyTypeHandle is inlined, xrefs length is 3 and it's the first function call, - // if not, it's the last call. - var getTypeInfoFromHandle = imageGetTypeXrefs.Length == 2 ? imageGetTypeXrefs.Last() : imageGetTypeXrefs.First(); - - Logger.Instance.LogTrace($"getTypeInfoFromHandle: {getTypeInfoFromHandle:X2}"); - - var getTypeInfoFromHandleXrefs = XrefScannerLowLevel.JumpTargets(getTypeInfoFromHandle).ToArray(); - - // If getTypeInfoFromHandle xrefs is not a single call, it's the function we want, if not, we keep xrefing until we find it - if (getTypeInfoFromHandleXrefs.Length != 1) + // Two calls, second one (GetIndexForTypeDefinitionInternal) is inlined + getTypeInfoFromTypeDefinitionIndex = getTypeInfoFromHandleXrefs.Single(); + // Xref scanner is sometimes confused about getTypeInfoFromHandle so we walk all the thunks until we hit the big method we need + while (XrefScanner.JumpTargets(getTypeInfoFromTypeDefinitionIndex).ToArray().Length == 1) { - getTypeInfoFromTypeDefinitionIndex = getTypeInfoFromHandle; - Logger.Instance.LogTrace($"Xrefs length was not 1, getTypeInfoFromTypeDefinitionIndex: {getTypeInfoFromTypeDefinitionIndex:X2}"); - } - else - { - // Two calls, second one (GetIndexForTypeDefinitionInternal) is inlined - getTypeInfoFromTypeDefinitionIndex = getTypeInfoFromHandleXrefs.Single(); - // Xref scanner is sometimes confused about getTypeInfoFromHandle so we walk all the thunks until we hit the big method we need - while (XrefScannerLowLevel.JumpTargets(getTypeInfoFromTypeDefinitionIndex).ToArray().Length == 1) - { - getTypeInfoFromTypeDefinitionIndex = XrefScannerLowLevel.JumpTargets(getTypeInfoFromTypeDefinitionIndex).Single(); - } + getTypeInfoFromTypeDefinitionIndex = XrefScanner.JumpTargets(getTypeInfoFromTypeDefinitionIndex).Single(); } } } - - return getTypeInfoFromTypeDefinitionIndex; } - public override IntPtr FindTargetMethod() - { - return FindGetTypeInfoFromTypeDefinitionIndex(); - } + return getTypeInfoFromTypeDefinitionIndex; + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public override IntPtr FindTargetMethod() + { + return FindGetTypeInfoFromTypeDefinitionIndex(); } } diff --git a/Il2CppInterop.Runtime/MemoryUtils.cs b/Il2CppInterop.Runtime/Injection/Hooks/SignatureDefinition.cs similarity index 79% rename from Il2CppInterop.Runtime/MemoryUtils.cs rename to Il2CppInterop.Runtime/Injection/Hooks/SignatureDefinition.cs index 71aa32e0..1e13abdc 100644 --- a/Il2CppInterop.Runtime/MemoryUtils.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/SignatureDefinition.cs @@ -1,11 +1,15 @@ using System.Diagnostics; using System.Linq; -using Il2CppInterop.Common.XrefScans; -namespace Il2CppInterop.Runtime; +namespace Il2CppInterop.Runtime.Injection.Hooks; -internal class MemoryUtils +internal struct SignatureDefinition { + public string pattern; + public string mask; + public int offset; + public bool xref; + public static nint FindSignatureInModule(ProcessModule module, SignatureDefinition sigDef) { var ptr = FindSignatureInBlock( @@ -16,7 +20,7 @@ public static nint FindSignatureInModule(ProcessModule module, SignatureDefiniti sigDef.offset ); if (ptr != 0 && sigDef.xref) - ptr = XrefScannerLowLevel.JumpTargets(ptr).FirstOrDefault(); + ptr = XrefScanner.JumpTargets(ptr).FirstOrDefault(); return ptr; } @@ -44,12 +48,4 @@ public static unsafe nint FindSignatureInBlock(nint block, long blockSize, char[ return 0; } - - public struct SignatureDefinition - { - public string pattern; - public string mask; - public int offset; - public bool xref; - } } diff --git a/Il2CppInterop.Runtime/Injection/IDetourProvider.cs b/Il2CppInterop.Runtime/Injection/IDetourProvider.cs new file mode 100644 index 00000000..c01739a0 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/IDetourProvider.cs @@ -0,0 +1,8 @@ +using System; + +namespace Il2CppInterop.Runtime.Injection; + +public interface IDetourProvider +{ + IDisposable Create(nint original, TDelegate target, out TDelegate trampoline) where TDelegate : Delegate; +} diff --git a/Il2CppInterop.Runtime/Injection/Il2CppModule.cs b/Il2CppInterop.Runtime/Injection/Il2CppModule.cs new file mode 100644 index 00000000..48a8f84f --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/Il2CppModule.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Il2CppInterop.Runtime.Injection; + +internal static class Il2CppModule +{ + internal static readonly ProcessModule Module = Process.GetCurrentProcess() + .Modules.OfType() + .Single((x) => x.ModuleName is "GameAssembly.dll" or "GameAssembly.dylib" or "GameAssembly.so" or "UserAssembly.dll"); + + private static readonly IntPtr Handle = NativeLibrary.Load("GameAssembly", typeof(Il2CppModule).Assembly, null); + + internal static IntPtr GetExport(string name) + { + if (!TryGetExport(name, out var address)) + { + throw new NotSupportedException($"Couldn't find {name} in {Module.ModuleName}'s exports"); + } + + return address; + } + + internal static bool TryGetExport(string name, out IntPtr address) + { + return NativeLibrary.TryGetExport(Handle, name, out address); + } +} diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs deleted file mode 100644 index 9bd9b0de..00000000 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.InteropServices; -using System.Threading; -using Il2CppInterop.Common; -using Il2CppInterop.Common.Extensions; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.Injection.Hooks; -using Il2CppInterop.Runtime.Runtime; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.FieldInfo; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Image; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo; -using Il2CppInterop.Runtime.Startup; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Runtime.Injection -{ - internal static unsafe class InjectorHelpers - { - internal static Assembly Il2CppMscorlib = typeof(Il2CppSystem.Type).Assembly; - internal static INativeAssemblyStruct InjectedAssembly; - internal static INativeImageStruct InjectedImage; - internal static ProcessModule Il2CppModule = Process.GetCurrentProcess() - .Modules.OfType() - .Single((x) => x.ModuleName is "GameAssembly.dll" or "GameAssembly.so" or "UserAssembly.dll"); - - internal static IntPtr Il2CppHandle = NativeLibrary.Load("GameAssembly", typeof(InjectorHelpers).Assembly, null); - - internal static readonly Dictionary StIndOpcodes = new() - { - [typeof(byte)] = OpCodes.Stind_I1, - [typeof(sbyte)] = OpCodes.Stind_I1, - [typeof(bool)] = OpCodes.Stind_I1, - [typeof(short)] = OpCodes.Stind_I2, - [typeof(ushort)] = OpCodes.Stind_I2, - [typeof(int)] = OpCodes.Stind_I4, - [typeof(uint)] = OpCodes.Stind_I4, - [typeof(long)] = OpCodes.Stind_I8, - [typeof(ulong)] = OpCodes.Stind_I8, - [typeof(float)] = OpCodes.Stind_R4, - [typeof(double)] = OpCodes.Stind_R8 - }; - - private static void CreateInjectedAssembly() - { - InjectedAssembly = UnityVersionHandler.NewAssembly(); - InjectedImage = UnityVersionHandler.NewImage(); - - InjectedAssembly.Name.Name = Marshal.StringToCoTaskMemUTF8("InjectedMonoTypes"); - - InjectedImage.Assembly = InjectedAssembly.AssemblyPointer; - InjectedImage.Dynamic = 1; - InjectedImage.Name = InjectedAssembly.Name.Name; - if (InjectedImage.HasNameNoExt) - InjectedImage.NameNoExt = InjectedAssembly.Name.Name; - } - - private static readonly GenericMethod_GetMethod_Hook GenericMethodGetMethodHook = new(); - private static readonly GenericMethod_GetMethod_Unity6_Hook GenericMethodGetMethodHook_Unity6 = new(); - private static readonly MetadataCache_GetTypeInfoFromTypeDefinitionIndex_Hook GetTypeInfoFromTypeDefinitionIndexHook = new(); - private static readonly Class_GetFieldDefaultValue_Hook GetFieldDefaultValueHook = new(); - private static readonly Class_FromIl2CppType_Hook FromIl2CppTypeHook = new(); - private static readonly Class_FromName_Hook FromNameHook = new(); - internal static void Setup() - { - if (InjectedAssembly == null) CreateInjectedAssembly(); - if (Il2CppInteropRuntime.Instance.UnityVersion.Major >= 6000) - GenericMethodGetMethodHook_Unity6.ApplyHook(); - else - GenericMethodGetMethodHook.ApplyHook(); - GetTypeInfoFromTypeDefinitionIndexHook.ApplyHook(); - GetFieldDefaultValueHook.ApplyHook(); - ClassInit ??= FindClassInit(); - FromIl2CppTypeHook.ApplyHook(); - FromNameHook.ApplyHook(); - } - - internal static long CreateClassToken(IntPtr classPointer) - { - long newToken = Interlocked.Decrement(ref s_LastInjectedToken); - s_InjectedClasses[newToken] = classPointer; - return newToken; - } - - internal static void AddTypeToLookup(IntPtr typePointer) where T : class => AddTypeToLookup(typeof(T), typePointer); - internal static void AddTypeToLookup(Type type, IntPtr typePointer) - { - string klass = type.Name; - if (klass == null) return; - string namespaze = type.Namespace ?? string.Empty; - var attribute = Attribute.GetCustomAttribute(type, typeof(Il2CppInterop.Runtime.Attributes.ClassInjectionAssemblyTargetAttribute)) as Il2CppInterop.Runtime.Attributes.ClassInjectionAssemblyTargetAttribute; - - foreach (IntPtr image in (attribute is null) ? IL2CPP.GetIl2CppImages() : attribute.GetImagePointers()) - { - s_ClassNameLookup.Add((namespaze, klass, image), typePointer); - } - } - - internal static IntPtr GetIl2CppExport(string name) - { - if (!TryGetIl2CppExport(name, out var address)) - { - throw new NotSupportedException($"Couldn't find {name} in {Il2CppModule.ModuleName}'s exports"); - } - - return address; - } - - internal static bool TryGetIl2CppExport(string name, out IntPtr address) - { - return NativeLibrary.TryGetExport(Il2CppHandle, name, out address); - } - - internal static IntPtr GetIl2CppMethodPointer(MethodBase proxyMethod) - { - if (proxyMethod == null) return IntPtr.Zero; - - FieldInfo methodInfoPointerField = Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(proxyMethod); - if (methodInfoPointerField == null) - throw new ArgumentException($"Couldn't find the generated method info pointer for {proxyMethod.Name}"); - - // Il2CppClassPointerStore calls the static constructor for the type - Il2CppClassPointerStore.GetNativeClassPointer(proxyMethod.DeclaringType); - - IntPtr methodInfoPointer = (IntPtr)methodInfoPointerField.GetValue(null); - if (methodInfoPointer == IntPtr.Zero) - throw new ArgumentException($"Generated method info pointer for {proxyMethod.Name} doesn't point to any il2cpp method info"); - INativeMethodInfoStruct methodInfo = UnityVersionHandler.Wrap((Il2CppMethodInfo*)methodInfoPointer); - return methodInfo.MethodPointer; - } - - private static long s_LastInjectedToken = -2; - internal static readonly ConcurrentDictionary s_InjectedClasses = new(); - /// (namespace, class, image) : class - internal static readonly Dictionary<(string _namespace, string _class, IntPtr imagePtr), IntPtr> s_ClassNameLookup = new(); - - #region Class::Init - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void d_ClassInit(Il2CppClass* klass); - internal static d_ClassInit ClassInit; - - private static readonly MemoryUtils.SignatureDefinition[] s_ClassInitSignatures = - { - new MemoryUtils.SignatureDefinition - { - pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x28\x83", - mask = "x????xxxxx", - xref = true - }, - new MemoryUtils.SignatureDefinition - { - pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x48\x48", - mask = "x????xxxxx", - xref = true - } - }; - - private static d_ClassInit FindClassInit() - { - static nint GetClassInitSubstitute() - { - if (TryGetIl2CppExport("mono_class_instance_size", out nint classInit)) - { - Logger.Instance.LogTrace("Picked mono_class_instance_size as a Class::Init substitute"); - return classInit; - } - if (TryGetIl2CppExport("mono_class_setup_vtable", out classInit)) - { - Logger.Instance.LogTrace("Picked mono_class_setup_vtable as a Class::Init substitute"); - return classInit; - } - if (TryGetIl2CppExport(nameof(IL2CPP.il2cpp_class_has_references), out classInit)) - { - Logger.Instance.LogTrace("Picked il2cpp_class_has_references as a Class::Init substitute"); - return classInit; - } - - Logger.Instance.LogTrace("GameAssembly.dll: 0x{Il2CppModuleAddress}", Il2CppModule.BaseAddress.ToInt64().ToString("X2")); - throw new NotSupportedException("Failed to use signature for Class::Init and a substitute cannot be found, please create an issue and report your unity version & game"); - } - nint pClassInit = s_ClassInitSignatures - .Select(s => MemoryUtils.FindSignatureInModule(Il2CppModule, s)) - .FirstOrDefault(p => p != 0); - - if (pClassInit == 0) - { - Logger.Instance.LogWarning("Class::Init signatures have been exhausted, using a substitute!"); - pClassInit = GetClassInitSubstitute(); - } - - Logger.Instance.LogTrace("Class::Init: 0x{PClassInitAddress}", pClassInit.ToString("X2")); - - return Marshal.GetDelegateForFunctionPointer(pClassInit); - } - #endregion - } -} diff --git a/Il2CppInterop.Runtime/Injection/InvokerSignatureHash.cs b/Il2CppInterop.Runtime/Injection/InvokerSignatureHash.cs new file mode 100644 index 00000000..110d8edc --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/InvokerSignatureHash.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO.Hashing; +using System.Reflection; +using Il2CppInterop.Runtime.Extensions; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; + +namespace Il2CppInterop.Runtime.Injection; + +/// +/// A hash representing the signature of a generated invoker method +/// +internal readonly record struct InvokerSignatureHash +{ + private readonly UInt128 _hash; + + public unsafe InvokerSignatureHash(INativeMethodInfoStruct methodInfo) + { + XxHash128 hash = new(); + hash.Append((methodInfo.Flags & Il2CppMethodFlags.METHOD_ATTRIBUTE_STATIC) != 0); + hash.Append(GetFullName(methodInfo.ReturnType)); + for (var i = 0; i < methodInfo.ParametersCount; i++) + { + var parameter = UnityVersionHandler.Wrap(methodInfo.Parameters, i); + hash.Append(GetFullName(parameter.ParameterType)); + } + _hash = hash.GetCurrentHashAsUInt128(); + } + + [RequiresDynamicCode("")] + public InvokerSignatureHash(MethodInfo methodInfo) + { + XxHash128 hash = new(); + hash.Append(methodInfo.IsStatic); + hash.Append(GetFullName(methodInfo.ReturnType)); + foreach (var parameter in methodInfo.GetParameters()) + { + hash.Append(GetFullName(parameter.ParameterType)); + } + _hash = hash.GetCurrentHashAsUInt128(); + } + + [RequiresDynamicCode("")] + private static unsafe Il2CppSystem.String GetFullName(Type type) + { + return GetFullName((Il2CppTypeStruct*)Il2CppTypePointerStore.GetNativeTypePointer(type)); + } + + private static unsafe Il2CppSystem.String GetFullName(Il2CppTypeStruct* type) + { + return Il2CppSystem.Type.FromTypePointer((nint)type).FullName; + } + + public override string ToString() => _hash.ToString(); +} diff --git a/Il2CppInterop.Runtime/Injection/NamedSignatureHash.cs b/Il2CppInterop.Runtime/Injection/NamedSignatureHash.cs new file mode 100644 index 00000000..3ff3e274 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/NamedSignatureHash.cs @@ -0,0 +1,57 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO.Hashing; +using System.Reflection; +using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Runtime.Extensions; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; + +namespace Il2CppInterop.Runtime.Injection; + +internal readonly record struct NamedSignatureHash +{ + private readonly UInt128 _hash; + + public unsafe NamedSignatureHash(INativeMethodInfoStruct methodInfo) + { + XxHash128 hash = new(); + hash.Append(methodInfo.GetNameSpan()); + hash.Append((methodInfo.Flags & Il2CppMethodFlags.METHOD_ATTRIBUTE_STATIC) != 0); + hash.Append(GetFullName(methodInfo.ReturnType)); + for (var i = 0; i < methodInfo.ParametersCount; i++) + { + var parameter = UnityVersionHandler.Wrap(methodInfo.Parameters, i); + hash.Append(GetFullName(parameter.ParameterType)); + } + _hash = hash.GetCurrentHashAsUInt128(); + } + + [RequiresDynamicCode("")] + public NamedSignatureHash(MethodInfo methodInfo) + { + XxHash128 hash = new(); + var name = methodInfo.GetCustomAttribute()?.Name ?? methodInfo.Name; + hash.AppendUtf8(name); + hash.Append(methodInfo.IsStatic); + hash.Append(GetFullName(methodInfo.ReturnType)); + foreach (var parameter in methodInfo.GetParameters()) + { + hash.Append(GetFullName(parameter.ParameterType)); + } + _hash = hash.GetCurrentHashAsUInt128(); + } + + [RequiresDynamicCode("")] + private static unsafe Il2CppSystem.String GetFullName(Type type) + { + return GetFullName((Il2CppTypeStruct*)Il2CppTypePointerStore.GetNativeTypePointer(type)); + } + + private static unsafe Il2CppSystem.String GetFullName(Il2CppTypeStruct* type) + { + return Il2CppSystem.Type.FromTypePointer((nint)type).FullName; + } + + public override string ToString() => _hash.ToString(); +} diff --git a/Il2CppInterop.Runtime/Injection/TokenAllocator.cs b/Il2CppInterop.Runtime/Injection/TokenAllocator.cs new file mode 100644 index 00000000..26db4a82 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/TokenAllocator.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Il2CppInterop.Runtime.Injection; + +internal static class TokenAllocator +{ + private static long s_LastInjectedToken = -2; + private static readonly ConcurrentDictionary s_InjectedClasses = new(); + + public static long Assign(IntPtr classPointer) + { + var newToken = Interlocked.Decrement(ref s_LastInjectedToken); + s_InjectedClasses[newToken] = classPointer; + return newToken; + } + + public static bool TryGetClassPointer(long token, out IntPtr classPointer) + { + return s_InjectedClasses.TryGetValue(token, out classPointer); + } +} diff --git a/Il2CppInterop.Runtime/Injection/TrampolineBuilder.cs b/Il2CppInterop.Runtime/Injection/TrampolineBuilder.cs new file mode 100644 index 00000000..4c3460cb --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/TrampolineBuilder.cs @@ -0,0 +1,300 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime.Structs; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime.Injection; + +[RequiresDynamicCode("")] +public static class TrampolineBuilder +{ + private static readonly AssemblyBuilder _fixedStructAssembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("FixedSizeStructAssembly"), AssemblyBuilderAccess.Run); + private static readonly ModuleBuilder _fixedStructModuleBuilder = _fixedStructAssembly.DefineDynamicModule("FixedSizeStructAssembly"); + private static readonly Dictionary _fixedStructCache = new(); + + private static readonly AssemblyBuilder _delegateAssembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Il2CppTrampolineDelegates"), AssemblyBuilderAccess.Run); + private static readonly ModuleBuilder _delegateModule = _delegateAssembly.DefineDynamicModule("Il2CppTrampolineDelegates"); + private static readonly ConcurrentDictionary ourDelegateTypes = new(); + + private static Type GetFixedSizeStructType(int size) + { + if (_fixedStructCache.TryGetValue(size, out var result)) + { + return result; + } + + var tb = _fixedStructModuleBuilder.DefineType($"IL2CPPDetour_FixedSizeStruct_{size}b", TypeAttributes.SequentialLayout | TypeAttributes.BeforeFieldInit | TypeAttributes.Sealed, typeof(ValueType)); + tb.DefineField("_element0", typeof(byte), FieldAttributes.Private); + + // Apply InlineArray attribute + var data = new byte[8]; + data[0] = 1; + BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(2), size); + tb.SetCustomAttribute(typeof(InlineArrayAttribute).GetConstructors()[0], data); + + var type = tb.CreateType(); + return _fixedStructCache[size] = type; + } + + public static Type GetNativeType(Type managedType) + { + if (managedType == typeof(void)) + { + return managedType; + } + else if (!managedType.IsValueType) + { + if (managedType.IsByRef) + { + throw new NotSupportedException("ByRef types are not supported in NativeType conversion."); + } + else if (managedType.IsArray || managedType.IsSZArray) + { + throw new NotSupportedException("Array types are not supported in NativeType conversion."); + } + + // General reference type + return typeof(IntPtr); + } + else if (managedType == typeof(Il2CppSystem.Boolean)) + { + // bool is byte in Il2Cpp, but int in CLR => force size to be correct + return typeof(byte); + } + else if (typeof(IByReference).IsAssignableFrom(managedType)) + { + // ByReference types have no class, so we need this marker interface to identify them. + return typeof(IntPtr); + } + else if (typeof(IPointer).IsAssignableFrom(managedType)) + { + return typeof(IntPtr); + } + else + { + // Struct that's passed on the stack => handle as general struct + + var nativeClassPtr = Il2CppType.GetClassPointer(managedType); + if (nativeClassPtr == IntPtr.Zero) + { + throw new NotSupportedException($"Type {managedType.FullName} is not an Il2Cpp type."); + } + + var fixedSize = IL2CPP.il2cpp_class_value_size(nativeClassPtr, out _); + return GetFixedSizeStructType(fixedSize); + } + } + + [RequiresUnreferencedCode("")] + public static Type GetOrCreateDelegateType(MethodInfo monoMethod) + { + return ourDelegateTypes.GetOrAdd(new TrampolineSignatureHash(monoMethod), CreateDelegateType, monoMethod); + } + + [RequiresUnreferencedCode("")] + private static Type CreateDelegateType(TrampolineSignatureHash signatureHash, MethodInfo monoMethod) + { + var typeName = $"Il2CppToManagedDelegate_{signatureHash}"; + + var newType = _delegateModule.DefineType(typeName, TypeAttributes.Sealed | TypeAttributes.Public, + typeof(MulticastDelegate)); + + newType.DefineConstructor( + MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | + MethodAttributes.Public, CallingConventions.HasThis, [typeof(object), typeof(IntPtr)]) + .SetImplementationFlags(MethodImplAttributes.CodeTypeMask); + + var parameterTypes = GetNativeParameters(monoMethod); + + newType.DefineMethod("Invoke", + MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public, + CallingConventions.HasThis, + GetNativeType(monoMethod.ReturnType), + parameterTypes) + .SetImplementationFlags(MethodImplAttributes.CodeTypeMask); + + newType.DefineMethod("BeginInvoke", + MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | + MethodAttributes.Public, + CallingConventions.HasThis, typeof(IAsyncResult), + parameterTypes.Concat([typeof(AsyncCallback), typeof(object)]).ToArray()) + .SetImplementationFlags(MethodImplAttributes.CodeTypeMask); + + newType.DefineMethod("EndInvoke", + MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public, + CallingConventions.HasThis, + GetNativeType(monoMethod.ReturnType), + [typeof(IAsyncResult)]) + .SetImplementationFlags(MethodImplAttributes.CodeTypeMask); + + return newType.CreateType(); + } + + private static Type[] GetNativeParameters(MethodInfo monoMethod) => + [ + ..(ReadOnlySpan)(monoMethod.IsStatic ? [] : [typeof(IntPtr)]), + ..monoMethod.GetParameters().Select(it => GetNativeType(it.ParameterType)), + typeof(Il2CppMethodInfo*), + ]; + + [RequiresUnreferencedCode("")] + internal static Delegate CreateTrampoline(MethodInfo monoMethod, bool callVirt) + { + Debug.Assert(monoMethod.DeclaringType is not null); + + var nativeReturnType = GetNativeType(monoMethod.ReturnType); + var nativeParameterTypes = GetNativeParameters(monoMethod); + + Type[] managedParameters = + [ + ..(ReadOnlySpan)(monoMethod.IsStatic ? [] : [monoMethod.DeclaringType!]), + ..monoMethod.GetParameters().Select(it => it.ParameterType), + ]; + + var trampoline = new DynamicMethod( + $"Trampoline_{monoMethod.DeclaringType}_{monoMethod.Name}_{new NamedSignatureHash(monoMethod)}{(callVirt ? "_Virtual" : "")}", + MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, + nativeReturnType, nativeParameterTypes, + typeof(TypeInjector), true); + + var delegateType = GetOrCreateDelegateType(monoMethod); + + var body = trampoline.GetILGenerator(); + + body.BeginExceptionBlock(); + + // Value types boxed as interfaces can be modified during the execution of the method. + List<(LocalBuilder Local, int ArgumentIndex)> interfaceArguments = []; + + LocalBuilder? thisVariable = null; + if (!monoMethod.IsStatic) + { + body.Emit(OpCodes.Ldarg_0); + if (monoMethod.DeclaringType.IsValueType) + { + // Need to store the value in a local variable so that we can pass a reference to the managed method + thisVariable = body.DeclareLocal(monoMethod.DeclaringType); + body.Emit(OpCodes.Conv_U); + body.Emit(OpCodes.Call, typeof(Il2CppType).GetMethod(nameof(Il2CppType.ReadFromPointer), BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(monoMethod.DeclaringType)); + body.Emit(OpCodes.Stloc, thisVariable); + body.Emit(OpCodes.Ldloca, thisVariable); + } + else + { + body.Emit(OpCodes.Call, typeof(Il2CppObjectPool).GetMethod(nameof(Il2CppObjectPool.Get))!); + body.Emit(OpCodes.Castclass, monoMethod.DeclaringType); + + if (monoMethod.DeclaringType.IsInterface) + { + var local = body.DeclareLocal(monoMethod.DeclaringType); + body.Emit(OpCodes.Stloc, local); + body.Emit(OpCodes.Ldloc, local); + interfaceArguments.Add((local, 0)); + } + } + } + + var argOffset = monoMethod.IsStatic ? 0 : 1; + + for (var i = argOffset; i < managedParameters.Length; i++) + { + body.Emit(OpCodes.Ldarg, i); + body.Emit(OpCodes.Call, typeof(TypeInjector).GetMethod(nameof(ConvertNativeToManaged), BindingFlags.Static | BindingFlags.NonPublic)!.MakeGenericMethod(nativeParameterTypes[i], managedParameters[i])); + if (managedParameters[i].IsInterface) + { + var local = body.DeclareLocal(managedParameters[i]); + body.Emit(OpCodes.Stloc, local); + body.Emit(OpCodes.Ldloc, local); + interfaceArguments.Add((local, i)); + } + } + + body.Emit(callVirt ? OpCodes.Callvirt : OpCodes.Call, monoMethod); + LocalBuilder? nativeReturnVariable = null; + if (monoMethod.ReturnType != typeof(void)) + { + nativeReturnVariable = body.DeclareLocal(nativeReturnType); + body.Emit(OpCodes.Call, typeof(TypeInjector).GetMethod(nameof(ConvertManagedToNative), BindingFlags.Static | BindingFlags.NonPublic)!.MakeGenericMethod(monoMethod.ReturnType, nativeReturnType)); + body.Emit(OpCodes.Stloc, nativeReturnVariable); + } + + if (thisVariable != null) + { + // Copy any changes to the value type back to the pointer passed by il2cpp + Debug.Assert(monoMethod.DeclaringType.IsValueType); + body.Emit(OpCodes.Ldloc, thisVariable); + body.Emit(OpCodes.Ldarg_0); + body.Emit(OpCodes.Conv_U); + body.Emit(OpCodes.Call, typeof(Il2CppType).GetMethod(nameof(Il2CppType.WriteToPointer), BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(monoMethod.DeclaringType)); + } + + // Update boxed value types passed as interfaces + foreach ((var local, var argumentIndex) in interfaceArguments) + { + body.Emit(OpCodes.Ldarg, argumentIndex); + body.Emit(OpCodes.Ldloc, local); + body.Emit(OpCodes.Call, typeof(TypeInjector).GetMethod(nameof(UpdateBoxedValue), BindingFlags.Static | BindingFlags.NonPublic)!); + } + + body.BeginCatchBlock(typeof(Exception)); + body.Emit(OpCodes.Call, typeof(TypeInjector).GetMethod(nameof(LogError), BindingFlags.Static | BindingFlags.NonPublic)!); + + body.EndExceptionBlock(); + + if (nativeReturnVariable != null) + { + body.Emit(OpCodes.Ldloc, nativeReturnVariable); + } + + body.Emit(OpCodes.Ret); + + return trampoline.CreateDelegate(delegateType); + } + + private static void LogError(Exception exception) + { + Logger.Instance.LogError("Exception in IL2CPP-to-Managed trampoline, not passing it to il2cpp: {Exception}", exception); + } + + private static unsafe void UpdateBoxedValue(IntPtr objectPointer, IIl2CppType? @object) + { + if (objectPointer == IntPtr.Zero || @object == null) + return; + + if (!@object.GetType().IsValueType) + return; + + var size = IL2CPP.il2cpp_class_value_size(@object.ObjectClass, out _); + var sourceSpan = new ReadOnlySpan((void*)IL2CPP.il2cpp_object_unbox((nint)@object.BoxNative()), size); + var destinationSpan = new Span((void*)IL2CPP.il2cpp_object_unbox(objectPointer), size); + sourceSpan.CopyTo(destinationSpan); + } + + private static unsafe TManaged? ConvertNativeToManaged(TNative value) + where TNative : unmanaged + where TManaged : IIl2CppType + { + var span = new ReadOnlySpan(&value, sizeof(TNative)); + return TManaged.ReadFromSpan(span); + } + + private static unsafe TNative ConvertManagedToNative(TManaged? value) + where TNative : unmanaged + where TManaged : IIl2CppType + { + TNative result = default; + var span = new Span(&result, sizeof(TNative)); + TManaged.WriteToSpan(value, span); + return result; + } +} diff --git a/Il2CppInterop.Runtime/Injection/TrampolineHelpers.cs b/Il2CppInterop.Runtime/Injection/TrampolineHelpers.cs deleted file mode 100644 index e2526e8c..00000000 --- a/Il2CppInterop.Runtime/Injection/TrampolineHelpers.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -using Il2CppInterop.Runtime.InteropTypes; - -namespace Il2CppInterop.Runtime.Injection; - -internal static class TrampolineHelpers -{ - private static AssemblyBuilder _fixedStructAssembly; - private static ModuleBuilder _fixedStructModuleBuilder; - private static readonly Dictionary _fixedStructCache = new(); - - private static Type GetFixedSizeStructType(int size) - { - if (_fixedStructCache.TryGetValue(size, out var result)) - { - return result; - } - - _fixedStructAssembly ??= AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("FixedSizeStructAssembly"), AssemblyBuilderAccess.Run); - _fixedStructModuleBuilder ??= _fixedStructAssembly.DefineDynamicModule("FixedSizeStructAssembly"); - - var tb = _fixedStructModuleBuilder.DefineType($"IL2CPPDetour_FixedSizeStruct_{size}b", TypeAttributes.ExplicitLayout, typeof(ValueType), size); - - var type = tb.CreateType(); - return _fixedStructCache[size] = type; - } - - internal static Type NativeType(this Type managedType) - { - if (managedType.IsByRef) - { - var directType = managedType.GetElementType(); - - // bool is byte in Il2Cpp, but int in CLR => force size to be correct - if (directType == typeof(bool)) - { - return typeof(byte).MakeByRefType(); - } - - if (directType == typeof(string) || directType.IsSubclassOf(typeof(Il2CppObjectBase))) - { - return typeof(IntPtr*); - } - } - else if (managedType.IsSubclassOf(typeof(Il2CppSystem.ValueType)) && !Environment.Is64BitProcess) - { - // Struct that's passed on the stack => handle as general struct - uint align = 0; - var fixedSize = IL2CPP.il2cpp_class_value_size(Il2CppClassPointerStore.GetNativeClassPointer(managedType), ref align); - return GetFixedSizeStructType(fixedSize); - } - else if (managedType == typeof(string) || managedType.IsSubclassOf(typeof(Il2CppObjectBase))) // General reference type - { - return typeof(IntPtr); - } - else if (managedType == typeof(bool)) - { - // bool is byte in Il2Cpp, but int in CLR => force size to be correct - return typeof(byte); - } - - return managedType; - } -} diff --git a/Il2CppInterop.Runtime/Injection/TrampolineSignatureHash.cs b/Il2CppInterop.Runtime/Injection/TrampolineSignatureHash.cs new file mode 100644 index 00000000..ce76adfb --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/TrampolineSignatureHash.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO.Hashing; +using System.Reflection; +using Il2CppInterop.Runtime.Extensions; + +namespace Il2CppInterop.Runtime.Injection; + +internal readonly record struct TrampolineSignatureHash +{ + private readonly UInt128 _hash; + + [RequiresDynamicCode("")] + public TrampolineSignatureHash(MethodInfo methodInfo) + { + XxHash128 hash = new(); + hash.Append(methodInfo.IsStatic); + hash.Append(TrampolineBuilder.GetNativeType(methodInfo.ReturnType).FullName); + foreach (var parameter in methodInfo.GetParameters()) + { + hash.Append(TrampolineBuilder.GetNativeType(parameter.ParameterType).FullName); + } + _hash = hash.GetCurrentHashAsUInt128(); + } + + public override string ToString() => _hash.ToString(); +} diff --git a/Il2CppInterop.Runtime/Injection/TypeInjector.cs b/Il2CppInterop.Runtime/Injection/TypeInjector.cs new file mode 100644 index 00000000..f92b1f5c --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/TypeInjector.cs @@ -0,0 +1,1394 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Runtime.Extensions; +using Il2CppInterop.Runtime.Injection.Hooks; +using Il2CppInterop.Runtime.Structs; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Class; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime.Injection; + +public static unsafe class TypeInjector +{ + private static readonly IntPtr value__Cached = Marshal.StringToCoTaskMemUTF8("value__"); + /// + /// fieldInfo : defaultValueBlob + /// + private static readonly ConcurrentDictionary s_DefaultValueOverrides = new(); + private static readonly ConcurrentDictionary<(Type type, FieldAttributes attrs), IntPtr> _injectedFieldTypes = new(); + private static readonly HashSet RegisteredTypes = new(); + private static readonly HashSet NeedsInitialized = new(); + private static readonly Dictionary NeedsVTableSet = new(); + private static readonly HashSet NeedsFieldsSet = new(); + private static readonly ConcurrentDictionary InvokerCache = new(); + + /// + /// If true, this type is part of the game and not something we are trying to inject nor something that has already been injected by us. + /// + /// The type to check. + /// True if the type is preexisting, false otherwise. + [RequiresDynamicCode("")] + public static bool IsPreexistingType(Type type) + { + return Il2CppType.GetClassPointer(type) is not 0 && !RegisteredTypes.Contains(type); + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public static void RegisterTypeInIl2Cpp() where T : IIl2CppType + { + RegisterTypeInIl2Cpp(typeof(T)); + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static void RegisterTypeInIl2Cpp(Type type) + { + if (Il2CppType.GetClassPointer(type) is not 0) + { + // Already registered + return; + } + // The above call guarantees that either: + // * the static constructor is currently running and called this method. + // * the static constructor is malformed and does not call this method. + + ValidateTypeUsingReflection(type); + Hook.ApplyInjectionHooks(); + if (typeof(Il2CppSystem.IEnum).IsAssignableFrom(type)) + { + RegisterEnumInIl2Cpp(type); + return; + } + var vtableUpperBound = CalculateVTableUpperBoundUsingReflection(type); + var classPointer = UnityVersionHandler.NewClass(vtableUpperBound); + + // Initialize as much of the class pointer as possible without touching other types. + (var assemblyName, var @namespace, var name) = GetFullyQualifiedName(type); + classPointer.Image = AssemblyInjector.GetOrCreateImage(assemblyName).ImagePointer; + classPointer.Name = Marshal.StringToCoTaskMemUTF8(name); + classPointer.Namespace = Marshal.StringToCoTaskMemUTF8(@namespace); + classPointer.ElementClass = classPointer.Class = classPointer.CastClass = classPointer.ClassPointer; + + classPointer.NativeSize = -1; + classPointer.ActualSize = classPointer.InstanceSize = 0; + + classPointer.Initialized = true; + classPointer.InitializedAndNoError = true; + classPointer.SizeInited = false; + classPointer.HasFinalize = !type.IsValueType; + classPointer.IsVtableInitialized = false; + classPointer.ValueType = type.IsValueType; + + classPointer.ThisArg.Type = classPointer.ByValArg.Type = type.IsValueType ? Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE : Il2CppTypeEnum.IL2CPP_TYPE_CLASS; + classPointer.ThisArg.Data = classPointer.ByValArg.Data = (nint)TokenAllocator.Assign(classPointer.Pointer); + classPointer.ThisArg.ValueType = classPointer.ByValArg.ValueType = type.IsValueType; + classPointer.ThisArg.ByRef = true; + + classPointer.Flags = TypeAttributesToClassAttributes(type.Attributes); + + // This guarantees that any future calls to GetClassPointer will return the class pointer, + // even if the class is not fully registered yet, which allows us to handle circular dependencies between types. + RegisteredTypes.Add(type); + NeedsInitialized.Add(type); + NeedsVTableSet.Add(type, vtableUpperBound); + NeedsFieldsSet.Add(type); + Il2CppType.SetClassPointer(type, (nint)classPointer.ClassPointer); + Class_FromName_Hook.AddTypeToLookup(assemblyName, @namespace, name, (nint)classPointer.ClassPointer); + + // Ensure that all other types that this type depends on are at least registered + { + if (type is not { BaseType: null } and not { IsValueType: true }) + { + EnsureNativeClassPointerNotNull(type.BaseType); + } + + foreach (var interfaceType in type.GetInterfaces().Where(IsIl2CppInterface)) + { + EnsureNativeClassPointerNotNull(interfaceType); + } + + foreach (var (fieldType, _, _) in GetIl2CppFields(type)) + { + EnsureNativeClassPointerNotNull(fieldType); + } + + foreach (var property in GetIl2CppProperties(type)) + { + EnsureNativeClassPointerNotNull(property.PropertyType); + } + + foreach (var method in GetIl2CppMethods(type)) + { + foreach (var methodType in GetMethodTypes(method)) + { + if (methodType.ContainsGenericParameters) + continue; + EnsureNativeClassPointerNotNull(methodType); + } + } + } + + if (NeedsInitialized.Contains(type)) + { + SetClassData(type, classPointer); + } + + if (NeedsVTableSet.ContainsKey(type)) + { + SetVTableAndInterfaces(type, classPointer, vtableUpperBound); + } + + if (NeedsFieldsSet.Contains(type)) + { + SetFields(type, classPointer); + } + + static void EnsureNativeClassPointerNotNull(Type type) => GetClassPointerNotNull(type); + + static nint GetClassPointerNotNull(Type type) + { + var classPointer = Il2CppType.GetClassPointer(type); + if (classPointer is 0) + { + Logger.Instance.LogWarning("The static constructor of {Type} is malformed and did not call RegisterTypeInIl2Cpp. Registering it now.", type.FullName); + RegisterTypeInIl2Cpp(type); + classPointer = Il2CppType.GetClassPointer(type); + Debug.Assert(classPointer is not 0); + } + else if (!RegisteredTypes.Contains(type)) + { + // Ensure that the vtable is initialized for this preexisting type + ClassInitializer.Invoke((Il2CppClass*)classPointer); + } + return classPointer; + } + + static void SetClassData(Type type, INativeClassStruct classPointer) + { + if (type.BaseType is not null && NeedsInitialized.Contains(type.BaseType)) + { + SetClassData(type.BaseType, UnityVersionHandler.Wrap((Il2CppClass*)GetClassPointerNotNull(type.BaseType))); + } + + var baseClassPointer = type switch + { + { BaseType: null } => null, + { IsValueType: true } => UnityVersionHandler.Wrap((Il2CppClass*)Il2CppType.GetClassPointer()), + _ => UnityVersionHandler.Wrap((Il2CppClass*)GetClassPointerNotNull(type.BaseType)), + }; + + // Static classes get unsealed during generation so that they can be used as generic parameters, which might mislead users into thinking they can inherit from them. + // This native check can be removed with changes to the generation, such as adding an attribute to indicate that a class is static and moving this check to the reflection validation. + if (baseClassPointer is not null && (baseClassPointer.Flags & Il2CppClassAttributes.TYPE_ATTRIBUTE_SEALED) != 0) + throw new ArgumentException($"Base class {type.BaseType} is sealed, so {type} can't inherit from it"); + + // Initialize the rest of the class pointer now that it's visible to other types. + classPointer.Parent = baseClassPointer?.ClassPointer; + if (baseClassPointer is not null) + { + classPointer.ActualSize = classPointer.InstanceSize = baseClassPointer.InstanceSize; + classPointer.SizeInited = true; + } + + //var properties = GetIl2CppProperties(type).ToArray(); + + var methods = GetIl2CppMethods(type).ToArray(); + var methodsOffset = type.IsInterface ? 0 : 1; // empty ctor + var methodCount = methodsOffset + methods.Length; + + classPointer.MethodCount = (ushort)methodCount; + var methodPointerArray = (Il2CppMethodInfo**)Marshal.AllocHGlobal(methodCount * IntPtr.Size); + classPointer.Methods = methodPointerArray; + + if (!type.IsInterface) + { + methodPointerArray[0] = CreateEmptyCtor(classPointer); + } + + for (var i = 0; i < methods.Length; i++) + { + var methodInfo = methods[i]; + methodPointerArray[i + methodsOffset] = ConvertMethodInfo(methodInfo, classPointer); + } + + NeedsInitialized.Remove(type); + } + + static void SetVTableAndInterfaces(Type type, INativeClassStruct classPointer, int vtableAllocatedSize) + { + if (NeedsInitialized.Contains(type)) + { + SetClassData(type, classPointer); + } + Type[] interfaceTypesNotImplementedByBaseType; + INativeClassStruct? baseClassPointer; + Type? baseType; + if (type is { BaseType: null }) + { + baseClassPointer = null; + baseType = null; + interfaceTypesNotImplementedByBaseType = type.GetInterfaces().Where(IsIl2CppInterface).ToArray(); + } + else + { + baseType = type.IsValueType ? typeof(Il2CppSystem.ValueType) : type.BaseType; + interfaceTypesNotImplementedByBaseType = type.GetInterfaces().Where(IsIl2CppInterface).Where(i => !i.IsAssignableFrom(baseType)).ToArray(); + baseClassPointer = UnityVersionHandler.Wrap((Il2CppClass*)GetClassPointerNotNull(baseType)); + if (NeedsVTableSet.TryGetValue(baseType, out var baseTypeVTableMaxSize)) + { + SetVTableAndInterfaces(baseType, baseClassPointer, baseTypeVTableMaxSize); + } + } + foreach (var @interface in interfaceTypesNotImplementedByBaseType) + { + if (NeedsVTableSet.TryGetValue(@interface, out var interfaceVTableMaxSize)) + { + var interfaceClassPointer = UnityVersionHandler.Wrap((Il2CppClass*)GetClassPointerNotNull(@interface)); + SetVTableAndInterfaces(@interface, interfaceClassPointer, interfaceVTableMaxSize); + } + } + + if (baseClassPointer is null) + { + classPointer.InterfaceCount = (ushort)interfaceTypesNotImplementedByBaseType.Length; + classPointer.ImplementedInterfaces = (Il2CppClass**)Marshal.AllocHGlobal(interfaceTypesNotImplementedByBaseType.Length * IntPtr.Size); + for (var i = 0; i < interfaceTypesNotImplementedByBaseType.Length; i++) + { + var interfaceType = interfaceTypesNotImplementedByBaseType[i]; + classPointer.ImplementedInterfaces[i] = (Il2CppClass*)GetClassPointerNotNull(interfaceType); + } + } + else + { + classPointer.InterfaceCount = (ushort)(interfaceTypesNotImplementedByBaseType.Length + baseClassPointer.InterfaceCount); + classPointer.ImplementedInterfaces = (Il2CppClass**)Marshal.AllocHGlobal(classPointer.InterfaceCount * IntPtr.Size); + Buffer.MemoryCopy(baseClassPointer.ImplementedInterfaces, classPointer.ImplementedInterfaces, classPointer.InterfaceCount * IntPtr.Size, baseClassPointer.InterfaceCount * IntPtr.Size); + for (var i = 0; i < interfaceTypesNotImplementedByBaseType.Length; i++) + { + var interfaceType = interfaceTypesNotImplementedByBaseType[i]; + classPointer.ImplementedInterfaces[baseClassPointer.InterfaceCount + i] = (Il2CppClass*)GetClassPointerNotNull(interfaceType); + } + } + + var pointersToInterfaces = type.GetInterfaces().Where(IsIl2CppInterface).ToDictionary(Il2CppType.GetClassPointer); + + var interfaceOffsets = new List(); + + var map1 = CreateMethodInfoToHashDictionary(type); + var map4 = CreateHashToNativeMethodInfoDictionary(classPointer); + + var index = 0; + if (baseClassPointer is not null) + { + Debug.Assert(baseType is not null); + + var baseMethodToMethodMap = GetIl2CppMethods(type).ToDictionary(m => m.GetBaseDefinition(), m => m); + + var baseMap2 = CreateHashToMethodInfoDictionary(baseType); + var baseMap3 = CreateNativeMethodInfoToHashDictionary(baseClassPointer); + + var lowestedInterfaceOffset = LowestInterfaceOffset(baseClassPointer); + for (; index < lowestedInterfaceOffset; index++) + { + ThrowIfNotEnoughAllocated(index, vtableAllocatedSize, type); + var vtableEntry = classPointer.VTable + index; + var baseVTableEntry = baseClassPointer.VTable + index; + var baseMethodInfo = baseMap2[baseMap3[(nint)baseVTableEntry->method]]; + if (baseMethodToMethodMap.TryGetValue(baseMethodInfo, out var methodInfo)) + { + var nativeMethodInfo = map4[map1[methodInfo]]; + vtableEntry->methodPtr = nativeMethodInfo.MethodPointer; + vtableEntry->method = nativeMethodInfo.MethodInfoPointer; + } + else + { + *vtableEntry = *baseVTableEntry; + } + } + + // Virtual methods declared in this class + foreach (var methodInfo in baseMethodToMethodMap.Values) + { + // If the method is not overridden, it will be a key in the dictionary. + if (!baseMethodToMethodMap.ContainsKey(methodInfo)) + continue; + + if (!methodInfo.IsAbstract && !methodInfo.IsVirtual) + continue; + + if (methodInfo.IsFinal) + continue; + + var nativeMethodInfo = map4[map1[methodInfo]]; + ThrowIfNotEnoughAllocated(index, vtableAllocatedSize, type); + classPointer.VTable[index] = new() + { + methodPtr = nativeMethodInfo.MethodPointer, + method = nativeMethodInfo.MethodInfoPointer + }; + index++; + } + + for (var interfaceIndex = 0; interfaceIndex < baseClassPointer.InterfaceOffsetsCount; interfaceIndex++) + { + var pair = baseClassPointer.InterfaceOffsets[interfaceIndex]; + var interfaceClassPointer = UnityVersionHandler.Wrap(pair.interfaceType); + var interfaceType = pointersToInterfaces[interfaceClassPointer.Pointer]; + var baseInterfaceOffset = pair.offset; + var interfaceOffset = index; + int interfaceVtableCount = interfaceClassPointer.VTableCount; + + interfaceOffsets.Add(new Il2CppRuntimeInterfaceOffsetPair + { + interfaceType = pair.interfaceType, + offset = interfaceOffset + }); + + Dictionary interfaceMethodToImplementingMethod; + { + var interfaceMapStruct = type.GetInterfaceMap(interfaceType); + interfaceMethodToImplementingMethod = new Dictionary(interfaceMapStruct.InterfaceMethods.Length); + for (var i = 0; i < interfaceMapStruct.InterfaceMethods.Length; i++) + { + interfaceMethodToImplementingMethod.Add(interfaceMapStruct.InterfaceMethods[i], interfaceMapStruct.TargetMethods[i]); + } + } + + var interfaceMap2 = CreateHashToMethodInfoDictionary(interfaceType); + var interfaceMap3 = CreateNativeMethodInfoToHashDictionary(interfaceClassPointer); + + for (var i = 0; i < interfaceVtableCount; i++) + { + Debug.Assert(index == interfaceOffset + i); + ThrowIfNotEnoughAllocated(index, vtableAllocatedSize, type); + var vtableEntry = classPointer.VTable + interfaceOffset + i; + var interfaceVTableEntry = interfaceClassPointer.VTable + i; + var interfaceMethodInfo = interfaceMap2[interfaceMap3[(nint)interfaceVTableEntry->method]]; + if (interfaceMethodToImplementingMethod.TryGetValue(interfaceMethodInfo, out var methodInfo) && methodInfo.DeclaringType == type) + { + var nativeMethodInfo = map4[map1[methodInfo]]; + vtableEntry->methodPtr = nativeMethodInfo.MethodPointer; + vtableEntry->method = nativeMethodInfo.MethodInfoPointer; + } + else + { + var baseVTableEntry = baseClassPointer.VTable + baseInterfaceOffset + i; + *vtableEntry = *baseVTableEntry; + } + index++; + } + } + } + + foreach (var interfaceType in interfaceTypesNotImplementedByBaseType) + { + var interfaceClassPointer = UnityVersionHandler.Wrap((Il2CppClass*)GetClassPointerNotNull(interfaceType)); + var interfaceOffset = index; + var interfaceVtableCount = LowestInterfaceOffset(interfaceClassPointer); // Not sure this is correct + + if (interfaceVtableCount == 0) + continue; + + interfaceOffsets.Add(new Il2CppRuntimeInterfaceOffsetPair + { + interfaceType = interfaceClassPointer.ClassPointer, + offset = interfaceOffset + }); + + Dictionary interfaceMethodToImplementingMethod; + { + var interfaceMapStruct = type.GetInterfaceMap(interfaceType); + interfaceMethodToImplementingMethod = new Dictionary(interfaceMapStruct.InterfaceMethods.Length); + for (var i = 0; i < interfaceMapStruct.InterfaceMethods.Length; i++) + { + interfaceMethodToImplementingMethod.Add(interfaceMapStruct.InterfaceMethods[i], interfaceMapStruct.TargetMethods[i]); + } + } + + var interfaceMap2 = CreateHashToMethodInfoDictionary(interfaceType); + var interfaceMap3 = CreateNativeMethodInfoToHashDictionary(interfaceClassPointer); + + for (var i = 0; i < interfaceVtableCount; i++) + { + Debug.Assert(index == interfaceOffset + i); + ThrowIfNotEnoughAllocated(index, vtableAllocatedSize, type); + var vtableEntry = classPointer.VTable + interfaceOffset + i; + var interfaceVTableEntry = interfaceClassPointer.VTable + i; + var interfaceMethodInfo = interfaceMap2[interfaceMap3[(nint)interfaceVTableEntry->method]]; + var methodInfo = interfaceMethodToImplementingMethod[interfaceMethodInfo]; + var nativeMethodInfo = map4[map1[methodInfo]]; + vtableEntry->methodPtr = nativeMethodInfo.MethodPointer; + vtableEntry->method = nativeMethodInfo.MethodInfoPointer; + index++; + } + } + + classPointer.InterfaceOffsetsCount = (ushort)interfaceOffsets.Count; + classPointer.InterfaceOffsets = (Il2CppRuntimeInterfaceOffsetPair*)Marshal.AllocHGlobal(interfaceOffsets.Count * sizeof(Il2CppRuntimeInterfaceOffsetPair)); + for (var i = 0; i < interfaceOffsets.Count; i++) + { + classPointer.InterfaceOffsets[i] = interfaceOffsets[i]; + } + + classPointer.IsVtableInitialized = true; + NeedsVTableSet.Remove(type); + + static void ThrowIfNotEnoughAllocated(int index, int allocatedSize, Type type) + { + if (index >= allocatedSize) + throw new InvalidOperationException($"Not enough vtable space allocated for type {type}. Allocated: {allocatedSize}"); + } + } + + static void SetFields(Type type, INativeClassStruct classPointer) + { + if (NeedsInitialized.Contains(type)) + { + SetClassData(type, classPointer); + } + foreach (var fieldType in GetIl2CppInstanceFieldTypes(type)) + { + if (fieldType.IsValueType && NeedsFieldsSet.Contains(fieldType)) + { + SetFields(fieldType, UnityVersionHandler.Wrap((Il2CppClass*)GetClassPointerNotNull(fieldType))); + } + } + + if (type.BaseType is not null && NeedsFieldsSet.Contains(type.BaseType)) + { + SetFields(type.BaseType, UnityVersionHandler.Wrap((Il2CppClass*)GetClassPointerNotNull(type.BaseType))); + } + + var fieldsToInject = GetIl2CppFields(type).ToArray(); + classPointer.FieldCount = (ushort)fieldsToInject.Length; + + var il2cppFields = (Il2CppFieldInfo*)Marshal.AllocHGlobal(classPointer.FieldCount * UnityVersionHandler.FieldInfoSize()); + var fieldOffset = (int)classPointer.InstanceSize; + for (var i = 0; i < classPointer.FieldCount; i++) + { + var (fieldType, fieldAttributes, fieldName) = fieldsToInject[i]; + + var fieldInfoClass = Il2CppType.GetClassPointer(fieldType); + if (fieldInfoClass == IntPtr.Zero) + throw new Exception($"Type {fieldType} in {type}.{fieldName} doesn't exist in Il2Cpp"); + if (!_injectedFieldTypes.TryGetValue((fieldType, fieldAttributes), out var fieldTypePtr)) + { + var classType = + UnityVersionHandler.Wrap((Il2CppTypeStruct*)IL2CPP.il2cpp_class_get_type(fieldInfoClass)); + + var duplicatedType = UnityVersionHandler.NewType(); + duplicatedType.Data = classType.Data; + duplicatedType.Attrs = (ushort)fieldAttributes; + duplicatedType.Type = classType.Type; + duplicatedType.ByRef = classType.ByRef; + duplicatedType.Pinned = classType.Pinned; + + _injectedFieldTypes[(fieldType, fieldAttributes)] = duplicatedType.Pointer; + fieldTypePtr = duplicatedType.Pointer; + } + + var fieldInfo = UnityVersionHandler.Wrap(il2cppFields + i * UnityVersionHandler.FieldInfoSize()); + fieldInfo.Name = Marshal.StringToCoTaskMemUTF8(fieldName); + fieldInfo.Parent = classPointer.ClassPointer; + fieldInfo.Type = (Il2CppTypeStruct*)fieldTypePtr; + + if (fieldAttributes.HasFlag(FieldAttributes.Static)) + { + fieldInfo.Offset = 0; + } + else + { + fieldInfo.Offset = fieldOffset; + if (IL2CPP.il2cpp_class_is_valuetype(fieldInfoClass)) + { + var fieldSize = IL2CPP.il2cpp_class_value_size(fieldInfoClass, out _); + fieldOffset += fieldSize; + } + else + { + fieldOffset += sizeof(Il2CppObject*); + } + } + } + + classPointer.Fields = il2cppFields; + + classPointer.InstanceSize = (uint)fieldOffset; + classPointer.ActualSize = classPointer.InstanceSize; + classPointer.SizeInited = true; + + NeedsFieldsSet.Remove(type); + } + } + + private static int LowestInterfaceOffset(INativeClassStruct classPointer) + { + int result = classPointer.VTableCount; + for (var i = classPointer.InterfaceOffsetsCount - 1; i >= 0; i--) + { + var offset = (classPointer.InterfaceOffsets + i)->offset; + if (offset < result) + result = offset; + } + return result; + } + + [RequiresDynamicCode("")] + private static Dictionary CreateHashToMethodInfoDictionary([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type type) + { + return GetAllIl2CppMethods(type).ToDictionary(m => new NamedSignatureHash(m)); + } + + [RequiresDynamicCode("")] + private static Dictionary CreateMethodInfoToHashDictionary([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type type) + { + return GetAllIl2CppMethods(type).ToDictionary(m => m, m => new NamedSignatureHash(m)); + } + + private static Dictionary CreateHashToNativeMethodInfoDictionary(INativeClassStruct classPointer) + { + var dict = new Dictionary(classPointer.MethodCount); + var currentClassPointer = classPointer; + while (true) + { + for (var i = 0; i < currentClassPointer.MethodCount; i++) + { + var methodInfo = UnityVersionHandler.Wrap(currentClassPointer.Methods[i]); + dict.TryAdd(new NamedSignatureHash(methodInfo), methodInfo); + } + if (currentClassPointer.Parent is null) + break; + currentClassPointer = UnityVersionHandler.Wrap(currentClassPointer.Parent); + } + return dict; + } + + private static Dictionary CreateNativeMethodInfoToHashDictionary(INativeClassStruct classPointer) + { + var dict = new Dictionary(classPointer.MethodCount); + var currentClassPointer = classPointer; + while (true) + { + for (var i = 0; i < currentClassPointer.MethodCount; i++) + { + var methodInfo = UnityVersionHandler.Wrap(currentClassPointer.Methods[i]); + dict.Add(methodInfo.Pointer, new NamedSignatureHash(methodInfo)); + } + if (currentClassPointer.Parent is null) + break; + currentClassPointer = UnityVersionHandler.Wrap(currentClassPointer.Parent); + } + return dict; + } + + private static IEnumerable GetAllIl2CppMethods([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type type) + { + return type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Where(IsIl2CppMethod); + } + + private static IEnumerable GetIl2CppMethods([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type) + { + return type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(IsIl2CppMethod); + } + + private static IEnumerable GetIl2CppProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type) + { + return type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(IsIl2CppProperty); + } + + private static IEnumerable<(Type FieldType, FieldAttributes Attributes, string Name)> GetIl2CppFields([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type type) + { + List<(Type, FieldAttributes, string)> fields = new(); + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) + { + if (IsIl2CppField(property)) + { + var fieldAttribute = property.GetCustomAttribute(); + var fieldName = fieldAttribute?.Name ?? property.Name; + var fieldType = property.PropertyType; + FieldAttributes fieldAttributes = default; + if (property.IsStatic()) + { + fieldAttributes |= FieldAttributes.Static; + } + if (fieldName.EndsWith(">k__BackingField", StringComparison.Ordinal)) + { + fieldAttributes |= FieldAttributes.SpecialName; + fieldAttributes |= FieldAttributes.Private; + } + else + { + fieldAttributes |= FieldAttributes.Public; + } + fields.Add((fieldType, fieldAttributes, fieldName)); + } + } + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + if (IsIl2CppField(field)) + { + var fieldAttribute = field.GetCustomAttribute(); + var fieldName = fieldAttribute?.Name ?? field.Name; + var fieldType = field.FieldType; + var fieldAttributes = field.Attributes; + fields.Add((fieldType, fieldAttributes, fieldName)); + } + } + return fields; + } + + private static IEnumerable GetIl2CppInstanceFieldTypes([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type type) + { + return GetIl2CppFields(type).Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).Select(f => f.FieldType); + } + + [RequiresDynamicCode("Calls System.Type.MakeGenericType(params Type[])")] + private static void ValidateTypeUsingReflection([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type type) + { + // Enums are transformed into structs during generation + if (type.IsEnum) + throw new ArgumentException("Enums must be written as structs to be registered in Il2Cpp. Use the IEnum interface to mark them as enums.", nameof(type)); + + // Injected types cannot be generic + if (type.IsGenericType) + throw new ArgumentException("Generic types cannot be registered in Il2Cpp.", nameof(type)); + + if (type.DeclaringType is not null) + throw new ArgumentException("Nested types cannot be registered in Il2Cpp.", nameof(type)); + + // Types must inherit from IIl2CppType + if (!typeof(IIl2CppType).IsAssignableFrom(type)) + throw new ArgumentException("Types must implement IIl2CppType to be registered in Il2Cpp.", nameof(type)); + + if (type.IsValueType) + { + // Value types must inherit from IValueType + if (!typeof(Il2CppSystem.IValueType).IsAssignableFrom(type)) + throw new ArgumentException("Value types must implement IValueType to be registered in Il2Cpp.", nameof(type)); + + // Enums must have a single instance field named "value__" that represents the underlying value of the enum + if (typeof(Il2CppSystem.IEnum).IsAssignableFrom(type)) + { + var instanceFields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (instanceFields.Length != 1) + throw new ArgumentException("Enums must have a single instance field that represents the underlying value of the enum to be registered in Il2Cpp.", nameof(type)); + + var instanceField = instanceFields[0]; + if (instanceField.Name != "value__") + throw new ArgumentException("Enums must have a single instance field named \"value__\" to be registered in Il2Cpp.", nameof(type)); + } + } + else if (type.IsClass) + { + // Class types must inherit from Il2CppSystem.Object + if (!typeof(Il2CppSystem.Object).IsAssignableFrom(type)) + throw new ArgumentException("Class types must inherit from Il2CppSystem.Object to be registered in Il2Cpp.", nameof(type)); + + if (typeof(Il2CppSystem.IValueType).IsAssignableFrom(type)) + throw new ArgumentException("Class types cannot implement IValueType to be registered in Il2Cpp.", nameof(type)); + + Debug.Assert(type.BaseType is not null); + + // The base type of class types cannot be generic + if (type.BaseType.IsGenericType) + throw new ArgumentException("The base type of class types cannot be generic to be registered in Il2Cpp.", nameof(type)); + + // The type must have a constructor that takes a ObjectPointer parameter + if (type.GetConstructor([typeof(ObjectPointer)]) is null) + throw new ArgumentException("Class types must have a constructor that takes an ObjectPointer parameter to be registered in Il2Cpp.", nameof(type)); + } + + // Types must inherit from IIl2CppType where T is the type itself + try + { + // This will throw if the type does not implement IIl2CppType where T is the type itself + // because T has a self-referential constraint that cannot be satisfied if the type does not implement the interface correctly. + // If https://github.com/dotnet/runtime/issues/28033 is implemented, we can replace this with a more direct check. + typeof(IIl2CppType<>).MakeGenericType(type); + } + catch + { + throw new ArgumentException("Types must implement IIl2CppType where T is the type itself to be registered in Il2Cpp.", nameof(type)); + } + } + + [RequiresUnreferencedCode("")] + private static int CalculateVTableUpperBoundUsingReflection([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) + { + var count = 0; + foreach (var interfaceType in type.GetInterfaces().Where(IsIl2CppInterface)) + { + count += CountIl2CppMethods(interfaceType); + } + var currentType = type; + while (currentType is not null) + { + count += CountIl2CppMethods(currentType); + currentType = currentType.BaseType; + } + if (type.IsValueType) + { + count += UnityVersionHandler.Wrap((Il2CppClass*)Il2CppType.GetClassPointer()).VTableCount; + } + return int.Min(count, ushort.MaxValue); + + static int CountIl2CppMethods([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type) + { + return type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Count(IsIl2CppMethod); + } + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public static void InjectEnumValues(Dictionary valuesToAdd) where TEnum : Il2CppSystem.IEnum + { + InjectEnumValues(typeof(TEnum), valuesToAdd); + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public static void InjectEnumValues(Type type, Dictionary valuesToAdd) + { + ArgumentNullException.ThrowIfNull(type); + + if (!typeof(Il2CppSystem.IEnum).IsAssignableFrom(type)) + throw new ArgumentException("Type argument needs to be an enum", nameof(type)); + + var enumPtr = Il2CppType.GetClassPointer(type); + if (enumPtr == IntPtr.Zero) + throw new ArgumentException("Type needs to be an Il2Cpp enum", nameof(type)); + + Hook.ApplyInjectionHooks(); + + ClassInitializer.Invoke((Il2CppClass*)enumPtr); + + var il2cppEnum = UnityVersionHandler.Wrap((Il2CppClass*)enumPtr); + var newFieldCount = il2cppEnum.FieldCount + valuesToAdd.Count; + var newFields = (Il2CppFieldInfo*)Marshal.AllocHGlobal(newFieldCount * UnityVersionHandler.FieldInfoSize()); + + int fieldIdx; + for (fieldIdx = 0; fieldIdx < il2cppEnum.FieldCount; ++fieldIdx) + { + var offset = fieldIdx * UnityVersionHandler.FieldInfoSize(); + var oldField = UnityVersionHandler.Wrap(il2cppEnum.Fields + offset); + var newField = UnityVersionHandler.Wrap(newFields + offset); + + newField.Name = oldField.Name; + newField.Type = oldField.Type; + newField.Parent = oldField.Parent; + newField.Offset = oldField.Offset; + + // Move the default value blob from the old field to the new one + if (s_DefaultValueOverrides.TryRemove((IntPtr)oldField.FieldInfoPointer, out var blob)) + s_DefaultValueOverrides[(IntPtr)newField.FieldInfoPointer] = blob; + } + + var enumElementType = UnityVersionHandler.Wrap(il2cppEnum.ElementClass).ByValArg; + + foreach (var newData in valuesToAdd) + { + var offset = fieldIdx * UnityVersionHandler.FieldInfoSize(); + var newField = UnityVersionHandler.Wrap(newFields + offset); + newField.Name = Marshal.StringToCoTaskMemUTF8(newData.Key); + newField.Type = enumElementType.TypePointer; + newField.Parent = il2cppEnum.ClassPointer; + newField.Offset = 0; + + CreateOrUpdateFieldDefaultValue(newField.FieldInfoPointer, enumElementType.TypePointer, newData.Value); + + ++fieldIdx; + } + + il2cppEnum.FieldCount = (ushort)newFieldCount; + il2cppEnum.Fields = newFields; + + if (TypeFromClassPointer(enumPtr, type.FullName) is Il2CppSystem.RuntimeType runtimeEnumType) + // The mono runtime caches the enum names and values the first time they are requested, so we reset this cache + runtimeEnumType.GenericCache = null; + + static Il2CppSystem.Type TypeFromClassPointer(nint classPointer, string? typeName) + { + if (classPointer == IntPtr.Zero) + { + throw new ArgumentException($"{typeName} does not have a corresponding IL2CPP class pointer"); + } + + var il2CppType = IL2CPP.il2cpp_class_get_type(classPointer); + if (il2CppType == IntPtr.Zero) + { + throw new ArgumentException($"{typeName} does not have a corresponding IL2CPP type pointer"); + } + + return Il2CppSystem.Type.FromTypePointer(il2CppType); + } + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static void RegisterEnumInIl2Cpp(Type type) + { + var baseEnum = UnityVersionHandler.Wrap((Il2CppClass*)Il2CppType.GetClassPointer()); + + ClassInitializer.Invoke(baseEnum.ClassPointer); + + var il2cppEnum = UnityVersionHandler.NewClass(baseEnum.VTableCount); + var elementClass = + UnityVersionHandler.Wrap( + (Il2CppClass*)Il2CppType.GetClassPointer(GetEnumUnderlyingType(type))); + + (var assemblyName, var @namespace, var name) = GetFullyQualifiedName(type); + il2cppEnum.Image = AssemblyInjector.GetOrCreateImage(assemblyName).ImagePointer; + il2cppEnum.Class = il2cppEnum.CastClass = il2cppEnum.ElementClass = elementClass.ClassPointer; + il2cppEnum.Parent = baseEnum.ClassPointer; + il2cppEnum.ActualSize = il2cppEnum.InstanceSize = + (uint)(baseEnum.InstanceSize + GetEnumElementSize(elementClass.ByValArg.Type)); + il2cppEnum.NativeSize = -1; + + il2cppEnum.ValueType = true; + il2cppEnum.EnumType = true; + il2cppEnum.Initialized = true; + il2cppEnum.InitializedAndNoError = true; + il2cppEnum.SizeInited = true; + il2cppEnum.HasFinalize = true; + il2cppEnum.IsVtableInitialized = true; + + il2cppEnum.Name = Marshal.StringToCoTaskMemUTF8(name); + il2cppEnum.Namespace = Marshal.StringToCoTaskMemUTF8(@namespace); + + il2cppEnum.ThisArg.Data = il2cppEnum.ByValArg.Data = (nint)TokenAllocator.Assign(il2cppEnum.Pointer); + + // Has to be IL2CPP_TYPE_VALUETYPE because IL2CPP_TYPE_ENUM isn't used + il2cppEnum.ThisArg.Type = il2cppEnum.ByValArg.Type = Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE; + + il2cppEnum.Flags = (Il2CppClassAttributes)type.Attributes; + + il2cppEnum.VTableCount = baseEnum.VTableCount; + var vtable = il2cppEnum.VTable; + var baseVTable = baseEnum.VTable; + for (var i = 0; i < baseEnum.VTableCount; i++) + vtable[i] = baseVTable[i]; + + var enumNamesAndValues = GetEnumNamesAndValues(type); + il2cppEnum.FieldCount = (ushort)(enumNamesAndValues.Length + 1); // value__ + + var il2cppFields = + (Il2CppFieldInfo*)Marshal.AllocHGlobal(il2cppEnum.FieldCount * UnityVersionHandler.FieldInfoSize()); + var valueField = UnityVersionHandler.Wrap(il2cppFields); + valueField.Name = value__Cached; + valueField.Parent = il2cppEnum.ClassPointer; + valueField.Offset = (int)baseEnum.InstanceSize; + + var enumValueType = UnityVersionHandler.NewType(); + enumValueType.Data = elementClass.ThisArg.Data; + enumValueType.Attrs = (ushort)(FieldAttributes.Private | FieldAttributes.Family | FieldAttributes.SpecialName | + FieldAttributes.RTSpecialName); + enumValueType.Type = elementClass.ThisArg.Type; + enumValueType.ByRef = elementClass.ThisArg.ByRef; + enumValueType.Pinned = elementClass.ThisArg.Pinned; + + valueField.Type = enumValueType.TypePointer; + + var enumConstType = UnityVersionHandler.NewType(); + enumConstType.Data = il2cppEnum.ThisArg.Data; + enumConstType.Attrs = (ushort)(FieldAttributes.Private | FieldAttributes.Family | FieldAttributes.InitOnly | + FieldAttributes.Literal | FieldAttributes.HasDefault); + enumConstType.Type = Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE; + enumConstType.ByRef = false; + enumConstType.Pinned = false; + + for (var i = 1; i < il2cppEnum.FieldCount; i++) + { + var (fieldName, fieldValue) = enumNamesAndValues[i - 1]; + var il2cppField = UnityVersionHandler.Wrap(il2cppFields + i * UnityVersionHandler.FieldInfoSize()); + il2cppField.Name = Marshal.StringToCoTaskMemUTF8(fieldName); + il2cppField.Type = enumConstType.TypePointer; + il2cppField.Parent = il2cppEnum.ClassPointer; + il2cppField.Offset = 0; + + CreateOrUpdateFieldDefaultValue(il2cppField.FieldInfoPointer, elementClass.ThisArg.TypePointer, fieldValue); + } + + il2cppEnum.Fields = il2cppFields; + + il2cppEnum.TypeHierarchyDepth = (byte)(1 + baseEnum.TypeHierarchyDepth); + il2cppEnum.TypeHierarchy = (Il2CppClass**)Marshal.AllocHGlobal(il2cppEnum.TypeHierarchyDepth * sizeof(void*)); + for (var i = 0; i < il2cppEnum.TypeHierarchyDepth; i++) + il2cppEnum.TypeHierarchy[i] = baseEnum.TypeHierarchy[i]; + il2cppEnum.TypeHierarchy[il2cppEnum.TypeHierarchyDepth - 1] = il2cppEnum.ClassPointer; + + Il2CppType.SetClassPointer(type, il2cppEnum.Pointer); + Class_FromName_Hook.AddTypeToLookup(assemblyName, @namespace, name, il2cppEnum.Pointer); + } + + private static bool IsIl2CppInterface(Type type) + { + return type.IsInterface + && typeof(Il2CppSystem.IObject).IsAssignableFrom(type) + && type != typeof(Il2CppSystem.IObject) + && type != typeof(Il2CppSystem.IValueType) + && type != typeof(Il2CppSystem.IEnum); + } + + private static bool IsIl2CppField(PropertyInfo property) + { + // Has Il2CppFieldAttribute + return property.GetCustomAttribute() is not null; + } + + private static bool IsIl2CppField(FieldInfo field) + { + // Has Il2CppFieldAttribute + return field.GetCustomAttribute() is not null; + } + + private static bool IsIl2CppProperty(PropertyInfo property) + { + // Has Il2CppPropertyAttribute + return property.GetCustomAttribute() is not null; + } + + private static bool IsIl2CppMethod(MethodInfo method) + { + // Has Il2CppMethodAttribute + return method.GetCustomAttribute() is not null && !method.ContainsGenericParameters && !method.IsConstructor; + } + + private static IEnumerable GetMethodTypes(MethodInfo method) + { + foreach (var parameter in method.GetParameters()) + { + yield return parameter.ParameterType; + } + if (method.ReturnType != typeof(void)) + { + yield return method.ReturnType; + } + } + + private static Il2CppMethodInfo* CreateEmptyCtor(INativeClassStruct declaringClass) + { + var converted = UnityVersionHandler.NewMethod(); + converted.Name = Marshal.StringToCoTaskMemUTF8(".ctor"); + converted.Class = declaringClass.ClassPointer; + + void* invoker; + if (UnityVersionHandler.IsMetadataV29OrHigher) + { + invoker = (delegate* unmanaged)&Invoker_MetadataV29; + } + else + { + invoker = (delegate* unmanaged)&Invoker; + } + + converted.InvokerMethod = (nint)invoker; + converted.MethodPointer = (nint)(delegate* unmanaged)&Method; + converted.Slot = ushort.MaxValue; + converted.ReturnType = (Il2CppTypeStruct*)IL2CPP.il2cpp_class_get_type(Il2CppType.GetClassPointer()); + + converted.Flags = Il2CppMethodFlags.METHOD_ATTRIBUTE_PUBLIC | + Il2CppMethodFlags.METHOD_ATTRIBUTE_HIDE_BY_SIG | + Il2CppMethodFlags.METHOD_ATTRIBUTE_SPECIAL_NAME | + Il2CppMethodFlags.METHOD_ATTRIBUTE_RT_SPECIAL_NAME; + + return converted.MethodInfoPointer; + + [UnmanagedCallersOnly] + static void Invoker_MetadataV29(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, IntPtr* args, IntPtr* returnValue) + { + if (returnValue != null) + *returnValue = IntPtr.Zero; + } + + [UnmanagedCallersOnly] + static IntPtr Invoker(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, IntPtr* args) + { + return IntPtr.Zero; + } + + [UnmanagedCallersOnly] + static void Method(IntPtr obj, Il2CppMethodInfo* methodInfo) + { + } + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static Il2CppMethodInfo* ConvertMethodInfo(MethodInfo monoMethod, INativeClassStruct declaringClass) + { + if (monoMethod.ContainsGenericParameters) + throw new ArgumentException("Generic methods cannot be converted.", nameof(monoMethod)); + + var converted = UnityVersionHandler.NewMethod(); + converted.Name = Marshal.StringToCoTaskMemUTF8(monoMethod.Name); + converted.Class = declaringClass.ClassPointer; + + var parameters = monoMethod.GetParameters(); + if (parameters.Length > 0) + { + converted.ParametersCount = (byte)parameters.Length; + var paramsArray = UnityVersionHandler.NewMethodParameterArray(parameters.Length); + converted.Parameters = paramsArray[0]; + for (var i = 0; i < parameters.Length; i++) + { + var parameterInfo = parameters[i]; + var param = UnityVersionHandler.Wrap(paramsArray[i]); + if (param.HasNamePosToken) + { + param.Name = Marshal.StringToCoTaskMemUTF8(parameterInfo.Name); + param.Position = i; + param.Token = 0; + } + + param.ParameterType = (Il2CppTypeStruct*)Il2CppTypePointerStore.GetNativeTypePointer(parameterInfo.ParameterType); + } + } + + if (!monoMethod.IsAbstract) + { + converted.InvokerMethod = Marshal.GetFunctionPointerForDelegate(GetOrCreateInvoker(monoMethod)); + converted.MethodPointer = Marshal.GetFunctionPointerForDelegate(CreateTrampoline(monoMethod, false)); + if (monoMethod.IsVirtual && !monoMethod.IsFinal) + { + converted.VirtualMethodPointer = Marshal.GetFunctionPointerForDelegate(CreateTrampoline(monoMethod, true)); + } + else + { + converted.VirtualMethodPointer = converted.MethodPointer; // Not certain if this should be null + } + } + + converted.Slot = ushort.MaxValue; + + converted.ReturnType = (Il2CppTypeStruct*)Il2CppTypePointerStore.GetNativeTypePointer(monoMethod.ReturnType); + + converted.Flags = MethodAttributesToMethodFlags(monoMethod.Attributes); + + return converted.MethodInfoPointer; + } + + [RequiresDynamicCode("")] + private static Delegate GetOrCreateInvoker(MethodInfo monoMethod) + { + return InvokerCache.GetOrAdd(new InvokerSignatureHash(monoMethod), + static (signatureHash, monoMethodInner) => CreateInvoker(signatureHash, monoMethodInner), monoMethod); + } + + [RequiresDynamicCode("")] + private static Delegate CreateInvoker(InvokerSignatureHash signatureHash, MethodInfo monoMethod) + { + Debug.Assert(monoMethod.DeclaringType is not null); + + DynamicMethod method; + if (UnityVersionHandler.IsMetadataV29OrHigher) + { + var parameterTypes = new[] { typeof(IntPtr), typeof(Il2CppMethodInfo*), typeof(IntPtr), typeof(IntPtr*), typeof(IntPtr*) }; + // Method pointer + // Method info pointer + // this pointer + // arguments pointer + // return value pointer (if not void) + + method = new DynamicMethod($"Invoker_{signatureHash}", + MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(void), + parameterTypes, typeof(TypeInjector), true); + } + else + { + var parameterTypes = new[] { typeof(IntPtr), typeof(Il2CppMethodInfo*), typeof(IntPtr), typeof(IntPtr*) }; + // Method pointer + // Method info pointer + // this pointer + // arguments pointer + + method = new DynamicMethod($"Invoker_{signatureHash}", + MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(IntPtr), + parameterTypes, typeof(TypeInjector), true); + } + + var body = method.GetILGenerator(); + + if (!monoMethod.IsStatic) + body.Emit(OpCodes.Ldarg_2); // obj + for (var i = 0; i < monoMethod.GetParameters().Length; i++) + { + var parameterInfo = monoMethod.GetParameters()[i]; + body.Emit(OpCodes.Ldarg_3); + body.Emit(OpCodes.Ldc_I4, i * IntPtr.Size); + body.Emit(OpCodes.Add_Ovf_Un); + var nativeType = TrampolineBuilder.GetNativeType(parameterInfo.ParameterType); + body.Emit(OpCodes.Ldobj, typeof(IntPtr)); + if (nativeType != typeof(IntPtr)) + body.Emit(OpCodes.Ldobj, nativeType); + } + + body.Emit(OpCodes.Ldarg_1); // methodMetadata + body.Emit(OpCodes.Ldarg_0); // methodPointer + + var nativeReturnType = TrampolineBuilder.GetNativeType(monoMethod.ReturnType); + Type[] nativeParameterTypes = + [ + ..(ReadOnlySpan)(monoMethod.IsStatic ? [] : [typeof(IntPtr)]), + ..monoMethod.GetParameters().Select(it => TrampolineBuilder.GetNativeType(it.ParameterType)), + typeof(Il2CppMethodInfo*), + ]; + body.EmitCalli(OpCodes.Calli, CallingConventions.Standard, nativeReturnType, nativeParameterTypes, null); + + if (UnityVersionHandler.IsMetadataV29OrHigher) + { + if (monoMethod.ReturnType != typeof(void)) + { + var returnValue = body.DeclareLocal(nativeReturnType); + body.Emit(OpCodes.Stloc, returnValue); + body.Emit(OpCodes.Ldarg_S, (byte)4); + body.Emit(OpCodes.Ldloc, returnValue); + body.Emit(OpCodes.Stobj, returnValue.LocalType); + } + } + else + { + if (monoMethod.ReturnType == typeof(void)) + { + // Return null for void methods + body.Emit(OpCodes.Ldc_I4_0); + body.Emit(OpCodes.Conv_I); + } + else if (monoMethod.ReturnType.IsValueType && nativeReturnType != typeof(IntPtr)) + { + // If managed return type is a value type and native return type is IntPtr, it means the managed return type is Pointer<> or ByReference<>. + // Those get returned as-is, so we don't want to box them. + var returnValue = body.DeclareLocal(nativeReturnType); + body.Emit(OpCodes.Stloc, returnValue); + var getClassPointer = typeof(Il2CppType) + .GetMethod(nameof(Il2CppType.GetClassPointer), 1, [])! + .MakeGenericMethod(monoMethod.ReturnType); + body.Emit(OpCodes.Call, getClassPointer); + body.Emit(OpCodes.Ldloca, returnValue); + body.Emit(OpCodes.Call, typeof(IL2CPP).GetMethod(nameof(IL2CPP.il2cpp_value_box))!); + } + } + + body.Emit(OpCodes.Ret); + + GCHandle.Alloc(method); + + var invokerDelegateType = UnityVersionHandler.IsMetadataV29OrHigher ? typeof(InvokerDelegateMetadataV29) : typeof(InvokerDelegate); + + var @delegate = method.CreateDelegate(invokerDelegateType); + GCHandle.Alloc(@delegate); + return @delegate; + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static Delegate CreateTrampoline(MethodInfo monoMethod, bool callVirt) + { + var @delegate = TrampolineBuilder.CreateTrampoline(monoMethod, callVirt); + GCHandle.Alloc(@delegate); // pin it forever + return @delegate; + } + + [RequiresDynamicCode("")] + private static (string AssemblyName, string Namespace, string Name) GetFullyQualifiedName(Type type) + { + var methodInfo = typeof(TypeInjector).GetMethod(nameof(GetFullyQualifiedNameGeneric), BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(type); + return methodInfo.Invoke(null, null) is ValueTuple result + ? result + : throw new InvalidOperationException("GetFullyQualifiedName should return a ValueTuple"); + } + + private static (string AssemblyName, string Namespace, string Name) GetFullyQualifiedNameGeneric() where T : IIl2CppType + { + return (T.AssemblyName, T.Namespace, T.Name); + } + + private static Il2CppClassAttributes TypeAttributesToClassAttributes(TypeAttributes typeAttributes) + { + Il2CppClassAttributes result = default; + result |= (Il2CppClassAttributes)(typeAttributes & TypeAttributes.VisibilityMask); + result |= (Il2CppClassAttributes)(typeAttributes & TypeAttributes.LayoutMask); + result |= (Il2CppClassAttributes)(typeAttributes & TypeAttributes.ClassSemanticsMask); + result |= (Il2CppClassAttributes)(typeAttributes & TypeAttributes.StringFormatMask); + result |= (typeAttributes & TypeAttributes.Abstract) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_ABSTRACT : default; + result |= (typeAttributes & TypeAttributes.Sealed) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_SEALED : default; + result |= (typeAttributes & TypeAttributes.SpecialName) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_SPECIAL_NAME : default; + result |= (typeAttributes & TypeAttributes.RTSpecialName) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_RT_SPECIAL_NAME : default; + result |= (typeAttributes & TypeAttributes.Import) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_IMPORT : default; +#pragma warning disable SYSLIB0050 // Type or member is obsolete + result |= (typeAttributes & TypeAttributes.Serializable) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_SERIALIZABLE : default; +#pragma warning restore SYSLIB0050 // Type or member is obsolete + result |= (typeAttributes & TypeAttributes.HasSecurity) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_HAS_SECURITY : default; + result |= (typeAttributes & TypeAttributes.BeforeFieldInit) != 0 ? Il2CppClassAttributes.TYPE_ATTRIBUTE_BEFORE_FIELD_INIT : default; + return result; + } + + private static Il2CppMethodFlags MethodAttributesToMethodFlags(MethodAttributes methodAttributes) + { + Il2CppMethodFlags result = default; + result |= (Il2CppMethodFlags)(methodAttributes & MethodAttributes.MemberAccessMask); + result |= (Il2CppMethodFlags)(methodAttributes & MethodAttributes.VtableLayoutMask); + result |= (methodAttributes & MethodAttributes.Static) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_STATIC : default; + result |= (methodAttributes & MethodAttributes.Final) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_FINAL : default; + result |= (methodAttributes & MethodAttributes.Virtual) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_VIRTUAL : default; + result |= (methodAttributes & MethodAttributes.HideBySig) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_HIDE_BY_SIG : default; + result |= (methodAttributes & MethodAttributes.NewSlot) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_NEW_SLOT : default; + result |= (methodAttributes & MethodAttributes.Abstract) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_ABSTRACT : default; + result |= (methodAttributes & MethodAttributes.SpecialName) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_SPECIAL_NAME : default; + result |= (methodAttributes & MethodAttributes.RTSpecialName) != 0 ? Il2CppMethodFlags.METHOD_ATTRIBUTE_RT_SPECIAL_NAME : default; + return result; + } + + private static Type GetEnumUnderlyingType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type enumType) + { + // Single instance field + var instanceField = enumType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Single(); + return instanceField.FieldType; + } + + [RequiresUnreferencedCode("")] + private static (string Name, object Value)[] GetEnumNamesAndValues([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type enumType) + { + var fields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); + if (AnyFieldWrongType(enumType, fields)) + fields = fields.Where(f => f.FieldType == enumType).ToArray(); + if (fields.Length == 0) + return []; + + var instanceField = enumType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Single(); + var underlyingType = instanceField.FieldType; + var primitiveType = underlyingType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Single().FieldType; + var underlyingTypeConversionToPrimitiveType = underlyingType.GetMethod("op_Implicit", BindingFlags.Public | BindingFlags.Static, null, [underlyingType], null)!; + + (string Name, object Value)[] result = new (string Name, object Value)[fields.Length]; + for (var i = 0; i < fields.Length; i++) + { + var field = fields[i]; + var enumValue = field.GetValue(null); + var underlyingValue = instanceField.GetValue(enumValue); + var primitiveValue = underlyingTypeConversionToPrimitiveType.Invoke(null, [underlyingValue])!; + result[i] = (field.Name, primitiveValue); + } + return result; + + static bool AnyFieldWrongType(Type enumType, FieldInfo[] fields) + { + foreach (var field in fields) + { + if (field.FieldType != enumType) + return true; + } + return false; + } + } + + private static int GetEnumElementSize(Il2CppTypeEnum type) + { + return type switch + { + Il2CppTypeEnum.IL2CPP_TYPE_I1 => sizeof(sbyte), + Il2CppTypeEnum.IL2CPP_TYPE_U1 => sizeof(byte), + + Il2CppTypeEnum.IL2CPP_TYPE_CHAR => sizeof(char), + + Il2CppTypeEnum.IL2CPP_TYPE_I2 => sizeof(short), + Il2CppTypeEnum.IL2CPP_TYPE_U2 => sizeof(ushort), + + Il2CppTypeEnum.IL2CPP_TYPE_I4 => sizeof(int), + Il2CppTypeEnum.IL2CPP_TYPE_U4 => sizeof(uint), + + Il2CppTypeEnum.IL2CPP_TYPE_I8 => sizeof(long), + Il2CppTypeEnum.IL2CPP_TYPE_U8 => sizeof(ulong), + + _ => throw new ArgumentOutOfRangeException(nameof(type), type, $"The type provided {type} is invalid") + }; + } + + private static IntPtr AllocateNewDefaultValueBlob(Il2CppTypeEnum type) + { + var size = GetEnumElementSize(type); + var blob = Marshal.AllocHGlobal(size); + Logger.Instance.LogTrace("Allocated default value blob at 0x{Blob:X2} of {Size} for {Type}", (long)blob, size, type); + return blob; + } + + internal static bool GetFieldDefaultValueOverride(Il2CppFieldInfo* fieldInfo, out IntPtr defaultValueBlob) + { + return s_DefaultValueOverrides.TryGetValue((IntPtr)fieldInfo, out defaultValueBlob); + } + + private static IntPtr CreateOrUpdateFieldDefaultValue(Il2CppFieldInfo* field, Il2CppTypeStruct* type, object value) + { + var typeEnum = UnityVersionHandler.Wrap(type).Type; + + if (!GetFieldDefaultValueOverride(field, out var newBlob)) + { + newBlob = AllocateNewDefaultValueBlob(typeEnum); + s_DefaultValueOverrides[(IntPtr)field] = newBlob; + } + + SetFieldDefaultValue(newBlob, typeEnum, value); + return newBlob; + } + + private static void SetFieldDefaultValue(IntPtr blob, Il2CppTypeEnum type, object value) + { + var valueData = Convert.ToInt64(value); + switch (type) + { + case Il2CppTypeEnum.IL2CPP_TYPE_I1: + *(sbyte*)blob = (sbyte)valueData; + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U1: + *(byte*)blob = (byte)valueData; + break; + + case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: + *(char*)blob = (char)valueData; + break; + + case Il2CppTypeEnum.IL2CPP_TYPE_I2: + *(short*)blob = (short)valueData; + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U2: + *(ushort*)blob = (ushort)valueData; + break; + + case Il2CppTypeEnum.IL2CPP_TYPE_I4: + *(int*)blob = (int)valueData; + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U4: + *(uint*)blob = (uint)valueData; + break; + + case Il2CppTypeEnum.IL2CPP_TYPE_I8: + *(long*)blob = valueData; + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U8: + *(ulong*)blob = (ulong)valueData; + break; + + default: throw new ArgumentOutOfRangeException(nameof(type), type, $"The type provided {type} is invalid"); + } + } + + private static bool IsStatic(this PropertyInfo property) + { + var accessor = property.GetMethod ?? property.SetMethod; + return accessor is not null && accessor.IsStatic; + } + + private delegate void InvokerDelegateMetadataV29(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, IntPtr* args, IntPtr* returnValue); + + private delegate IntPtr InvokerDelegate(IntPtr methodPointer, Il2CppMethodInfo* methodInfo, IntPtr obj, IntPtr* args); +} diff --git a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs b/Il2CppInterop.Runtime/Injection/XrefScanner.cs similarity index 60% rename from Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs rename to Il2CppInterop.Runtime/Injection/XrefScanner.cs index a8d44d7c..cd2cb1aa 100644 --- a/Il2CppInterop.Common/XrefScans/XrefScannerLowLevel.cs +++ b/Il2CppInterop.Runtime/Injection/XrefScanner.cs @@ -1,15 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; using Iced.Intel; -namespace Il2CppInterop.Common.XrefScans; +namespace Il2CppInterop.Runtime.Injection; -public static class XrefScannerLowLevel +internal static class XrefScanner { - public static IEnumerable JumpTargets(IntPtr codeStart, bool ignoreRetn = false) + public static IEnumerable JumpTargets(IntPtr codeStart, bool ignoreReturn = false) { - return JumpTargetsImpl(XrefScanner.DecoderForAddress(codeStart), ignoreRetn); + return JumpTargetsImpl(DecoderForAddress(codeStart), ignoreReturn); } - private static IEnumerable JumpTargetsImpl(Decoder myDecoder, bool ignoreRetn) + private static IEnumerable JumpTargetsImpl(Decoder myDecoder, bool ignoreReturn) { var firstFlowControl = true; @@ -22,7 +25,7 @@ private static IEnumerable JumpTargetsImpl(Decoder myDecoder, bool ignor if (instruction.Mnemonic == Mnemonic.Int3) yield break; - if (instruction.FlowControl == FlowControl.Return && !ignoreRetn) + if (instruction.FlowControl == FlowControl.Return && !ignoreReturn) yield break; if (instruction.FlowControl == FlowControl.UnconditionalBranch || @@ -31,8 +34,11 @@ private static IEnumerable JumpTargetsImpl(Decoder myDecoder, bool ignor // We hope and pray that the compiler didn't use short jumps for any function calls if (!instruction.IsJmpShort) { - yield return (IntPtr)ExtractTargetAddress(in instruction); - if (firstFlowControl && instruction.FlowControl == FlowControl.UnconditionalBranch) yield break; + var targetAddress = (IntPtr)ExtractTargetAddress(in instruction); + if (targetAddress != IntPtr.Zero) + yield return targetAddress; + if (firstFlowControl && instruction.FlowControl == FlowControl.UnconditionalBranch) + yield break; } } @@ -45,7 +51,7 @@ private static IEnumerable JumpTargetsImpl(Decoder myDecoder, bool ignor public static IEnumerable CallAndIndirectTargets(IntPtr pointer) { - return CallAndIndirectTargetsImpl(XrefScanner.DecoderForAddress(pointer, 1024 * 1024)); + return CallAndIndirectTargetsImpl(DecoderForAddress(pointer, 1024 * 1024)); } private static IEnumerable CallAndIndirectTargetsImpl(Decoder decoder) @@ -63,7 +69,7 @@ private static IEnumerable CallAndIndirectTargetsImpl(Decoder decoder) if (instruction.Mnemonic == Mnemonic.Call || instruction.Mnemonic == Mnemonic.Jmp) { - var targetAddress = XrefScanner.ExtractTargetAddress(instruction); + var targetAddress = ExtractTargetAddress(instruction); if (targetAddress != 0) yield return (IntPtr)targetAddress; continue; @@ -79,22 +85,26 @@ private static IEnumerable CallAndIndirectTargetsImpl(Decoder decoder) } } - private static ulong ExtractTargetAddress(in Instruction instruction) + private static ulong ExtractTargetAddress(in Instruction instruction) => instruction.Op0Kind switch { - switch (instruction.Op0Kind) - { - case OpKind.NearBranch16: - return instruction.NearBranch16; - case OpKind.NearBranch32: - return instruction.NearBranch32; - case OpKind.NearBranch64: - return instruction.NearBranch64; - case OpKind.FarBranch16: - return instruction.FarBranch16; - case OpKind.FarBranch32: - return instruction.FarBranch32; - default: - throw new ArgumentOutOfRangeException(); - } + OpKind.NearBranch16 => instruction.NearBranch16, + OpKind.NearBranch32 => instruction.NearBranch32, + OpKind.NearBranch64 => instruction.NearBranch64, + OpKind.FarBranch16 => instruction.FarBranch16, + OpKind.FarBranch32 => instruction.FarBranch32, + _ => 0, + }; + + private static unsafe Decoder DecoderForAddress(IntPtr codeStart, int lengthLimit = 1000) + { + if (codeStart == IntPtr.Zero) + throw new NullReferenceException(nameof(codeStart)); + + var stream = new UnmanagedMemoryStream((byte*)codeStart, lengthLimit, lengthLimit, FileAccess.Read); + var codeReader = new StreamCodeReader(stream); + var decoder = Decoder.Create(IntPtr.Size * 8, codeReader); + decoder.IP = (ulong)codeStart; + + return decoder; } } diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayBase.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayBase.cs index 3fbf4b69..ae47fae9 100644 --- a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayBase.cs +++ b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayBase.cs @@ -1,14 +1,14 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Il2CppInterop.Runtime.Runtime; +using System.Buffers; +using System.Runtime.CompilerServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Structs; namespace Il2CppInterop.Runtime.InteropTypes.Arrays; -public abstract class Il2CppArrayBase : Il2CppObjectBase, IEnumerable +public abstract class Il2CppArrayBase : Il2CppSystem.Array { - protected Il2CppArrayBase(IntPtr pointer) : base(pointer) + private protected Il2CppArrayBase(ObjectPointer pointer) : base(pointer) { } @@ -17,9 +17,7 @@ protected Il2CppArrayBase(IntPtr pointer) : base(pointer) /// private protected unsafe IntPtr ArrayStartPointer => IntPtr.Add(Pointer, sizeof(Il2CppObject) /* base */ + sizeof(void*) /* bounds */ + sizeof(nuint) /* max_length */); - public int Length => (int)IL2CPP.il2cpp_array_length(Pointer); - - public abstract IEnumerator GetEnumerator(); + public new int Length => base.Length; private protected static bool ThrowImmutableLength() { @@ -32,146 +30,115 @@ private protected void ThrowIfIndexOutOfRange(int index) throw new ArgumentOutOfRangeException(nameof(index), "Array index may not be negative or above length of the array"); } -} -public abstract class Il2CppArrayBase : Il2CppArrayBase, IList, IReadOnlyList -{ - protected Il2CppArrayBase(IntPtr pointer) : base(pointer) - { - } - public sealed override IEnumerator GetEnumerator() + public T LoadElementUnsafe(int index) where T : unmanaged { - return new IndexEnumerator(this); + ThrowIfIndexOutOfRange(index); + return Unsafe.As(ref GetUnsafeSpanForElement(index)[0]); } - void ICollection.Add(T item) + public void StoreElementUnsafe(int index, T value) where T : unmanaged { - ThrowImmutableLength(); + ThrowIfIndexOutOfRange(index); + ref var element = ref Unsafe.As(ref GetUnsafeSpanForElement(index)[0]); + element = value; } - void ICollection.Clear() + public IIl2CppType? LoadReferenceElementUnsafe(int index) { - ThrowImmutableLength(); + ThrowIfIndexOutOfRange(index); + var span = GetUnsafeSpanForElement(index); + var ptr = Il2CppType.ReadPointer(span); + return (IIl2CppType?)Il2CppObjectPool.Get(ptr); } - public bool Contains(T item) + public void StoreReferenceElementUnsafe(int index, IIl2CppType? value) { - return IndexOf(item) != -1; + ThrowIfIndexOutOfRange(index); + var span = GetUnsafeSpanForElement(index); + var ptr = (nint)NativeBoxing.Box(value); + Il2CppType.WritePointer(ptr, span); } - public void CopyTo(T[] array, int arrayIndex) + private protected virtual Span GetUnsafeSpanForElement(int index) { - if (array == null) throw new ArgumentNullException(nameof(array)); - if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - if (array.Length - arrayIndex < Length) - throw new ArgumentException( - $"Not enough space in target array: need {Length} slots, have {array.Length - arrayIndex}"); - - for (var i = 0; i < Length; i++) - array[i + arrayIndex] = this[i]; + throw new NotSupportedException("Only rank 1 arrays support unsafe element access"); } - bool ICollection.Remove(T item) + private protected static unsafe ObjectPointer AllocateArray(ReadOnlySpan lengths, IntPtr arrayClass) { - return ThrowImmutableLength(); - } + if (lengths.Length <= 1) + { + throw new ArgumentException("Use single-dimensional array allocation for single-dimensional arrays.", nameof(lengths)); + } + + var sizes = ArrayPool.Shared.Rent(lengths.Length); + for (var i = 0; i < lengths.Length; i++) + { + ArgumentOutOfRangeException.ThrowIfNegative(lengths[i]); + sizes[i] = (ulong)lengths[i]; + } - public new int Length => base.Length;// For binary compatibility - public int Count => Length; - bool ICollection.IsReadOnly => false; + var lowerBounds = ArrayPool.Shared.Rent(lengths.Length); + lowerBounds.AsSpan().Clear(); - public int IndexOf(T item) - { - for (var i = 0; i < Length; i++) - if (Equals(item, this[i])) - return i; + ObjectPointer result; + fixed (ulong* pSizes = sizes) + { + fixed (ulong* pLowerBounds = lowerBounds) + { + result = (ObjectPointer)IL2CPP.il2cpp_array_new_full(arrayClass, pSizes, pLowerBounds); + } + } - return -1; - } + ArrayPool.Shared.Return(sizes); + ArrayPool.Shared.Return(lowerBounds); - void IList.Insert(int index, T item) - { - ThrowImmutableLength(); + return result; } - void IList.RemoveAt(int index) + // https://github.com/js6pak/libil2cpp-archive/blob/90c6b7ed1c291d54b257d751a4d743d07dea8d62/vm/Array.cpp#L273-L286 + private protected long IndexFromIndices(ReadOnlySpan indices) { - ThrowImmutableLength(); - } + int rank = GetRank(); + long pos; - public abstract T this[int index] { get; set; } + pos = indices[0] - GetLowerBound(0); - protected static void StaticCtorBody(Type ownType) - { - var nativeClassPtr = Il2CppClassPointerStore.NativeClassPtr; - if (nativeClassPtr == IntPtr.Zero) - return; + for (var i = 1; i < rank; i++) + pos = pos * GetLength(i) + indices[i] - GetLowerBound(i); - var targetClassType = IL2CPP.il2cpp_array_class_get(nativeClassPtr, 1); - if (targetClassType == IntPtr.Zero) - return; - - Il2CppClassPointerStore.SetNativeClassPointer(ownType, targetClassType); - Il2CppClassPointerStore.SetNativeClassPointer(typeof(Il2CppArrayBase), targetClassType); - Il2CppClassPointerStore>.CreatedTypeRedirect = ownType; + return pos; } - [return: NotNullIfNotNull(nameof(il2CppArray))] - public static implicit operator T[]?(Il2CppArrayBase? il2CppArray) + private protected static void SetClassPointer(uint rank) + where TArray : Il2CppArrayBase, IIl2CppType + where TElement : IIl2CppType { - if (il2CppArray == null) - return null; - - var arr = new T[il2CppArray.Length]; - for (var i = 0; i < arr.Length; i++) - arr[i] = il2CppArray[i]; - - return arr; + Il2CppType.SetClassPointer(IL2CPP.il2cpp_array_class_get(Il2CppType.GetClassPointer(), rank)); } - - public static Il2CppArrayBase? WrapNativeGenericArrayPointer(IntPtr pointer) +} +public abstract class Il2CppArrayBase : Il2CppArrayBase + where T : IIl2CppType +{ + private protected Il2CppArrayBase(ObjectPointer pointer) : base(pointer) { - if (pointer == IntPtr.Zero) return null; - - if (typeof(T) == typeof(string)) - return new Il2CppStringArray(pointer) as Il2CppArrayBase; - if (typeof(T).IsValueType) // can't construct required types here directly because of unfulfilled generic constraint - return Activator.CreateInstance(typeof(Il2CppStructArray<>).MakeGenericType(typeof(T)), pointer) as - Il2CppArrayBase; - if (typeof(Il2CppObjectBase).IsAssignableFrom(typeof(T))) - return Activator.CreateInstance(typeof(Il2CppReferenceArray<>).MakeGenericType(typeof(T)), pointer) as - Il2CppArrayBase; - - throw new ArgumentException( - $"{typeof(T)} is not a value type, not a string and not an IL2CPP object; it can't be used in IL2CPP arrays"); } - private class IndexEnumerator : IEnumerator + private protected Il2CppArrayBase(ReadOnlySpan lengths, IntPtr arrayClass) : this(AllocateArray(lengths, arrayClass)) { - private Il2CppArrayBase myArray; - private int myIndex = -1; - - public IndexEnumerator(Il2CppArrayBase array) - { - myArray = array; - } - - public void Dispose() - { - myArray = null!; - } - - public bool MoveNext() - { - return ++myIndex < myArray.Count; - } + } - public void Reset() - { - myIndex = -1; - } + public T this[params ReadOnlySpan indices] + { + get => GetElementAddress(indices).GetValue()!; + set => GetElementAddress(indices).SetValue(value); + } - object? IEnumerator.Current => Current; - public T Current => myArray[myIndex]; + public unsafe ByReference GetElementAddress(params ReadOnlySpan indices) + { + var flatIndex = IndexFromIndices(indices); + void* elementPtr = (byte*)ArrayStartPointer + flatIndex * T.Size; + return new ByReference(elementPtr); } } diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank1.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank1.cs new file mode 100644 index 00000000..d2fc317f --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank1.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.InteropTypes.Arrays; + +public static class Il2CppArrayRank1 +{ + public static Il2CppArrayRank1 Create(ReadOnlySpan span) where T : IIl2CppType + { + return new Il2CppArrayRank1(span); + } + + public static Il2CppArrayRank1 CreateUnmanaged(ReadOnlySpan span) + where T : unmanaged, IIl2CppType + where U : unmanaged + { + if (Unsafe.SizeOf() != Unsafe.SizeOf()) + throw new ArgumentException($"Cannot create an array of {typeof(T)} from a span of {typeof(U)}: sizes do not match"); + + return Create(MemoryMarshal.Cast(span)); + } +} +[CollectionBuilder(typeof(Il2CppArrayRank1), nameof(Il2CppArrayRank1.Create))] +public sealed class Il2CppArrayRank1 : Il2CppArrayBase, IIl2CppType>, IList, IReadOnlyList, IEnumerable, ICollection + where T : IIl2CppType +{ + static Il2CppArrayRank1() + { + SetClassPointer, T>(1); + } + + public Il2CppArrayRank1(ObjectPointer pointer) : base(pointer) + { + } + + public Il2CppArrayRank1(int length) : base(AllocateArray(length)) + { + } + + public Il2CppArrayRank1(ReadOnlySpan values) : this(values.Length) + { + for (var i_0 = 0; i_0 < values.Length; i_0++) + { + this[i_0] = values[i_0]; + } + } + + private static ObjectPointer AllocateArray(int length) + { + ArgumentOutOfRangeException.ThrowIfNegative(length); + + var elementTypeClassPointer = Il2CppType.GetClassPointer(); + if (elementTypeClassPointer == IntPtr.Zero) + throw new ArgumentException($"{nameof(Il2CppArrayRank1<>)} requires an Il2Cpp type, which {typeof(T)} isn't"); + return (ObjectPointer)IL2CPP.il2cpp_array_new(elementTypeClassPointer, (ulong)length); + } + + public T this[int index] + { + get + { + ThrowIfIndexOutOfRange(index); + return T.ReadFromSpan(AsSpan().Slice(index * T.Size, T.Size))!; + } + set + { + ThrowIfIndexOutOfRange(index); + T.WriteToSpan(value, AsSpan().Slice(index * T.Size, T.Size)); + } + } + + public unsafe ByReference GetElementAddress(int index) + { + ThrowIfIndexOutOfRange(index); + return new ByReference((byte*)ArrayStartPointer.ToPointer() + index * T.Size); + } + + private unsafe Span AsSpan() + { + return new Span(ArrayStartPointer.ToPointer(), Length * T.Size); + } + + private protected override Span GetUnsafeSpanForElement(int index) + { + return GetElementAddress(index).AsSpan(); + } + + public IEnumerator GetEnumerator() + { + return new IndexEnumerator(this); + } + + public bool Contains(T item) + { + return IndexOf(item) != -1; + } + + public void CopyTo(T[] array, int arrayIndex) + { + ArgumentNullException.ThrowIfNull(array); + ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); + if (array.Length - arrayIndex < Length) + throw new ArgumentException( + $"Not enough space in target array: need {Length} slots, have {array.Length - arrayIndex}"); + + for (var i = 0; i < Length; i++) + array[i + arrayIndex] = this[i]; + } + + public int IndexOf(T item) + { + for (var i = 0; i < Length; i++) + if (Equals(item, this[i])) + return i; + + return -1; + } + + #region Conversions + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator Il2CppArrayRank1?(T[]? array) => array is null ? null : new(array); + + public static explicit operator Il2CppArrayRank1(ReadOnlySpan span) + { + return new Il2CppArrayRank1(span); + } + + public static explicit operator Il2CppArrayRank1(Span span) + { + return new Il2CppArrayRank1(span); + } + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator T[]?(Il2CppArrayRank1? array) + { + if (array is null) + return null; + + var length_0 = array.GetLength(0); + var result = new T[length_0]; + for (var i_0 = 0; i_0 < length_0; i_0++) + { + result[i_0] = array[i_0]; + } + return result; + } + + public static explicit operator ReadOnlySpan(Il2CppArrayRank1? array) + { + return (ReadOnlySpan)(T[]?)array; + } + + public static explicit operator Span(Il2CppArrayRank1? array) + { + return (Span)(T[]?)array; + } + #endregion + + #region IIl2CppType Implementation + nint IIl2CppType.ObjectClass => Il2CppType.GetClassPointer>(); + static void IIl2CppType>.WriteToSpan(Il2CppArrayRank1? value, Span span) => Il2CppType.WriteReference(value, span); + static Il2CppArrayRank1? IIl2CppType>.ReadFromSpan(ReadOnlySpan span) => Il2CppType.ReadReference>(span); + static Il2CppArrayRank1 IIl2CppType>.UnboxNative(ObjectPointer pointer) => new(pointer); + #endregion + + #region Collection Implementation + int ICollection.Count => Length; + bool ICollection.IsSynchronized => false; + object ICollection.SyncRoot => throw new NotSupportedException(); + int ICollection.Count => Length; + int IReadOnlyCollection.Count => Length; + bool ICollection.IsReadOnly => false; + + void ICollection.Add(T item) + { + ThrowImmutableLength(); + } + + void ICollection.Clear() + { + ThrowImmutableLength(); + } + + void ICollection.CopyTo(Array array, int index) => CopyTo((T[])array, index); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + void IList.Insert(int index, T item) + { + ThrowImmutableLength(); + } + + bool ICollection.Remove(T item) + { + return ThrowImmutableLength(); + } + + void IList.RemoveAt(int index) + { + ThrowImmutableLength(); + } + + private sealed class IndexEnumerator(Il2CppArrayRank1 array) : IEnumerator + { + private int index = -1; + + public void Dispose() => array = null!; + + public bool MoveNext() + { + return ++index < ((ICollection)array).Count; + } + + public void Reset() => index = -1; + + object? IEnumerator.Current => Current; + public T Current => array[index]; + } + #endregion +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank2.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank2.cs new file mode 100644 index 00000000..5e2bedc2 --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank2.cs @@ -0,0 +1,72 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.InteropTypes.Arrays; + +public sealed class Il2CppArrayRank2 : Il2CppArrayBase, IIl2CppType> + where T : IIl2CppType +{ + static Il2CppArrayRank2() + { + SetClassPointer, T>(2); + } + + public Il2CppArrayRank2(ObjectPointer pointer) : base(pointer) + { + } + + public Il2CppArrayRank2(int length0, int length1) : base([length0, length1], Il2CppType.GetClassPointer>()) + { + } + + public Il2CppArrayRank2(T[,] values) : this(values.GetLength(0), values.GetLength(1)) + { + var length_0 = values.GetLength(0); + var length_1 = values.GetLength(1); + + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + this[i_0, i_1] = values[i_0, i_1]; + } + } + } + + public T this[int index0, int index1] + { + get => this[[index0, index1]]; + set => this[[index0, index1]] = value; + } + public ByReference GetElementAddress(int index0, int index1) => GetElementAddress([index0, index1]); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator Il2CppArrayRank2?(T[,]? array) => array is null ? null : new(array); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator T[,]?(Il2CppArrayRank2? array) + { + if (array is null) + return null; + + var length_0 = array.GetLength(0); + var length_1 = array.GetLength(1); + var result = new T[length_0, length_1]; + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + result[i_0, i_1] = array[i_0, i_1]; + } + } + return result; + } + + #region IIl2CppType Implementation + nint IIl2CppType.ObjectClass => Il2CppType.GetClassPointer>(); + static void IIl2CppType>.WriteToSpan(Il2CppArrayRank2? value, Span span) => Il2CppType.WriteReference(value, span); + static Il2CppArrayRank2? IIl2CppType>.ReadFromSpan(ReadOnlySpan span) => Il2CppType.ReadReference>(span); + static Il2CppArrayRank2 IIl2CppType>.UnboxNative(ObjectPointer pointer) => new(pointer); + #endregion +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank3.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank3.cs new file mode 100644 index 00000000..6bbcffac --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank3.cs @@ -0,0 +1,80 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.InteropTypes.Arrays; + +public sealed class Il2CppArrayRank3 : Il2CppArrayBase, IIl2CppType> + where T : IIl2CppType +{ + static Il2CppArrayRank3() + { + SetClassPointer, T>(3); + } + + public Il2CppArrayRank3(ObjectPointer pointer) : base(pointer) + { + } + + public Il2CppArrayRank3(int length0, int length1, int length2) : base([length0, length1, length2], Il2CppType.GetClassPointer>()) + { + } + + public Il2CppArrayRank3(T[,,] values) : this(values.GetLength(0), values.GetLength(1), values.GetLength(2)) + { + var length_0 = values.GetLength(0); + var length_1 = values.GetLength(1); + var length_2 = values.GetLength(2); + + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + for (var i_2 = 0; i_2 < length_2; i_2++) + { + this[i_0, i_1, i_2] = values[i_0, i_1, i_2]; + } + } + } + } + + public T this[int index0, int index1, int index2] + { + get => this[[index0, index1, index2]]; + set => this[[index0, index1, index2]] = value; + } + public ByReference GetElementAddress(int index0, int index1, int index2) => GetElementAddress([index0, index1, index2]); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator Il2CppArrayRank3?(T[,,]? array) => array is null ? null : new(array); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator T[,,]?(Il2CppArrayRank3? array) + { + if (array is null) + return null; + + var length_0 = array.GetLength(0); + var length_1 = array.GetLength(1); + var length_2 = array.GetLength(2); + var result = new T[length_0, length_1, length_2]; + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + for (var i_2 = 0; i_2 < length_2; i_2++) + { + result[i_0, i_1, i_2] = array[i_0, i_1, i_2]; + } + } + } + return result; + } + + #region IIl2CppType Implementation + nint IIl2CppType.ObjectClass => Il2CppType.GetClassPointer>(); + static void IIl2CppType>.WriteToSpan(Il2CppArrayRank3? value, Span span) => Il2CppType.WriteReference(value, span); + static Il2CppArrayRank3? IIl2CppType>.ReadFromSpan(ReadOnlySpan span) => Il2CppType.ReadReference>(span); + static Il2CppArrayRank3 IIl2CppType>.UnboxNative(ObjectPointer pointer) => new(pointer); + #endregion +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank4.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank4.cs new file mode 100644 index 00000000..336769a4 --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank4.cs @@ -0,0 +1,89 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.InteropTypes.Arrays; + +public sealed class Il2CppArrayRank4 : Il2CppArrayBase, IIl2CppType> + where T : IIl2CppType +{ + static Il2CppArrayRank4() + { + SetClassPointer, T>(4); + } + + public Il2CppArrayRank4(ObjectPointer pointer) : base(pointer) + { + } + + public Il2CppArrayRank4(int length0, int length1, int length2, int length3) : base([length0, length1, length2, length3], Il2CppType.GetClassPointer>()) + { + } + + public Il2CppArrayRank4(T[,,,] values) : this(values.GetLength(0), values.GetLength(1), values.GetLength(2), values.GetLength(3)) + { + var length_0 = values.GetLength(0); + var length_1 = values.GetLength(1); + var length_2 = values.GetLength(2); + var length_3 = values.GetLength(3); + + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + for (var i_2 = 0; i_2 < length_2; i_2++) + { + for (var i_3 = 0; i_3 < length_3; i_3++) + { + this[i_0, i_1, i_2, i_3] = values[i_0, i_1, i_2, i_3]; + } + } + } + } + } + + public T this[int index0, int index1, int index2, int index3] + { + get => this[[index0, index1, index2, index3]]; + set => this[[index0, index1, index2, index3]] = value; + } + + public ByReference GetElementAddress(int index0, int index1, int index2, int index3) => GetElementAddress([index0, index1, index2, index3]); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator Il2CppArrayRank4?(T[,,,]? array) => array is null ? null : new(array); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator T[,,,]?(Il2CppArrayRank4? array) + { + if (array is null) + return null; + + var length_0 = array.GetLength(0); + var length_1 = array.GetLength(1); + var length_2 = array.GetLength(2); + var length_3 = array.GetLength(3); + var result = new T[length_0, length_1, length_2, length_3]; + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + for (var i_2 = 0; i_2 < length_2; i_2++) + { + for (var i_3 = 0; i_3 < length_3; i_3++) + { + result[i_0, i_1, i_2, i_3] = array[i_0, i_1, i_2, i_3]; + } + } + } + } + return result; + } + + #region IIl2CppType Implementation + nint IIl2CppType.ObjectClass => Il2CppType.GetClassPointer>(); + static void IIl2CppType>.WriteToSpan(Il2CppArrayRank4? value, Span span) => Il2CppType.WriteReference(value, span); + static Il2CppArrayRank4? IIl2CppType>.ReadFromSpan(ReadOnlySpan span) => Il2CppType.ReadReference>(span); + static Il2CppArrayRank4 IIl2CppType>.UnboxNative(ObjectPointer pointer) => new(pointer); + #endregion +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank5.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank5.cs new file mode 100644 index 00000000..a3e33caf --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppArrayRank5.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.InteropTypes.Arrays; + +public sealed class Il2CppArrayRank5 : Il2CppArrayBase, IIl2CppType> + where T : IIl2CppType +{ + static Il2CppArrayRank5() + { + SetClassPointer, T>(5); + } + + public Il2CppArrayRank5(ObjectPointer pointer) : base(pointer) + { + } + + public Il2CppArrayRank5(int length0, int length1, int length2, int length3, int length4) : base([length0, length1, length2, length3, length4], Il2CppType.GetClassPointer>()) + { + } + + public Il2CppArrayRank5(T[,,,,] values) : this(values.GetLength(0), values.GetLength(1), values.GetLength(2), values.GetLength(3), values.GetLength(4)) + { + var length_0 = values.GetLength(0); + var length_1 = values.GetLength(1); + var length_2 = values.GetLength(2); + var length_3 = values.GetLength(3); + var length_4 = values.GetLength(4); + + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + for (var i_2 = 0; i_2 < length_2; i_2++) + { + for (var i_3 = 0; i_3 < length_3; i_3++) + { + for (var i_4 = 0; i_4 < length_4; i_4++) + { + this[i_0, i_1, i_2, i_3, i_4] = values[i_0, i_1, i_2, i_3, i_4]; + } + } + } + } + } + } + + public T this[int index0, int index1, int index2, int index3, int index4] + { + get => this[[index0, index1, index2, index3, index4]]; + set => this[[index0, index1, index2, index3, index4]] = value; + } + + public ByReference GetElementAddress(int index0, int index1, int index2, int index3, int index4) => GetElementAddress([index0, index1, index2, index3, index4]); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator Il2CppArrayRank5?(T[,,,,]? array) => array is null ? null : new(array); + + [return: NotNullIfNotNull(nameof(array))] + public static explicit operator T[,,,,]?(Il2CppArrayRank5? array) + { + if (array is null) + return null; + + var length_0 = array.GetLength(0); + var length_1 = array.GetLength(1); + var length_2 = array.GetLength(2); + var length_3 = array.GetLength(3); + var length_4 = array.GetLength(4); + var result = new T[length_0, length_1, length_2, length_3, length_4]; + for (var i_0 = 0; i_0 < length_0; i_0++) + { + for (var i_1 = 0; i_1 < length_1; i_1++) + { + for (var i_2 = 0; i_2 < length_2; i_2++) + { + for (var i_3 = 0; i_3 < length_3; i_3++) + { + for (var i_4 = 0; i_4 < length_4; i_4++) + { + result[i_0, i_1, i_2, i_3, i_4] = array[i_0, i_1, i_2, i_3, i_4]; + } + } + } + } + } + return result; + } + + #region IIl2CppType Implementation + nint IIl2CppType.ObjectClass => Il2CppType.GetClassPointer>(); + static void IIl2CppType>.WriteToSpan(Il2CppArrayRank5? value, Span span) => Il2CppType.WriteReference(value, span); + static Il2CppArrayRank5? IIl2CppType>.ReadFromSpan(ReadOnlySpan span) => Il2CppType.ReadReference>(span); + static Il2CppArrayRank5 IIl2CppType>.UnboxNative(ObjectPointer pointer) => new(pointer); + #endregion +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppReferenceArray.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppReferenceArray.cs deleted file mode 100644 index b2d9b181..00000000 --- a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppReferenceArray.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using Il2CppInterop.Runtime.Runtime; - -namespace Il2CppInterop.Runtime.InteropTypes.Arrays; - -public class Il2CppReferenceArray : Il2CppArrayBase where T : Il2CppObjectBase? -{ - private static readonly int ourElementTypeSize; - private static readonly bool ourElementIsValueType; - - static Il2CppReferenceArray() - { - ourElementTypeSize = IntPtr.Size; - var nativeClassPtr = Il2CppClassPointerStore.NativeClassPtr; - if (nativeClassPtr == IntPtr.Zero) return; - uint align = 0; - if (IL2CPP.il2cpp_class_is_valuetype(nativeClassPtr)) - { - ourElementIsValueType = true; - ourElementTypeSize = IL2CPP.il2cpp_class_value_size(nativeClassPtr, ref align); - } - - StaticCtorBody(typeof(Il2CppReferenceArray)); - } - - public Il2CppReferenceArray(IntPtr nativeObject) : base(nativeObject) - { - } - - public Il2CppReferenceArray(long size) : base(AllocateArray(size)) - { - } - - public Il2CppReferenceArray(T[] arr) : base(AllocateArray(arr.Length)) - { - for (var i = 0; i < arr.Length; i++) - this[i] = arr[i]; - } - - public override T this[int index] - { - get => WrapElement(GetElementPointer(index))!; - set => StoreValue(GetElementPointer(index), value?.Pointer ?? IntPtr.Zero); - } - - private IntPtr GetElementPointer(int index) - { - ThrowIfIndexOutOfRange(index); - return IntPtr.Add(ArrayStartPointer, index * ourElementTypeSize); - } - - [return: NotNullIfNotNull(nameof(arr))] - public static implicit operator Il2CppReferenceArray?(T[]? arr) - { - if (arr == null) return null; - - return new Il2CppReferenceArray(arr); - } - - private static unsafe void StoreValue(IntPtr targetPointer, IntPtr valuePointer) - { - if (ourElementIsValueType) - { - if (valuePointer == IntPtr.Zero) - throw new NullReferenceException(); - - var valueRawPointer = (byte*)IL2CPP.il2cpp_object_unbox(valuePointer); - var targetRawPointer = (byte*)targetPointer; - - Unsafe.CopyBlock(targetRawPointer, valueRawPointer, (uint)ourElementTypeSize); - } - else - { - *(IntPtr*)targetPointer = valuePointer; - } - } - - private static unsafe T? WrapElement(IntPtr memberPointer) - { - if (ourElementIsValueType) - memberPointer = IL2CPP.il2cpp_value_box(Il2CppClassPointerStore.NativeClassPtr, memberPointer); - else - memberPointer = *(IntPtr*)memberPointer; - - if (memberPointer == IntPtr.Zero) - return default; - - return Il2CppObjectPool.Get(memberPointer); - } - - private static IntPtr AllocateArray(long size) - { - if (size < 0) - throw new ArgumentOutOfRangeException(nameof(size), "Array size must not be negative"); - - var elementTypeClassPointer = Il2CppClassPointerStore.NativeClassPtr; - if (elementTypeClassPointer == IntPtr.Zero) - throw new ArgumentException( - $"{nameof(Il2CppReferenceArray)} requires an Il2Cpp reference type, which {typeof(T)} isn't"); - return IL2CPP.il2cpp_array_new(elementTypeClassPointer, (ulong)size); - } -} diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppStringArray.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppStringArray.cs deleted file mode 100644 index 834f24e2..00000000 --- a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppStringArray.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Il2CppInterop.Runtime.InteropTypes.Arrays; - -public class Il2CppStringArray : Il2CppArrayBase -{ - static Il2CppStringArray() - { - StaticCtorBody(typeof(Il2CppStringArray)); - } - - public Il2CppStringArray(IntPtr pointer) : base(pointer) - { - } - - public Il2CppStringArray(long size) : base(AllocateArray(size)) - { - } - - public Il2CppStringArray(string?[] arr) : base(AllocateArray(arr.Length)) - { - for (var i = 0; i < arr.Length; i++) - this[i] = arr[i]; - } -#nullable disable - public override unsafe string this[int index] - { - get => IL2CPP.Il2CppStringToManaged(*GetElementPointer(index)); - set => *GetElementPointer(index) = IL2CPP.ManagedStringToIl2Cpp(value); - } -#nullable enable - private unsafe IntPtr* GetElementPointer(int index) - { - ThrowIfIndexOutOfRange(index); - return (IntPtr*)IntPtr.Add(ArrayStartPointer, index * IntPtr.Size).ToPointer(); - } - - [return: NotNullIfNotNull(nameof(arr))] - public static implicit operator Il2CppStringArray?(string?[]? arr) - { - if (arr == null) return null; - - return new Il2CppStringArray(arr); - } - - private static IntPtr AllocateArray(long size) - { - if (size < 0) - throw new ArgumentOutOfRangeException(nameof(size), "Array size must not be negative"); - - var elementTypeClassPointer = Il2CppClassPointerStore.NativeClassPtr; - if (elementTypeClassPointer == IntPtr.Zero) - throw new ArgumentException("String class pointer is missing, something is very wrong"); - return IL2CPP.il2cpp_array_new(elementTypeClassPointer, (ulong)size); - } -} diff --git a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppStructArray.cs b/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppStructArray.cs deleted file mode 100644 index 044928d9..00000000 --- a/Il2CppInterop.Runtime/InteropTypes/Arrays/Il2CppStructArray.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Il2CppInterop.Runtime.InteropTypes.Arrays; - -public class Il2CppStructArray : Il2CppArrayBase where T : unmanaged -{ - static Il2CppStructArray() - { - StaticCtorBody(typeof(Il2CppStructArray)); - } - - public Il2CppStructArray(IntPtr nativeObject) : base(nativeObject) - { - } - - public Il2CppStructArray(long size) : base(AllocateArray(size)) - { - } - - public Il2CppStructArray(T[] arr) : base(AllocateArray(arr.Length)) - { - arr.CopyTo(this); - } - - public override T this[int index] - { - get => AsSpan()[index]; - set => AsSpan()[index] = value; - } - - public unsafe Span AsSpan() - { - return new Span(ArrayStartPointer.ToPointer(), Length); - } - - [return: NotNullIfNotNull(nameof(arr))] - public static implicit operator Il2CppStructArray?(T[]? arr) - { - if (arr == null) return null; - - return new Il2CppStructArray(arr); - } - - public static implicit operator Span(Il2CppStructArray? il2CppArray) - { - return il2CppArray is not null ? il2CppArray.AsSpan() : default; - } - - public static implicit operator ReadOnlySpan(Il2CppStructArray? il2CppArray) - { - return il2CppArray is not null ? il2CppArray.AsSpan() : default; - } - - private static IntPtr AllocateArray(long size) - { - if (size < 0) - throw new ArgumentOutOfRangeException(nameof(size), "Array size must not be negative"); - - var elementTypeClassPointer = Il2CppClassPointerStore.NativeClassPtr; - if (elementTypeClassPointer == IntPtr.Zero) - throw new ArgumentException( - $"{nameof(Il2CppStructArray)} requires an Il2Cpp reference type, which {typeof(T)} isn't"); - return IL2CPP.il2cpp_array_new(elementTypeClassPointer, (ulong)size); - } -} diff --git a/Il2CppInterop.Runtime/InteropTypes/ByReference.cs b/Il2CppInterop.Runtime/InteropTypes/ByReference.cs new file mode 100644 index 00000000..b344900b --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/ByReference.cs @@ -0,0 +1,105 @@ +using System; +using Il2CppInterop.Common; + +namespace Il2CppInterop.Runtime.InteropTypes; + +public static class ByReference +{ + public static T? GetValue(ByReference byReference) + where T : IIl2CppType + { + return byReference.GetValue(); + } + + public static void SetValue1(ByReference byReference, T? value) + where T : IIl2CppType + { + byReference.SetValue(value); + } + + public static void SetValue2(T? value, ByReference byReference) + where T : IIl2CppType + { + byReference.SetValue(value); + } + + // ldflda + public static unsafe ByReference GetReferenceAtOffset(ByReference byReference, int offset) + where T : IIl2CppType + where U : IIl2CppType + { + return new ByReference((byte*)byReference.ToPointer() + offset); + } +} +public readonly unsafe struct ByReference(void* pointer) : IIl2CppType>, IByReference + where T : IIl2CppType +{ + private readonly void* _pointer = pointer; + + public static ByReference Null => new(null); + public readonly bool IsNull => _pointer is null; + + public readonly T? GetValue() + { + ThrowIfNull(); + return Il2CppType.ReadFromPointer(_pointer); + } + + public readonly void SetValue(T? value) + { + ThrowIfNull(); + Il2CppType.WriteToPointer(value, _pointer); + } + + public readonly void Clear() + { + ThrowIfNull(); + Span span = new(_pointer, ReferenceSize); + span.Clear(); + } + + public readonly void CopyFrom(in T? value) => SetValue(value); + + public readonly void CopyTo(out T? value) => value = GetValue(); + + public readonly void CopyToUnmanaged(out U value) where U : unmanaged + { + ThrowIfNull(); + if (ReferenceSize != sizeof(U)) + { + throw new InvalidOperationException($"Cannot copy ByReference<{typeof(T).Name}> to unmanaged type {typeof(U).Name} because their sizes do not match. Size of ByReference<{typeof(T).Name}> is {ReferenceSize} bytes, size of {typeof(U).Name} is {sizeof(U)} bytes."); + } + value = *(U*)_pointer; + } + + public readonly void* ToPointer() => _pointer; + + private static int ReferenceSize => T.Size; + + readonly nint IIl2CppType.ObjectClass => 0; // ByReference types have no class + + static int IIl2CppType>.Size => IntPtr.Size; + + public static explicit operator ByReference(void* value) => new(value); + public static explicit operator void*(ByReference pointer) => pointer._pointer; + + public static explicit operator ByReference(IntPtr value) => new(value.ToPointer()); + public static explicit operator IntPtr(ByReference pointer) => new(pointer._pointer); + + public readonly Span AsSpan() + { + return new Span(_pointer, T.Size); + } + + private readonly void ThrowIfNull() + { + if (_pointer is null) + { + throw new NullReferenceException($"Cannot access reference of type {typeof(T).Name} because it is null."); + } + } + + ObjectPointer IIl2CppType.BoxNative() => throw new NotSupportedException("Boxing is not supported for by reference types."); + static void IIl2CppType>.WriteToSpan(ByReference value, Span span) => Il2CppType.WritePointer((IntPtr)value._pointer, span); + static ByReference IIl2CppType>.ReadFromSpan(ReadOnlySpan span) => (ByReference)(void*)Il2CppType.ReadPointer(span); +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppReferenceField.cs b/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppReferenceField.cs deleted file mode 100644 index ee5c8024..00000000 --- a/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppReferenceField.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using Il2CppInterop.Runtime.Runtime; - -namespace Il2CppInterop.Runtime.InteropTypes.Fields; - -public unsafe class Il2CppReferenceField where TRefObj : Il2CppObjectBase -{ - private readonly IntPtr _fieldPtr; - private readonly Il2CppObjectBase _obj; - - internal Il2CppReferenceField(Il2CppObjectBase obj, string fieldName) - { - _obj = obj; - _fieldPtr = IL2CPP.GetIl2CppField(obj.ObjectClass, fieldName); - } - - public TRefObj Value - { - get => Get(); - set => Set(value); - } - - public TRefObj? Get() - { - var ptr = *GetPointerToData(); - return ptr == IntPtr.Zero ? null : Il2CppObjectPool.Get(ptr); - } - - public void Set(TRefObj value) - { - *GetPointerToData() = value != null ? value.Pointer : IntPtr.Zero; - } - - public static implicit operator TRefObj(Il2CppReferenceField _this) - { - return _this.Get(); - } - - public static implicit operator Il2CppReferenceField(TRefObj _) - { - throw null; - } - - private IntPtr* GetPointerToData() - { - return (IntPtr*)(IL2CPP.Il2CppObjectBaseToPtrNotNull(_obj) + (int)IL2CPP.il2cpp_field_get_offset(_fieldPtr)); - } -} diff --git a/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppStringField.cs b/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppStringField.cs deleted file mode 100644 index 50a98e98..00000000 --- a/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppStringField.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.InteropTypes.Fields; - -public unsafe class Il2CppStringField -{ - private readonly IntPtr _fieldPtr; - - private readonly Il2CppObjectBase _obj; - - internal Il2CppStringField(Il2CppObjectBase obj, string fieldName) - { - _obj = obj; - _fieldPtr = IL2CPP.GetIl2CppField(obj.ObjectClass, fieldName); - } - - public string Value - { - get => Get(); - set => Set(value); - } - - public string? Get() - { - var ptr = *GetPointerToData(); - if (ptr == IntPtr.Zero) return null; - - return IL2CPP.Il2CppStringToManaged(ptr); - } - - public void Set(string value) - { - *GetPointerToData() = IL2CPP.ManagedStringToIl2Cpp(value); - } - - public static implicit operator string(Il2CppStringField _this) - { - return _this.Get(); - } - - public static implicit operator Il2CppStringField(string _) - { - throw null; - } - - public override string ToString() - { - return Get(); - } - - private IntPtr* GetPointerToData() - { - return (IntPtr*)(IL2CPP.Il2CppObjectBaseToPtrNotNull(_obj) + (int)IL2CPP.il2cpp_field_get_offset(_fieldPtr)); - } -} diff --git a/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppValueField.cs b/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppValueField.cs deleted file mode 100644 index 5c02564b..00000000 --- a/Il2CppInterop.Runtime/InteropTypes/Fields/Il2CppValueField.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.InteropTypes.Fields; - -public unsafe class Il2CppValueField where T : unmanaged -{ - private readonly IntPtr _fieldPtr; - - private readonly Il2CppObjectBase _obj; - - internal Il2CppValueField(Il2CppObjectBase obj, string fieldName) - { - _obj = obj; - _fieldPtr = IL2CPP.GetIl2CppField(obj.ObjectClass, fieldName); - } - - public T Value - { - get => Get(); - set => Set(value); - } - - public T Get() - { - return *GetPointerToData(); - } - - public void Set(T value) - { - *GetPointerToData() = value; - } - - public static implicit operator T(Il2CppValueField _this) - { - return _this.Get(); - } - - public static implicit operator Il2CppValueField(T _) - { - throw null; - } - - private T* GetPointerToData() - { - return (T*)(IL2CPP.Il2CppObjectBaseToPtrNotNull(_obj) + (int)IL2CPP.il2cpp_field_get_offset(_fieldPtr)); - } -} diff --git a/Il2CppInterop.Runtime/InteropTypes/IByReference.cs b/Il2CppInterop.Runtime/InteropTypes/IByReference.cs new file mode 100644 index 00000000..865eba66 --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/IByReference.cs @@ -0,0 +1,11 @@ +namespace Il2CppInterop.Runtime.InteropTypes; + +/// +/// Marker interface for +/// +/// +/// Nothing else should implement this interface. +/// +internal interface IByReference +{ +} diff --git a/Il2CppInterop.Runtime/InteropTypes/IPointer.cs b/Il2CppInterop.Runtime/InteropTypes/IPointer.cs new file mode 100644 index 00000000..7a32682a --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/IPointer.cs @@ -0,0 +1,11 @@ +namespace Il2CppInterop.Runtime.InteropTypes; + +/// +/// Marker interface for +/// +/// +/// Nothing else should implement this interface. +/// +internal interface IPointer +{ +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs b/Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs deleted file mode 100644 index 7cbc0f1a..00000000 --- a/Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using Il2CppInterop.Runtime.Runtime; - -namespace Il2CppInterop.Runtime.InteropTypes; - -public class Il2CppObjectBase -{ - private static readonly MethodInfo _unboxMethod = typeof(Il2CppObjectBase).GetMethod(nameof(Unbox)); - internal bool isWrapped; - internal IntPtr pooledPtr; - - private nint myGcHandle; - - public Il2CppObjectBase(IntPtr pointer) - { - CreateGCHandle(pointer); - } - - public IntPtr ObjectClass => IL2CPP.il2cpp_object_get_class(Pointer); - - public IntPtr Pointer - { - get - { - var handleTarget = IL2CPP.il2cpp_gchandle_get_target(myGcHandle); - if (handleTarget == IntPtr.Zero) - throw new ObjectCollectedException("Object was garbage collected in IL2CPP domain"); - return handleTarget; - } - } - - public bool WasCollected - { - get - { - var handleTarget = IL2CPP.il2cpp_gchandle_get_target(myGcHandle); - if (handleTarget == IntPtr.Zero) return true; - return false; - } - } - - internal void CreateGCHandle(IntPtr objHdl) - { - if (objHdl == IntPtr.Zero) - throw new NullReferenceException(); - - // This object already wraps an Il2Cpp object, skip the pointer and let it be GC'd - if (isWrapped) - return; - - myGcHandle = IL2CPP.il2cpp_gchandle_new(objHdl, false); - } - - public T Cast() where T : Il2CppObjectBase - { - return TryCast() ?? throw new InvalidCastException( - $"Can't cast object of type {IL2CPP.il2cpp_class_get_name_(IL2CPP.il2cpp_object_get_class(Pointer))} to type {typeof(T)}"); - } - - internal static unsafe T UnboxUnsafe(IntPtr pointer) - { - var nestedTypeClassPointer = Il2CppClassPointerStore.NativeClassPtr; - if (nestedTypeClassPointer == IntPtr.Zero) - throw new ArgumentException($"{typeof(T)} is not an Il2Cpp reference type"); - - var ownClass = IL2CPP.il2cpp_object_get_class(pointer); - if (!IL2CPP.il2cpp_class_is_assignable_from(nestedTypeClassPointer, ownClass)) - throw new InvalidCastException( - $"Can't cast object of type {IL2CPP.il2cpp_class_get_name_(ownClass)} to type {typeof(T)}"); - - return Unsafe.AsRef(IL2CPP.il2cpp_object_unbox(pointer).ToPointer()); - } - - public T Unbox() where T : unmanaged - { - return UnboxUnsafe(Pointer); - } - - private static readonly Type[] _intPtrTypeArray = { typeof(IntPtr) }; - private static readonly MethodInfo _getUninitializedObject = typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.GetUninitializedObject))!; - private static readonly MethodInfo _getTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!; - private static readonly MethodInfo _createGCHandle = typeof(Il2CppObjectBase).GetMethod(nameof(CreateGCHandle), BindingFlags.Instance | BindingFlags.NonPublic)!; - private static readonly FieldInfo _isWrapped = typeof(Il2CppObjectBase).GetField(nameof(isWrapped), BindingFlags.Instance | BindingFlags.NonPublic)!; - - internal static class InitializerStore - { - private static Func? _initializer; - - private static Func Create() - { - var type = Il2CppClassPointerStore.CreatedTypeRedirect ?? typeof(T); - - var dynamicMethod = new DynamicMethod($"Initializer<{typeof(T).AssemblyQualifiedName}>", type, _intPtrTypeArray); - dynamicMethod.DefineParameter(0, ParameterAttributes.None, "pointer"); - - var il = dynamicMethod.GetILGenerator(); - - if (type.GetConstructor(new[] { typeof(IntPtr) }) is { } pointerConstructor) - { - // Base case: Il2Cpp constructor => call it directly - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Newobj, pointerConstructor); - } - else - { - // Special case: We have a parameterless constructor - // However, it could be be user-made or implicit - // In that case we set the GCHandle and then call the ctor and let GC destroy any objects created by DerivedConstructorPointer - - // var obj = (T)RuntimeHelpers.GetUninitializedObject(type); - il.Emit(OpCodes.Ldtoken, type); - il.Emit(OpCodes.Call, _getTypeFromHandle); - il.Emit(OpCodes.Call, _getUninitializedObject); - il.Emit(OpCodes.Castclass, type); - - // obj.CreateGCHandle(pointer); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Callvirt, _createGCHandle); - - // obj.isWrapped = true; - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Stfld, _isWrapped); - - var parameterlessConstructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.EmptyTypes); - if (parameterlessConstructor != null) - { - // obj..ctor(); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Callvirt, parameterlessConstructor); - } - } - - il.Emit(OpCodes.Ret); - - return dynamicMethod.CreateDelegate>(); - } - - public static Func Initializer => _initializer ??= Create(); - } - - public T? TryCast() where T : Il2CppObjectBase - { - var nestedTypeClassPointer = Il2CppClassPointerStore.NativeClassPtr; - if (nestedTypeClassPointer == IntPtr.Zero) - throw new ArgumentException($"{typeof(T)} is not an Il2Cpp reference type"); - - var ownClass = IL2CPP.il2cpp_object_get_class(Pointer); - if (!IL2CPP.il2cpp_class_is_assignable_from(nestedTypeClassPointer, ownClass)) - return null; - - if (RuntimeSpecificsStore.IsInjected(ownClass)) - { - if (ClassInjectorBase.GetMonoObjectFromIl2CppPointer(Pointer) is T monoObject) return monoObject; - } - - return InitializerStore.Initializer(Pointer); - } - - ~Il2CppObjectBase() - { - IL2CPP.il2cpp_gchandle_free(myGcHandle); - - if (pooledPtr == IntPtr.Zero) return; - Il2CppObjectPool.Remove(pooledPtr); - } -} diff --git a/Il2CppInterop.Runtime/InteropTypes/Il2CppToMonoDelegateReference.cs b/Il2CppInterop.Runtime/InteropTypes/Il2CppToMonoDelegateReference.cs new file mode 100644 index 00000000..71527cb4 --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/Il2CppToMonoDelegateReference.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Common.Attributes; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime.InteropTypes; + +[InjectedType(Assembly = "Assembly-CSharp.dll")] +internal sealed partial class Il2CppToMonoDelegateReference : Object +{ + [Il2CppField] + public partial Il2CppSystem.IntPtr MethodInfo { get; set; } + [ManagedField] + public partial Delegate ReferencedDelegate { get; set; } + + public Il2CppToMonoDelegateReference(Delegate referencedDelegate, IntPtr methodInfo) : this(ObjectPointer.New()) + { + ReferencedDelegate = referencedDelegate; + MethodInfo = methodInfo; + } + + [Il2CppFinalizer] + private void DisposeMethodInfo() + { + Marshal.FreeHGlobal(MethodInfo); + MethodInfo = IntPtr.Zero; + } + + partial void LogErrorIl2CppFinalize(Exception exception) + { + Logger.Instance.LogError($"Exception in {nameof(Il2CppToMonoDelegateReference)}.{nameof(Il2CppFinalize)}: {{Exception}}", exception); + } + + private static ConcurrentDictionary _methodInfoCache = new(); + + /// + /// Creates or retrieves a cached dynamic method that will cast + /// to the specified and invoke it with the provided arguments. + /// + /// + /// Due to limitations in dynamic method creation, the dynamic method must be static. + /// As such, the first parameter of the dynamic method is always . + /// The remaining parameters are copied from the Invoke method of . + /// + /// The type of the delegate to cast and invoke. + /// The dynamic method. + [RequiresDynamicCode("")] + [RequiresUnreferencedCode("")] + internal static MethodInfo GetOrCreateInvokeMethod([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type delegateType) + { + return _methodInfoCache.GetOrAdd(delegateType, CreateInvokeMethod); + } + + [RequiresDynamicCode("")] + private static MethodInfo CreateInvokeMethod([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type delegateType) + { + var delegateInvokeMethod = delegateType.GetMethod("Invoke") + ?? throw new InvalidOperationException($"Delegate type {delegateType.FullName} does not have an Invoke method."); + + var returnType = delegateInvokeMethod.ReturnType; + + // Dynamic methods must be static, so we prepend the declaring type as the first parameter + Type[] parameterTypes = + [ + typeof(Il2CppToMonoDelegateReference), + ..delegateInvokeMethod.GetParameters().Select(p => p.ParameterType) + ]; + + var invokeMethod = new DynamicMethod($"Invoke_{delegateType.FullName}", returnType, parameterTypes, + typeof(Il2CppToMonoDelegateReference), false); + var bodyBuilder = invokeMethod.GetILGenerator(); + + bodyBuilder.Emit(OpCodes.Ldarg_0); + bodyBuilder.Emit(OpCodes.Callvirt, typeof(Il2CppToMonoDelegateReference).GetProperty(nameof(ReferencedDelegate))!.GetMethod!); + bodyBuilder.Emit(OpCodes.Castclass, delegateType); + for (var i = 1; i < parameterTypes.Length; i++) + { + bodyBuilder.Emit(OpCodes.Ldarg, i); + } + + bodyBuilder.Emit(OpCodes.Callvirt, delegateInvokeMethod); + bodyBuilder.Emit(OpCodes.Ret); + + return invokeMethod; + } +} diff --git a/Il2CppInterop.Runtime/InteropTypes/Pointer.cs b/Il2CppInterop.Runtime/InteropTypes/Pointer.cs new file mode 100644 index 00000000..18cae2a8 --- /dev/null +++ b/Il2CppInterop.Runtime/InteropTypes/Pointer.cs @@ -0,0 +1,57 @@ +using System; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Extensions; + +namespace Il2CppInterop.Runtime.InteropTypes; + +public readonly unsafe struct Pointer(void* pointer) : IIl2CppType>, IPointer + where T : IIl2CppType +{ + private readonly void* _pointer = pointer; + + public static Pointer Null => new(null); + public readonly bool IsNull => _pointer is null; + + public readonly T? this[int index] + { + get + { + ThrowIfNull(); + void* start = (byte*)_pointer + T.Size * index; + return Il2CppType.ReadFromPointer(start); + } + set + { + ThrowIfNull(); + void* start = (byte*)_pointer + T.Size * index; + Il2CppType.WriteToPointer(value, start); + } + } + + public readonly void* ToPointer() => _pointer; + + private readonly void ThrowIfNull() + { + if (_pointer is null) + { + throw new NullReferenceException($"Cannot access reference of type {typeof(T).Name} because it is null."); + } + } + + static int IIl2CppType>.Size => IntPtr.Size; + + readonly nint IIl2CppType.ObjectClass => Il2CppType.GetClassPointer>(); + + static Pointer IIl2CppType>.ReadFromSpan(ReadOnlySpan span) => (Pointer)(void*)Il2CppType.ReadPointer(span); + static void IIl2CppType>.WriteToSpan(Pointer value, Span span) => Il2CppType.WritePointer((IntPtr)value._pointer, span); + public ObjectPointer BoxNative() => throw new NotSupportedException("Boxing is not supported for pointer types."); + + public static explicit operator Pointer(void* value) => new(value); + public static explicit operator void*(Pointer pointer) => pointer._pointer; + + static Pointer() + { + var elementType = Il2CppSystem.Type.FromClassPointer(Il2CppType.GetClassPointer()); + Il2CppType.SetClassPointer>(elementType.MakePointerType().ToClassPointer()); + } +} diff --git a/Il2CppInterop.Runtime/Libs/Il2Cppmscorlib.dll b/Il2CppInterop.Runtime/Libs/Il2Cppmscorlib.dll deleted file mode 100644 index 0a5715c3..00000000 Binary files a/Il2CppInterop.Runtime/Libs/Il2Cppmscorlib.dll and /dev/null differ diff --git a/Il2CppInterop.Runtime/NativeBoxing.cs b/Il2CppInterop.Runtime/NativeBoxing.cs new file mode 100644 index 00000000..6431dd34 --- /dev/null +++ b/Il2CppInterop.Runtime/NativeBoxing.cs @@ -0,0 +1,40 @@ +using Il2CppInterop.Common; +using Il2CppSystem; + +namespace Il2CppInterop.Runtime; + +public static class NativeBoxing +{ + internal static ObjectPointer Box(T? value) where T : IIl2CppType + { + return value?.BoxNative() ?? ObjectPointer.Null; + } + + public static ObjectPointer BoxReferenceType(Object? value) + { + return (ObjectPointer?)value?.Pointer ?? ObjectPointer.Null; + } + + public static unsafe ObjectPointer BoxValueType(in T value) where T : struct, IIl2CppType + { + var data = stackalloc byte[T.Size]; + Il2CppType.WriteToPointer(value, data); + return (ObjectPointer)IL2CPP.il2cpp_value_box(value.ObjectClass, (nint)data); + } + + public static ObjectPointer BoxNullableValueType(in Nullable value) where T : struct, IIl2CppType, IValueType + { + return value.hasValue ? BoxValueType(value.value) : ObjectPointer.Null; + } + + public static Nullable UnboxNullableValueType(ObjectPointer pointer) where T : struct, IIl2CppType, IValueType + { + Nullable result = default; + if (pointer != ObjectPointer.Null) + { + result.hasValue = true; + result.value = T.UnboxNative(pointer); + } + return result; + } +} diff --git a/Il2CppInterop.Runtime/Runtime/ClassInjectorBase.cs b/Il2CppInterop.Runtime/Runtime/ClassInjectorBase.cs deleted file mode 100644 index 96ea8d05..00000000 --- a/Il2CppInterop.Runtime/Runtime/ClassInjectorBase.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Il2CppInterop.Runtime.Runtime; - -internal struct InjectedClassData -{ - public IntPtr managedGcHandle; -} - -public static class ClassInjectorBase -{ - public static object GetMonoObjectFromIl2CppPointer(IntPtr pointer) - { - var gcHandle = GetGcHandlePtrFromIl2CppObject(pointer); - - if (gcHandle == IntPtr.Zero) // The Garbage collector handle might return a null pointer - { - gcHandle = FallbackGetGcHandlePtrFromIl2CppDelegateMTarget(pointer); - } - - return GCHandle.FromIntPtr(gcHandle).Target; - } - - /// - /// Tries to get the Garbage collector pointer from the m_target object from the m_target of the delegate. - /// Fixes Harmony in Unity 2021.2.x . - /// - private static IntPtr FallbackGetGcHandlePtrFromIl2CppDelegateMTarget(IntPtr pointer) - { - if (IL2CPP.il2cpp_class_is_assignable_from(Il2CppClassPointerStore.NativeClassPtr, IL2CPP.il2cpp_object_get_class(pointer))) - { - var delegateObject = new Il2CppSystem.Delegate(pointer); - if (delegateObject.m_target != null && delegateObject.m_target.Pointer != IntPtr.Zero) - return GetGcHandlePtrFromIl2CppObject(delegateObject.m_target.Pointer); - } - return IntPtr.Zero; - } - - public static unsafe IntPtr GetGcHandlePtrFromIl2CppObject(IntPtr pointer) - { - return GetInjectedData(pointer)->managedGcHandle; - } - - internal static unsafe InjectedClassData* GetInjectedData(IntPtr objectPointer) - { - var pObjectClass = (Il2CppClass*)IL2CPP.il2cpp_object_get_class(objectPointer); - return (InjectedClassData*)(objectPointer + (int)UnityVersionHandler.Wrap(pObjectClass).InstanceSize - - sizeof(InjectedClassData)).ToPointer(); - } -} diff --git a/Il2CppInterop.Runtime/Runtime/Il2CppObjectPool.cs b/Il2CppInterop.Runtime/Runtime/Il2CppObjectPool.cs deleted file mode 100644 index 10abec31..00000000 --- a/Il2CppInterop.Runtime/Runtime/Il2CppObjectPool.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Runtime.CompilerServices; -using Il2CppInterop.Runtime.InteropTypes; -using Object = Il2CppSystem.Object; - -namespace Il2CppInterop.Runtime.Runtime; - -public static class Il2CppObjectPool -{ - internal static bool DisableCaching { get; set; } - - private static readonly ConcurrentDictionary> s_cache = new(); - - internal static void Remove(IntPtr ptr) - { - s_cache.TryRemove(ptr, out _); - } - - public static T Get(IntPtr ptr) - { - if (ptr == IntPtr.Zero) return default; - - var ownClass = IL2CPP.il2cpp_object_get_class(ptr); - if (RuntimeSpecificsStore.IsInjected(ownClass)) - { - var monoObject = ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ptr); - if (monoObject is T monoObjectT) return monoObjectT; - } - - if (DisableCaching) return Il2CppObjectBase.InitializerStore.Initializer(ptr); - - if (s_cache.TryGetValue(ptr, out var reference) && reference.TryGetTarget(out var cachedObject)) - { - if (cachedObject is T cachedObjectT) return cachedObjectT; - cachedObject.pooledPtr = IntPtr.Zero; - // This leaves the case when you cast to an interface handled as if nothing was cached - } - - var newObj = Il2CppObjectBase.InitializerStore.Initializer(ptr); - - var il2CppObjectBase = Unsafe.As(ref newObj); - s_cache[ptr] = new WeakReference(il2CppObjectBase); - il2CppObjectBase.pooledPtr = ptr; - return newObj; - } -} diff --git a/Il2CppInterop.Runtime/Runtime/StructHandlerInterfaces.cs b/Il2CppInterop.Runtime/Runtime/StructHandlerInterfaces.cs deleted file mode 100644 index 2a079019..00000000 --- a/Il2CppInterop.Runtime/Runtime/StructHandlerInterfaces.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime; - -public interface INativeStructHandler -{ - public int Size(); -} - -public interface INativeStruct -{ - IntPtr Pointer { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs deleted file mode 100644 index 0e4e1f7d..00000000 --- a/Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Il2CppInterop.Common; -using Il2CppInterop.Common.Extensions; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.EventInfo; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Exception; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.FieldInfo; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Image; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.ParameterInfo; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.PropertyInfo; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -using Il2CppInterop.Runtime.Startup; -using Microsoft.Extensions.Logging; - -namespace Il2CppInterop.Runtime.Runtime; - -[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -internal class ApplicableToUnityVersionsSinceAttribute : Attribute -{ - public ApplicableToUnityVersionsSinceAttribute(string startVersion) - { - StartVersion = startVersion; - } - - public string StartVersion { get; } -} - -public static class UnityVersionHandler -{ - private static readonly Type[] InterfacesOfInterest; - private static readonly Dictionary> VersionedHandlers = new(); - private static readonly Dictionary Handlers = new(); - - internal static INativeAssemblyStructHandler assemblyStructHandler; - internal static INativeAssemblyNameStructHandler assemblyNameStructHandler; - internal static INativeClassStructHandler classStructHandler; - internal static INativeEventInfoStructHandler eventInfoStructHandler; - internal static INativeExceptionStructHandler exceptionStructHandler; - internal static INativeFieldInfoStructHandler fieldInfoStructHandler; - internal static INativeImageStructHandler imageStructHandler; - internal static INativeMethodInfoStructHandler methodInfoStructHandler; - internal static INativeParameterInfoStructHandler parameterInfoStructHandler; - internal static INativePropertyInfoStructHandler propertyInfoStructHandler; - internal static INativeTypeStructHandler typeStructHandler; - - static UnityVersionHandler() - { - var allTypes = GetAllTypesSafe(); - var interfacesOfInterest = allTypes.Where(t => - t.IsInterface && typeof(INativeStructHandler).IsAssignableFrom(t) && t != typeof(INativeStructHandler)) - .ToArray(); - InterfacesOfInterest = interfacesOfInterest; - - foreach (var i in interfacesOfInterest) VersionedHandlers[i] = new List<(Version Version, object Handler)>(); - - foreach (var handlerImpl in allTypes.Where(t => - !t.IsAbstract && interfacesOfInterest.Any(i => i.IsAssignableFrom(t)))) - foreach (var startVersion in handlerImpl.GetCustomAttributes()) - { - var instance = Activator.CreateInstance(handlerImpl); - foreach (var i in handlerImpl.GetInterfaces()) - if (interfacesOfInterest.Contains(i)) - VersionedHandlers[i].Add((Version.Parse(startVersion.StartVersion), instance)); - } - - foreach (var handlerList in VersionedHandlers.Values) - handlerList.Sort((a, b) => -a.Version.CompareTo(b.Version)); - - RecalculateHandlers(); - } - - public static bool HasGetMethodFromReflection { get; private set; } - public static bool HasShimForGetMethod { get; private set; } - public static bool IsMetadataV29OrHigher { get; private set; } - - // Version since which extra_arg is set to invoke_multicast, necessitating constructor calls - public static bool MustUseDelegateConstructor => IsMetadataV29OrHigher; - - internal static void RecalculateHandlers() - { - Handlers.Clear(); - var unityVersion = Il2CppInteropRuntime.Instance.UnityVersion; - - foreach (var type in InterfacesOfInterest) - foreach (var valueTuple in VersionedHandlers[type]) - { - if (valueTuple.Version > unityVersion) continue; - - Handlers[type] = valueTuple.Handler; - break; - } - - HasGetMethodFromReflection = unityVersion > new Version(2018, 1, 0); - IsMetadataV29OrHigher = unityVersion >= new Version(2021, 2, 0); - - HasShimForGetMethod = unityVersion >= new Version(2020, 3, 41) || IsMetadataV29OrHigher; - - assemblyStructHandler = GetHandler(); - assemblyNameStructHandler = GetHandler(); - classStructHandler = GetHandler(); - eventInfoStructHandler = GetHandler(); - exceptionStructHandler = GetHandler(); - fieldInfoStructHandler = GetHandler(); - imageStructHandler = GetHandler(); - methodInfoStructHandler = GetHandler(); - parameterInfoStructHandler = GetHandler(); - propertyInfoStructHandler = GetHandler(); - typeStructHandler = GetHandler(); - } - - private static T GetHandler() - { - if (Handlers.TryGetValue(typeof(T), out var result)) - return (T)result; - - Logger.Instance.LogError("No direct for {TypeFullName} found for Unity {UnityVersion}; this likely indicates a severe error somewhere", typeof(T).FullName, Il2CppInteropRuntime.Instance.UnityVersion); - - throw new ApplicationException("No handler"); - } - - private static Type[] GetAllTypesSafe() - { - return typeof(UnityVersionHandler).Assembly.GetTypesSafe(); - } - - //Assemblies - public static INativeAssemblyStruct NewAssembly() - { - return assemblyStructHandler.CreateNewStruct(); - } - - public static unsafe INativeAssemblyStruct Wrap(Il2CppAssembly* assemblyPointer) - { - return assemblyStructHandler.Wrap(assemblyPointer); - } - - public static int AssemblySize() - { - return assemblyStructHandler.Size(); - } - - //Assembly Names - public static INativeAssemblyNameStruct NewAssemblyName() - { - return assemblyNameStructHandler.CreateNewStruct(); - } - - public static unsafe INativeAssemblyNameStruct Wrap(Il2CppAssemblyName* assemblyNamePointer) - { - return assemblyNameStructHandler.Wrap(assemblyNamePointer); - } - - public static int AssemblyNameSize() - { - return assemblyNameStructHandler.Size(); - } - - //Classes - public static INativeClassStruct NewClass(int vTableSlots) - { - return classStructHandler.CreateNewStruct(vTableSlots); - } - - public static unsafe INativeClassStruct Wrap(Il2CppClass* classPointer) - { - return classStructHandler.Wrap(classPointer); - } - - public static int ClassSize() - { - return classStructHandler.Size(); - } - - //Events - public static INativeEventInfoStruct NewEvent() - { - return eventInfoStructHandler.CreateNewStruct(); - } - - public static unsafe INativeEventInfoStruct Wrap(Il2CppEventInfo* eventInfoPointer) - { - return eventInfoStructHandler.Wrap(eventInfoPointer); - } - - public static int EventSize() - { - return eventInfoStructHandler.Size(); - } - - //Exceptions - public static INativeExceptionStruct NewException() - { - return exceptionStructHandler.CreateNewStruct(); - } - - public static unsafe INativeExceptionStruct Wrap(Il2CppException* exceptionPointer) - { - return exceptionStructHandler.Wrap(exceptionPointer); - } - - public static int ExceptionSize() - { - return exceptionStructHandler.Size(); - } - - //Fields - public static INativeFieldInfoStruct NewField() - { - return fieldInfoStructHandler.CreateNewStruct(); - } - - public static unsafe INativeFieldInfoStruct Wrap(Il2CppFieldInfo* fieldInfoPointer) - { - return fieldInfoStructHandler.Wrap(fieldInfoPointer); - } - - public static int FieldInfoSize() - { - return fieldInfoStructHandler.Size(); - } - - - //Images - public static INativeImageStruct NewImage() - { - return imageStructHandler.CreateNewStruct(); - } - - public static unsafe INativeImageStruct Wrap(Il2CppImage* imagePointer) - { - return imageStructHandler.Wrap(imagePointer); - } - - public static int ImageSize() - { - return imageStructHandler.Size(); - } - - //Methods - public static INativeMethodInfoStruct NewMethod() - { - return methodInfoStructHandler.CreateNewStruct(); - } - - public static unsafe INativeMethodInfoStruct Wrap(Il2CppMethodInfo* methodPointer) - { - return methodInfoStructHandler.Wrap(methodPointer); - } - - public static int MethodSize() - { - return methodInfoStructHandler.Size(); - } - - //Parameters - public static unsafe Il2CppParameterInfo*[] NewMethodParameterArray(int count) - { - return parameterInfoStructHandler.CreateNewParameterInfoArray(count); - } - - public static unsafe INativeParameterInfoStruct Wrap(Il2CppParameterInfo* parameterInfo) - { - return parameterInfoStructHandler.Wrap(parameterInfo); - } - - public static unsafe INativeParameterInfoStruct Wrap(Il2CppParameterInfo* parameterInfo, int index) - { - return parameterInfoStructHandler.Wrap(parameterInfo, index); - } - - public static bool ParameterInfoHasNamePosToken() - { - return parameterInfoStructHandler.HasNamePosToken; - } - - - //Properties - public static INativePropertyInfoStruct NewProperty() - { - return propertyInfoStructHandler.CreateNewStruct(); - } - - public static unsafe INativePropertyInfoStruct Wrap(Il2CppPropertyInfo* propertyInfoPointer) - { - return propertyInfoStructHandler.Wrap(propertyInfoPointer); - } - - public static int ParameterInfoSize() - { - return parameterInfoStructHandler.Size(); - } - - //Types - public static INativeTypeStruct NewType() - { - return typeStructHandler.CreateNewStruct(); - } - - public static unsafe INativeTypeStruct Wrap(Il2CppTypeStruct* typePointer) - { - return typeStructHandler.Wrap(typePointer); - } - - public static int TypeSize() - { - return typeStructHandler.Size(); - } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Interfaces.cs deleted file mode 100644 index 9ecb5ea6..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Interfaces.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly; - -public interface INativeAssemblyStructHandler : INativeStructHandler -{ - INativeAssemblyStruct CreateNewStruct(); - unsafe INativeAssemblyStruct Wrap(Il2CppAssembly* assemblyPointer); -} - -public interface INativeAssemblyStruct : INativeStruct -{ - unsafe Il2CppAssembly* AssemblyPointer { get; } - - unsafe ref Il2CppImage* Image { get; } - - INativeAssemblyNameStruct Name { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/Interfaces.cs deleted file mode 100644 index bb55c1fc..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/Interfaces.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; - -public interface INativeAssemblyNameStructHandler : INativeStructHandler -{ - INativeAssemblyNameStruct CreateNewStruct(); - unsafe INativeAssemblyNameStruct Wrap(Il2CppAssemblyName* assemblyNamePointer); -} - -public interface INativeAssemblyNameStruct : INativeStruct -{ - unsafe Il2CppAssemblyName* AssemblyNamePointer { get; } - ref IntPtr Name { get; } - ref IntPtr Culture { get; } - ref IntPtr PublicKey { get; } - ref int Major { get; } - ref int Minor { get; } - ref int Build { get; } - ref int Revision { get; } - ref ulong PublicKeyToken { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Interfaces.cs deleted file mode 100644 index 930da95f..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Interfaces.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class; - -public interface INativeClassStructHandler : INativeStructHandler -{ - INativeClassStruct CreateNewStruct(int vTableSlots); - unsafe INativeClassStruct Wrap(Il2CppClass* classPointer); -} - -public interface INativeClassStruct : INativeStruct -{ - unsafe Il2CppClass* ClassPointer { get; } - IntPtr VTable { get; } - - ref uint InstanceSize { get; } - ref ushort VtableCount { get; } - ref ushort InterfaceCount { get; } - ref ushort InterfaceOffsetsCount { get; } - ref byte TypeHierarchyDepth { get; } - ref int NativeSize { get; } - ref uint ActualSize { get; } - ref ushort MethodCount { get; } - ref ushort FieldCount { get; } - ref Il2CppClassAttributes Flags { get; } - - bool ValueType { get; set; } - bool EnumType { get; set; } - bool IsGeneric { get; set; } - bool Initialized { get; set; } - bool InitializedAndNoError { get; set; } - bool SizeInited { get; set; } - bool HasFinalize { get; set; } - bool IsVtableInitialized { get; set; } - - ref IntPtr Name { get; } - ref IntPtr Namespace { get; } - - INativeTypeStruct ByValArg { get; } - INativeTypeStruct ThisArg { get; } - - unsafe ref Il2CppImage* Image { get; } - unsafe ref Il2CppClass* Parent { get; } - unsafe ref Il2CppClass* ElementClass { get; } - unsafe ref Il2CppClass* CastClass { get; } - unsafe ref Il2CppClass* DeclaringType { get; } - unsafe ref Il2CppClass* Class { get; } - - unsafe ref Il2CppFieldInfo* Fields { get; } - unsafe ref Il2CppMethodInfo** Methods { get; } - unsafe ref Il2CppClass** ImplementedInterfaces { get; } - unsafe ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets { get; } - unsafe ref Il2CppClass** TypeHierarchy { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/Interfaces.cs deleted file mode 100644 index e5514719..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/Interfaces.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.EventInfo; - -public interface INativeEventInfoStructHandler : INativeStructHandler -{ - INativeEventInfoStruct CreateNewStruct(); - unsafe INativeEventInfoStruct Wrap(Il2CppEventInfo* eventInfoPointer); -} - -public interface INativeEventInfoStruct : INativeStruct -{ - unsafe Il2CppEventInfo* EventInfoPointer { get; } - - ref IntPtr Name { get; } - - unsafe ref Il2CppTypeStruct* EventType { get; } - - unsafe ref Il2CppClass* Parent { get; } - - unsafe ref Il2CppMethodInfo* Add { get; } - - unsafe ref Il2CppMethodInfo* Remove { get; } - - unsafe ref Il2CppMethodInfo* Raise { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Interfaces.cs deleted file mode 100644 index 16bc3b45..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Interfaces.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Exception; - -public interface INativeExceptionStructHandler : INativeStructHandler -{ - INativeExceptionStruct CreateNewStruct(); - unsafe INativeExceptionStruct Wrap(Il2CppException* exceptionPointer); -} - -public interface INativeExceptionStruct : INativeStruct -{ - unsafe Il2CppException* ExceptionPointer { get; } - - unsafe ref Il2CppException* InnerException { get; } - - unsafe ref Il2CppString* Message { get; } - - unsafe ref Il2CppString* HelpLink { get; } - - unsafe ref Il2CppString* ClassName { get; } - - unsafe ref Il2CppString* StackTrace { get; } - - unsafe ref Il2CppString* RemoteStackTrace { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/Interfaces.cs deleted file mode 100644 index 03a60831..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/Interfaces.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.FieldInfo; - -public interface INativeFieldInfoStructHandler : INativeStructHandler -{ - INativeFieldInfoStruct CreateNewStruct(); - unsafe INativeFieldInfoStruct Wrap(Il2CppFieldInfo* fieldInfoPointer); -} - -public interface INativeFieldInfoStruct : INativeStruct -{ - unsafe Il2CppFieldInfo* FieldInfoPointer { get; } - - ref IntPtr Name { get; } - - unsafe ref Il2CppTypeStruct* Type { get; } - - unsafe ref Il2CppClass* Parent { get; } - - ref int Offset { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Interfaces.cs deleted file mode 100644 index a0d6265b..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Interfaces.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image; - -public interface INativeImageStructHandler : INativeStructHandler -{ - INativeImageStruct CreateNewStruct(); - unsafe INativeImageStruct Wrap(Il2CppImage* imagePointer); -} - -public interface INativeImageStruct : INativeStruct -{ - unsafe Il2CppImage* ImagePointer { get; } - - unsafe ref Il2CppAssembly* Assembly { get; } - - ref byte Dynamic { get; } - - ref IntPtr Name { get; } - - bool HasNameNoExt { get; } - - ref IntPtr NameNoExt { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/Interfaces.cs deleted file mode 100644 index 8c553178..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/Interfaces.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo; - -public interface INativeMethodInfoStructHandler : INativeStructHandler -{ - INativeMethodInfoStruct CreateNewStruct(); - unsafe INativeMethodInfoStruct Wrap(Il2CppMethodInfo* methodPointer); -} - -public interface INativeMethodInfoStruct : INativeStruct -{ - unsafe Il2CppMethodInfo* MethodInfoPointer { get; } - ref IntPtr Name { get; } - ref ushort Slot { get; } - ref IntPtr MethodPointer { get; } - - ref IntPtr VirtualMethodPointer { get; } - unsafe ref Il2CppClass* Class { get; } - ref IntPtr InvokerMethod { get; } - unsafe ref Il2CppTypeStruct* ReturnType { get; } - ref Il2CppMethodFlags Flags { get; } - ref byte ParametersCount { get; } - unsafe ref Il2CppParameterInfo* Parameters { get; } - ref uint Token { get; } - bool IsGeneric { get; set; } - bool IsInflated { get; set; } - bool IsMarshalledFromNative { get; set; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/Interfaces.cs deleted file mode 100644 index ec14427e..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/Interfaces.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.ParameterInfo; - -public interface INativeParameterInfoStructHandler : INativeStructHandler -{ - unsafe Il2CppParameterInfo*[] CreateNewParameterInfoArray(int paramCount); - unsafe INativeParameterInfoStruct Wrap(Il2CppParameterInfo* paramInfoPointer); - unsafe INativeParameterInfoStruct Wrap(Il2CppParameterInfo* paramInfoListBegin, int index); - bool HasNamePosToken { get; } -#if DEBUG - string GetName(); -#endif -} - -public interface INativeParameterInfoStruct : INativeStruct -{ - unsafe Il2CppParameterInfo* ParameterInfoPointer { get; } - bool HasNamePosToken { get; } - ref IntPtr Name { get; } - ref int Position { get; } - ref uint Token { get; } - unsafe ref Il2CppTypeStruct* ParameterType { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_16_0.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_16_0.cs deleted file mode 100644 index c718abce..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_16_0.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.ParameterInfo; - -[ApplicableToUnityVersionsSince("5.3.0")] -internal class NativeParameterInfoStructHandler_16_0 : INativeParameterInfoStructHandler -{ - public unsafe int Size() - { - return sizeof(Il2CppParameterInfo_16_0); - } - - public unsafe Il2CppParameterInfo*[] CreateNewParameterInfoArray(int paramCount) - { - var ptr = (Il2CppParameterInfo_16_0*)Marshal.AllocHGlobal(Marshal.SizeOf() * - paramCount); - var res = new Il2CppParameterInfo*[paramCount]; - for (var i = 0; i < paramCount; i++) - { - ptr[i] = default; - res[i] = (Il2CppParameterInfo*)&ptr[i]; - } - - return res; - } - - public unsafe INativeParameterInfoStruct? Wrap(Il2CppParameterInfo* paramInfoPointer) - { - if ((IntPtr)paramInfoPointer == IntPtr.Zero) return null; - return new NativeParameterInfoStructWrapper((IntPtr)paramInfoPointer); - } - - public unsafe INativeParameterInfoStruct? Wrap(Il2CppParameterInfo* paramInfoListBegin, int index) - { - if ((IntPtr)paramInfoListBegin == IntPtr.Zero) return null; - return new NativeParameterInfoStructWrapper((IntPtr)paramInfoListBegin + - Marshal.SizeOf() * index); - } - - public bool HasNamePosToken => true; - -#if DEBUG - public string GetName() => "NativeParameterInfoStructHandler_16_0"; -#endif - - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct Il2CppParameterInfo_16_0 - { - public IntPtr name; // const char* - public int position; - public uint token; - public int customAttributeIndex; - public Il2CppTypeStruct* parameter_type; // const - } - - internal unsafe class NativeParameterInfoStructWrapper : INativeParameterInfoStruct - { - public NativeParameterInfoStructWrapper(IntPtr pointer) - { - Pointer = pointer; - } - - private Il2CppParameterInfo_16_0* NativeParameter => (Il2CppParameterInfo_16_0*)Pointer; - - public IntPtr Pointer { get; } - - public Il2CppParameterInfo* ParameterInfoPointer => (Il2CppParameterInfo*)Pointer; - - public bool HasNamePosToken => true; - - public ref IntPtr Name => ref NativeParameter->name; - - public ref int Position => ref NativeParameter->position; - - public ref uint Token => ref NativeParameter->token; - - public ref Il2CppTypeStruct* ParameterType => ref NativeParameter->parameter_type; - } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_24_1.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_24_1.cs deleted file mode 100644 index a9cc117c..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_24_1.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.ParameterInfo; - -[ApplicableToUnityVersionsSince("2018.3.0")] -internal class NativeParameterInfoStructHandler_24_1 : INativeParameterInfoStructHandler -{ - public unsafe int Size() - { - return sizeof(Il2CppParameterInfo_24_1); - } - - public unsafe Il2CppParameterInfo*[] CreateNewParameterInfoArray(int paramCount) - { - var ptr = (Il2CppParameterInfo_24_1*)Marshal.AllocHGlobal(Marshal.SizeOf() * - paramCount); - var res = new Il2CppParameterInfo*[paramCount]; - for (var i = 0; i < paramCount; i++) - { - ptr[i] = default; - res[i] = (Il2CppParameterInfo*)&ptr[i]; - } - - return res; - } - - public unsafe INativeParameterInfoStruct? Wrap(Il2CppParameterInfo* paramInfoPointer) - { - if ((IntPtr)paramInfoPointer == IntPtr.Zero) return null; - return new NativeParameterInfoStructWrapper((IntPtr)paramInfoPointer); - } - - public unsafe INativeParameterInfoStruct? Wrap(Il2CppParameterInfo* paramInfoListBegin, int index) - { - if ((IntPtr)paramInfoListBegin == IntPtr.Zero) return null; - return new NativeParameterInfoStructWrapper((IntPtr)paramInfoListBegin + - Marshal.SizeOf() * index); - } - - public bool HasNamePosToken => true; - -#if DEBUG - public string GetName() => "NativeParameterInfoStructHandler_24_1"; -#endif - - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct Il2CppParameterInfo_24_1 - { - public IntPtr name; // const char* - public int position; - public uint token; - public Il2CppTypeStruct* parameter_type; // const - } - - internal unsafe class NativeParameterInfoStructWrapper : INativeParameterInfoStruct - { - public NativeParameterInfoStructWrapper(IntPtr pointer) - { - Pointer = pointer; - } - - private Il2CppParameterInfo_24_1* NativeParameter => (Il2CppParameterInfo_24_1*)Pointer; - - public IntPtr Pointer { get; } - - public Il2CppParameterInfo* ParameterInfoPointer => (Il2CppParameterInfo*)Pointer; - - public bool HasNamePosToken => true; - - public ref IntPtr Name => ref NativeParameter->name; - - public ref int Position => ref NativeParameter->position; - - public ref uint Token => ref NativeParameter->token; - - public ref Il2CppTypeStruct* ParameterType => ref NativeParameter->parameter_type; - } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_27_3.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_27_3.cs deleted file mode 100644 index 84d7dd75..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/ParameterInfo/ParameterInfo_27_3.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.ParameterInfo; - -[ApplicableToUnityVersionsSince("2021.2.0")] -internal class NativeParameterInfoStructHandler_27_3 : INativeParameterInfoStructHandler -{ - public unsafe int Size() - { - return sizeof(Il2CppParameterInfo_27_3); - } - - public unsafe Il2CppParameterInfo*[] CreateNewParameterInfoArray(int paramCount) - { - var ptr = (Il2CppParameterInfo_27_3*)Marshal.AllocHGlobal(Marshal.SizeOf() * - paramCount); - var res = new Il2CppParameterInfo*[paramCount]; - for (var i = 0; i < paramCount; i++) - { - ptr[i] = default; - res[i] = (Il2CppParameterInfo*)&ptr[i]; - } - - return res; - } - - public unsafe INativeParameterInfoStruct? Wrap(Il2CppParameterInfo* paramInfoPointer) - { - if ((IntPtr)paramInfoPointer == IntPtr.Zero) return null; - return new NativeParameterInfoStructWrapper((IntPtr)paramInfoPointer); - } - - public unsafe INativeParameterInfoStruct? Wrap(Il2CppParameterInfo* paramInfoListBegin, int index) - { - if ((IntPtr)paramInfoListBegin == IntPtr.Zero) return null; - return new NativeParameterInfoStructWrapper((IntPtr)paramInfoListBegin + - Marshal.SizeOf() * index); - } - - public bool HasNamePosToken => false; - -#if DEBUG - public string GetName() => "NativeParameterInfoStructHandler_27_3"; -#endif - - //Doesn't actually exist; just using this for type pointer storage in MethodInfo 27_3 + - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct Il2CppParameterInfo_27_3 - { - public Il2CppTypeStruct* parameter_type; - } - - internal unsafe class NativeParameterInfoStructWrapper : INativeParameterInfoStruct - { - public NativeParameterInfoStructWrapper(IntPtr pointer) - { - Pointer = pointer; - } - - private Il2CppParameterInfo_27_3* NativeParameter => (Il2CppParameterInfo_27_3*)Pointer; - - public IntPtr Pointer { get; } - - public Il2CppParameterInfo* ParameterInfoPointer => (Il2CppParameterInfo*)Pointer; - - public bool HasNamePosToken => false; - - public ref IntPtr Name => throw new NotSupportedException("ParameterInfo does not exist in Unity 2021.2.0+"); - - public ref int Position => throw new NotSupportedException("ParameterInfo does not exist in Unity 2021.2.0+"); - - public ref uint Token => throw new NotSupportedException("ParameterInfo does not exist in Unity 2021.2.0+"); - - public ref Il2CppTypeStruct* ParameterType => ref NativeParameter->parameter_type; - } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/Interfaces.cs deleted file mode 100644 index 9caca5b7..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/Interfaces.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.PropertyInfo; - -public interface INativePropertyInfoStructHandler : INativeStructHandler -{ - INativePropertyInfoStruct CreateNewStruct(); - unsafe INativePropertyInfoStruct Wrap(Il2CppPropertyInfo* propertyInfoPointer); -} - -public interface INativePropertyInfoStruct : INativeStruct -{ - unsafe Il2CppPropertyInfo* PropertyInfoPointer { get; } - - ref IntPtr Name { get; } - - unsafe ref Il2CppClass* Parent { get; } - - unsafe ref Il2CppMethodInfo* Get { get; } - - unsafe ref Il2CppMethodInfo* Set { get; } - - ref uint Attrs { get; } -} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Interfaces.cs b/Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Interfaces.cs deleted file mode 100644 index 31d73eb3..00000000 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Interfaces.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; - -public interface INativeTypeStructHandler : INativeStructHandler -{ - INativeTypeStruct CreateNewStruct(); - unsafe INativeTypeStruct Wrap(Il2CppTypeStruct* typePointer); -} - -public interface INativeTypeStruct : INativeStruct -{ - unsafe Il2CppTypeStruct* TypePointer { get; } - - ref IntPtr Data { get; } - - ref ushort Attrs { get; } - - ref Il2CppTypeEnum Type { get; } - - bool ByRef { get; set; } - - bool Pinned { get; set; } - bool ValueType { get; set; } -} diff --git a/Il2CppInterop.Runtime/RuntimeInvoke.cs b/Il2CppInterop.Runtime/RuntimeInvoke.cs new file mode 100644 index 00000000..c6b60c9c --- /dev/null +++ b/Il2CppInterop.Runtime/RuntimeInvoke.cs @@ -0,0 +1,192 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using Il2CppInterop.Common; +using Il2CppInterop.Runtime.Injection; +using Il2CppInterop.Runtime.InteropTypes; +using Microsoft.Extensions.Logging; +using Il2CppException = Il2CppInterop.Runtime.Exceptions.Il2CppException; + +namespace Il2CppInterop.Runtime; + +public static unsafe class RuntimeInvoke +{ + private static IntPtr Il2CppRuntimeInvoke(IntPtr method, IntPtr obj, void** parameters) + { + ArgumentNullException.ThrowIfNull(method.ToPointer(), nameof(method)); + IntPtr exception = default; + var result = IL2CPP.il2cpp_runtime_invoke(method, obj, parameters, ref exception); + Il2CppException.RaiseExceptionIfNecessary(exception); + return result; + } + + public static void InvokeAction(IntPtr method, IntPtr obj, void** parameters) + { + Il2CppRuntimeInvoke(method, obj, parameters); + } + + public static TResult? InvokeFunction(IntPtr method, IntPtr obj, void** parameters) + where TResult : IIl2CppType + { + var result = Il2CppRuntimeInvoke(method, obj, parameters); + if (IsPointerOrByRef()) + { + // Pointers and by refs are returned directly. + return Unsafe.As(ref result); + } + else if (IsValueType()) + { + // This is a performance optimization. The other code path would also return the correct result. + return TResult.UnboxNative((ObjectPointer)result); + } + else + { + return TResult.Unbox(Il2CppObjectPool.Get(result)); + } + } + + public static IntPtr GetPointerForThis(ByReference @this) + where T : IIl2CppType + { + if (typeof(T).IsValueType) + { + return new IntPtr(@this.ToPointer()); + } + else + { + return (IntPtr)NativeBoxing.Box(@this.GetValue()); + } + } + + public static IntPtr GetPointerForParameter(ByReference parameter) + where T : IIl2CppType + { + if (IsPointerOrByRef()) + { + // Pointer to pointer, which is passed directly + return *(IntPtr*)parameter.ToPointer(); + } + else if (IsValueType()) + { + // Pointer to value type data + return new IntPtr(parameter.ToPointer()); + } + else + { + // Pointer to object pointer, which is passed directly + return *(IntPtr*)parameter.ToPointer(); + } + } + + private static bool IsPointerOrByRef() + { + return IsPointer() || IsByRef(); + } + + private static bool IsPointer() + { + return typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Pointer<>); + } + + private static bool IsByRef() + { + return typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(ByReference<>); + } + + private static bool IsValueType() + { + return typeof(T).IsValueType; + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + public static T ResolveICall(string signature) where T : Delegate + { + var icallPtr = IL2CPP.il2cpp_resolve_icall(signature); + if (icallPtr == nint.Zero) + { + Logger.Instance.LogTrace("ICall {Signature} not resolved", signature); + return GenerateDelegateForMissingICall(signature); + } + + return GenerateDelegateForICall(icallPtr); + } + + [RequiresDynamicCode("")] + private static T GenerateDelegateForMissingICall<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>(string signature) where T : Delegate + { + var invoke = typeof(T).GetMethod("Invoke")!; + + var trampoline = new DynamicMethod("(missing icall delegate) " + typeof(T).FullName, + invoke.ReturnType, invoke.GetParameters().Select(it => it.ParameterType).ToArray(), typeof(RuntimeInvoke), true); + var bodyBuilder = trampoline.GetILGenerator(); + + bodyBuilder.Emit(OpCodes.Ldstr, $"ICall with signature {signature} was not resolved"); + bodyBuilder.Emit(OpCodes.Newobj, typeof(Exception).GetConstructor([typeof(string)])!); + bodyBuilder.Emit(OpCodes.Throw); + + return (T)trampoline.CreateDelegate(typeof(T)); + } + + [RequiresUnreferencedCode("")] + [RequiresDynamicCode("")] + private static T GenerateDelegateForICall<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>(nint icallPtr) where T : Delegate + { + var invoke = typeof(T).GetMethod("Invoke")!; + + var trampoline = new DynamicMethod("(icall delegate) " + typeof(T).FullName, + invoke.ReturnType, invoke.GetParameters().Select(it => it.ParameterType).ToArray(), typeof(RuntimeInvoke), true); + var bodyBuilder = trampoline.GetILGenerator(); + + var parameters = invoke.GetParameters(); + var parameterTypes = new Type[parameters.Length]; + var locals = new LocalBuilder[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var parameterType = parameter.ParameterType; + + var nativeStruct = TrampolineBuilder.GetNativeType(parameterType); + parameterTypes[i] = nativeStruct; + + var nativeLocal = bodyBuilder.DeclareLocal(nativeStruct); + locals[i] = nativeLocal; + + bodyBuilder.Emit(OpCodes.Ldarg, i); + bodyBuilder.Emit(OpCodes.Ldloca, nativeLocal); + bodyBuilder.Emit(OpCodes.Call, typeof(Il2CppType).GetMethod(nameof(Il2CppType.WriteToPointer))!.MakeGenericMethod(parameterType)); + } + + foreach (var local in locals) + { + bodyBuilder.Emit(OpCodes.Ldloc, local); + } + + bodyBuilder.Emit(OpCodes.Ldc_I8, icallPtr); + bodyBuilder.Emit(OpCodes.Conv_I); + + if (invoke.ReturnType == typeof(void)) + { + bodyBuilder.EmitCalli(OpCodes.Calli, CallingConventions.Standard, typeof(void), parameterTypes, null); + } + else + { + var returnType = invoke.ReturnType; + var nativeStruct = TrampolineBuilder.GetNativeType(returnType); + + bodyBuilder.EmitCalli(OpCodes.Calli, CallingConventions.Standard, nativeStruct, parameterTypes, null); + + var returnLocal = bodyBuilder.DeclareLocal(nativeStruct); + bodyBuilder.Emit(OpCodes.Stloc, returnLocal); + + bodyBuilder.Emit(OpCodes.Ldloca, returnLocal); + bodyBuilder.Emit(OpCodes.Call, typeof(Il2CppType).GetMethod(nameof(Il2CppType.ReadFromPointer))!.MakeGenericMethod(returnType)); + } + bodyBuilder.Emit(OpCodes.Ret); + + return (T)trampoline.CreateDelegate(typeof(T)); + } +} diff --git a/Il2CppInterop.Runtime/RuntimeReflectionHelper.cs b/Il2CppInterop.Runtime/RuntimeReflectionHelper.cs deleted file mode 100644 index bcd93b98..00000000 --- a/Il2CppInterop.Runtime/RuntimeReflectionHelper.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Il2CppSystem.Reflection; -using RuntimeTypeHandle = Il2CppSystem.RuntimeTypeHandle; -using Type = Il2CppSystem.Type; - -namespace Il2CppInterop.Runtime; - -public static class RuntimeReflectionHelper -{ - public static IntPtr GetNestedTypeViaReflection(IntPtr enclosingClass, string nestedTypeName) - { - var reflectionType = Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(enclosingClass)); - var nestedType = reflectionType.GetNestedType(nestedTypeName, BindingFlags.Public | BindingFlags.NonPublic); - - return nestedType != null ? IL2CPP.il2cpp_class_from_system_type(nestedType.Pointer) : IntPtr.Zero; - } - - public static RuntimeTypeHandle GetRuntimeTypeHandle() - { - return Il2CppType.Of().TypeHandle; - } -} diff --git a/Il2CppInterop.Runtime/RuntimeSpecificsStore.cs b/Il2CppInterop.Runtime/RuntimeSpecificsStore.cs deleted file mode 100644 index 0edb90ca..00000000 --- a/Il2CppInterop.Runtime/RuntimeSpecificsStore.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Il2CppInterop.Runtime; - -public static class RuntimeSpecificsStore -{ - private static readonly ReaderWriterLockSlim Lock = new(); - private static readonly Dictionary WasInjectedStore = new(); - - public static bool IsInjected(IntPtr nativeClass) - { - Lock.EnterReadLock(); - try - { - return WasInjectedStore.TryGetValue(nativeClass, out var result) && result; - } - finally - { - Lock.ExitReadLock(); - } - } - - public static void SetClassInfo(IntPtr nativeClass, bool wasInjected) - { - Lock.EnterWriteLock(); - try - { - WasInjectedStore[nativeClass] = wasInjected; - } - finally - { - Lock.ExitWriteLock(); - } - } -} diff --git a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs index 3147986b..ad0ec305 100644 --- a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs +++ b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs @@ -1,18 +1,10 @@ -using System; +using AssetRipper.Primitives; using Il2CppInterop.Common.Host; -using Il2CppInterop.Common.XrefScans; using Il2CppInterop.Runtime.Injection; -using Il2CppInterop.Runtime.Runtime; -using Il2CppInterop.Runtime.XrefScans; +using Il2CppInterop.Runtime.Structs; namespace Il2CppInterop.Runtime.Startup; -public record RuntimeConfiguration -{ - public Version UnityVersion { get; init; } - public IDetourProvider DetourProvider { get; init; } -} - public sealed class Il2CppInteropRuntime : BaseHost { private Il2CppInteropRuntime() @@ -21,9 +13,9 @@ private Il2CppInteropRuntime() public static Il2CppInteropRuntime Instance => GetInstance(); - public Version UnityVersion { get; private init; } + public UnityVersion UnityVersion { get; private init; } - public IDetourProvider DetourProvider { get; private init; } + public IDetourProvider DetourProvider { get; private init; } = null!; public static Il2CppInteropRuntime Create(RuntimeConfiguration configuration) { @@ -33,7 +25,6 @@ public static Il2CppInteropRuntime Create(RuntimeConfiguration configuration) DetourProvider = configuration.DetourProvider }; SetInstance(res); - res.AddXrefScanner(); return res; } diff --git a/Il2CppInterop.Runtime/Startup/RuntimeConfiguration.cs b/Il2CppInterop.Runtime/Startup/RuntimeConfiguration.cs new file mode 100644 index 00000000..8a7b1edd --- /dev/null +++ b/Il2CppInterop.Runtime/Startup/RuntimeConfiguration.cs @@ -0,0 +1,10 @@ +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Injection; + +namespace Il2CppInterop.Runtime.Startup; + +public record RuntimeConfiguration +{ + public required UnityVersion UnityVersion { get; init; } + public required IDetourProvider DetourProvider { get; init; } +} diff --git a/Il2CppInterop.Runtime/Structs/ApplicableToUnityVersionsSinceAttribute.cs b/Il2CppInterop.Runtime/Structs/ApplicableToUnityVersionsSinceAttribute.cs new file mode 100644 index 00000000..9fc771a8 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/ApplicableToUnityVersionsSinceAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace Il2CppInterop.Runtime.Structs; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] +internal sealed class ApplicableToUnityVersionsSinceAttribute : Attribute +{ + public ApplicableToUnityVersionsSinceAttribute(string startVersion) + { + StartVersion = startVersion; + } + + public string StartVersion { get; } +} diff --git a/Il2CppInterop.Runtime/Runtime/NativeStructUtils.cs b/Il2CppInterop.Runtime/Structs/INativeStruct.cs similarity index 65% rename from Il2CppInterop.Runtime/Runtime/NativeStructUtils.cs rename to Il2CppInterop.Runtime/Structs/INativeStruct.cs index 26fefde5..0b27a347 100644 --- a/Il2CppInterop.Runtime/Runtime/NativeStructUtils.cs +++ b/Il2CppInterop.Runtime/Structs/INativeStruct.cs @@ -1,18 +1,11 @@ -using System; -using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs; -namespace Il2CppInterop.Runtime.Runtime; - -public static class NativeStructUtils +public interface INativeStruct +{ + nint Pointer { get; } +} +internal static class INativeStructExtensions { - public static IntPtr GetMethodInfoForMissingMethod(string methodName) - { - var methodInfo = UnityVersionHandler.NewMethod(); - methodInfo.Name = Marshal.StringToCoTaskMemUTF8(methodName); - methodInfo.Slot = ushort.MaxValue; - return methodInfo.Pointer; - } - public static unsafe bool CheckBit(this INativeStruct self, int startOffset, int bit) { var byteOffset = bit / 8; diff --git a/Il2CppInterop.Runtime/Structs/INativeStructHandler.cs b/Il2CppInterop.Runtime/Structs/INativeStructHandler.cs new file mode 100644 index 00000000..001c22e4 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/INativeStructHandler.cs @@ -0,0 +1,6 @@ +namespace Il2CppInterop.Runtime.Structs; + +public interface INativeStructHandler +{ + public int Size { get; } +} diff --git a/Il2CppInterop.Runtime/Runtime/Il2CppStructs.cs b/Il2CppInterop.Runtime/Structs/Il2CppStructs.cs similarity index 99% rename from Il2CppInterop.Runtime/Runtime/Il2CppStructs.cs rename to Il2CppInterop.Runtime/Structs/Il2CppStructs.cs index c2f48fc0..8deeb873 100644 --- a/Il2CppInterop.Runtime/Runtime/Il2CppStructs.cs +++ b/Il2CppInterop.Runtime/Structs/Il2CppStructs.cs @@ -8,7 +8,7 @@ // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Global -namespace Il2CppInterop.Runtime.Runtime; +namespace Il2CppInterop.Runtime.Structs; //Stub structs public struct Il2CppAssembly diff --git a/Il2CppInterop.Runtime/Structs/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/UnityVersionHandler.cs new file mode 100644 index 00000000..1a9c62c9 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/UnityVersionHandler.cs @@ -0,0 +1,243 @@ +using System; +using System.Runtime.InteropServices; +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Startup; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly; +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Class; +using Il2CppInterop.Runtime.Structs.VersionSpecific.EventInfo; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Exception; +using Il2CppInterop.Runtime.Structs.VersionSpecific.FieldInfo; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Image; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; +using Il2CppInterop.Runtime.Structs.VersionSpecific.ParameterInfo; +using Il2CppInterop.Runtime.Structs.VersionSpecific.PropertyInfo; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; + +namespace Il2CppInterop.Runtime.Structs; + +public static partial class UnityVersionHandler +{ + static UnityVersionHandler() + { + RecalculateHandlers(); + } + + public static bool HasGetMethodFromReflection { get; private set; } + public static bool HasShimForGetMethod { get; private set; } + public static bool IsMetadataV29OrHigher { get; private set; } + + // Version since which extra_arg is set to invoke_multicast, necessitating constructor calls + public static bool MustUseDelegateConstructor => IsMetadataV29OrHigher; + + internal static void RecalculateHandlers() + { + var unityVersion = Il2CppInteropRuntime.Instance.UnityVersion; + + HasGetMethodFromReflection = unityVersion.GreaterThanOrEquals(2018, 2, 0, UnityVersionType.Beta, 6); + IsMetadataV29OrHigher = unityVersion.GreaterThanOrEquals(2021, 2, 0); + + HasShimForGetMethod = unityVersion.GreaterThanOrEquals(2020, 3, 41) || IsMetadataV29OrHigher; + + SetAssemblyNameStructHandler(unityVersion); + SetAssemblyStructHandler(unityVersion); + SetClassStructHandler(unityVersion); + SetEventInfoStructHandler(unityVersion); + SetExceptionStructHandler(unityVersion); + SetFieldInfoStructHandler(unityVersion); + SetImageStructHandler(unityVersion); + SetMethodInfoStructHandler(unityVersion); + SetParameterInfoStructHandler(unityVersion); + SetPropertyInfoStructHandler(unityVersion); + SetTypeStructHandler(unityVersion); + } + + //Assemblies + public static INativeAssemblyStruct NewAssembly() + { + return AssemblyStructHandler.CreateNewStruct(); + } + + public static unsafe INativeAssemblyStruct Wrap(Il2CppAssembly* assemblyPointer) + { + return AssemblyStructHandler.Wrap(assemblyPointer); + } + + public static int AssemblySize() + { + return AssemblyStructHandler.Size; + } + + //Assembly Names + public static INativeAssemblyNameStruct NewAssemblyName() + { + return AssemblyNameStructHandler.CreateNewStruct(); + } + + public static unsafe INativeAssemblyNameStruct Wrap(Il2CppAssemblyName* assemblyNamePointer) + { + return AssemblyNameStructHandler.Wrap(assemblyNamePointer); + } + + public static int AssemblyNameSize() + { + return AssemblyNameStructHandler.Size; + } + + //Classes + public static INativeClassStruct NewClass(int vTableSlots) + { + return ClassStructHandler.CreateNewStruct(vTableSlots); + } + + public static unsafe INativeClassStruct Wrap(Il2CppClass* classPointer) + { + return ClassStructHandler.Wrap(classPointer); + } + + public static int ClassSize() + { + return ClassStructHandler.Size; + } + + //Events + public static INativeEventInfoStruct NewEvent() + { + return EventInfoStructHandler.CreateNewStruct(); + } + + public static unsafe INativeEventInfoStruct Wrap(Il2CppEventInfo* eventInfoPointer) + { + return EventInfoStructHandler.Wrap(eventInfoPointer); + } + + public static int EventSize() + { + return EventInfoStructHandler.Size; + } + + //Exceptions + public static INativeExceptionStruct NewException() + { + return ExceptionStructHandler.CreateNewStruct(); + } + + public static unsafe INativeExceptionStruct Wrap(Il2CppException* exceptionPointer) + { + return ExceptionStructHandler.Wrap(exceptionPointer); + } + + public static int ExceptionSize() + { + return ExceptionStructHandler.Size; + } + + //Fields + public static INativeFieldInfoStruct NewField() + { + return FieldInfoStructHandler.CreateNewStruct(); + } + + public static unsafe INativeFieldInfoStruct Wrap(Il2CppFieldInfo* fieldInfoPointer) + { + return FieldInfoStructHandler.Wrap(fieldInfoPointer); + } + + public static int FieldInfoSize() + { + return FieldInfoStructHandler.Size; + } + + //Images + public static INativeImageStruct NewImage() + { + return ImageStructHandler.CreateNewStruct(); + } + + public static unsafe INativeImageStruct Wrap(Il2CppImage* imagePointer) + { + return ImageStructHandler.Wrap(imagePointer); + } + + public static int ImageSize() + { + return ImageStructHandler.Size; + } + + //Methods + public static INativeMethodInfoStruct NewMethod() + { + return MethodInfoStructHandler.CreateNewStruct(); + } + + public static unsafe INativeMethodInfoStruct Wrap(Il2CppMethodInfo* methodPointer) + { + return MethodInfoStructHandler.Wrap(methodPointer); + } + + public static int MethodSize() + { + return MethodInfoStructHandler.Size; + } + + //Parameters + public static unsafe Il2CppParameterInfo*[] NewMethodParameterArray(int count) + { + if (count == 0) + return []; + + var elementSize = ParameterInfoStructHandler.Size; + var totalSize = elementSize * count; + var startPointer = Marshal.AllocHGlobal(totalSize); + new Span(startPointer.ToPointer(), totalSize).Clear(); + var result = new Il2CppParameterInfo*[count]; + for (var i = 0; i < count; i++) + { + result[i] = (Il2CppParameterInfo*)(startPointer + i * elementSize); + } + return result; + } + + public static unsafe INativeParameterInfoStruct Wrap(Il2CppParameterInfo* parameterInfo) + { + return ParameterInfoStructHandler.Wrap(parameterInfo); + } + + public static unsafe INativeParameterInfoStruct Wrap(Il2CppParameterInfo* parameterInfo, int index) + { + var address = (nint)parameterInfo + index * ParameterInfoStructHandler.Size; + return ParameterInfoStructHandler.Wrap((Il2CppParameterInfo*)address); + } + + //Properties + public static INativePropertyInfoStruct NewProperty() + { + return PropertyInfoStructHandler.CreateNewStruct(); + } + + public static unsafe INativePropertyInfoStruct Wrap(Il2CppPropertyInfo* propertyInfoPointer) + { + return PropertyInfoStructHandler.Wrap(propertyInfoPointer); + } + + public static int ParameterInfoSize() + { + return ParameterInfoStructHandler.Size; + } + + //Types + public static INativeTypeStruct NewType() + { + return TypeStructHandler.CreateNewStruct(); + } + + public static unsafe INativeTypeStruct Wrap(Il2CppTypeStruct* typePointer) + { + return TypeStructHandler.Wrap(typePointer); + } + + public static int TypeSize() + { + return TypeStructHandler.Size; + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_104_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_104_0.cs new file mode 100644 index 00000000..196db1fc --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_104_0.cs @@ -0,0 +1,49 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly +{ + [ApplicableToUnityVersionsSince("6000.5.0")] + public unsafe class NativeAssemblyStructHandler_104_0 : INativeAssemblyStructHandler + { + private NativeAssemblyStructHandler_104_0() + { + } + public INativeAssemblyStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppAssembly_104_0* _ = (Il2CppAssembly_104_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeAssemblyStruct Wrap(Il2CppAssembly* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeAssemblyStructHandler_104_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssembly_104_0); + internal unsafe struct Il2CppAssembly_104_0 + { + public Il2CppImage* image; + public uint token; + public uint moduleToken; + public int referencedAssemblyStart; + public int referencedAssemblyCount; + public NativeAssemblyNameStructHandler_24_1.Il2CppAssemblyName_24_1 aname; + } + internal class NativeStructWrapper : INativeAssemblyStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppAssembly_104_0* _ => (Il2CppAssembly_104_0*)Pointer; + public Il2CppAssembly* AssemblyPointer => (Il2CppAssembly*)Pointer; + public INativeAssemblyNameStruct Name + { + get => UnityVersionHandler.Wrap((Il2CppAssemblyName*)&_->aname); + set => _->aname = *(NativeAssemblyNameStructHandler_24_1.Il2CppAssemblyName_24_1*)Name.AssemblyNamePointer; + } + public ref Il2CppImage* Image => ref _->image; + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_16_0.cs similarity index 64% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_16_0.cs index 22c2ebcb..9e132bdc 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_16_0.cs @@ -1,15 +1,18 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativeAssemblyStructHandler_16_0 : INativeAssemblyStructHandler { - public int Size() => sizeof(Il2CppAssembly_16_0); + private NativeAssemblyStructHandler_16_0() + { + } public INativeAssemblyStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssembly_16_0* _ = (Il2CppAssembly_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,19 +20,20 @@ public INativeAssemblyStruct CreateNewStruct() public INativeAssemblyStruct Wrap(Il2CppAssembly* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssembly_16_0); internal unsafe struct Il2CppAssembly_16_0 { public int imageIndex; public int customAttributeIndex; public NativeAssemblyNameStructHandler_16_0.Il2CppAssemblyName_16_0 aname; } - internal class NativeStructWrapper : INativeAssemblyStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssembly_16_0* _ => (Il2CppAssembly_16_0*)Pointer; public Il2CppAssembly* AssemblyPointer => (Il2CppAssembly*)Pointer; public INativeAssemblyNameStruct Name @@ -37,9 +41,7 @@ public INativeAssemblyNameStruct Name get => UnityVersionHandler.Wrap((Il2CppAssemblyName*)&_->aname); set => _->aname = *(NativeAssemblyNameStructHandler_16_0.Il2CppAssemblyName_16_0*)Name.AssemblyNamePointer; } - public ref Il2CppImage* Image => throw new NotSupportedException(); + public ref Il2CppImage* Image => throw new System.NotSupportedException(); } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_20_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_20_0.cs similarity index 64% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_20_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_20_0.cs index 24fd7b72..972cec10 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_20_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_20_0.cs @@ -1,15 +1,18 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly { - [ApplicableToUnityVersionsSince("5.3.3")] + [ApplicableToUnityVersionsSince("5.3.2p1")] + [ApplicableToUnityVersionsSince("5.4.0b4")] public unsafe class NativeAssemblyStructHandler_20_0 : INativeAssemblyStructHandler { - public int Size() => sizeof(Il2CppAssembly_20_0); + private NativeAssemblyStructHandler_20_0() + { + } public INativeAssemblyStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssembly_20_0* _ = (Il2CppAssembly_20_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +20,10 @@ public INativeAssemblyStruct CreateNewStruct() public INativeAssemblyStruct Wrap(Il2CppAssembly* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyStructHandler_20_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssembly_20_0); internal unsafe struct Il2CppAssembly_20_0 { public int imageIndex; @@ -27,11 +32,10 @@ internal unsafe struct Il2CppAssembly_20_0 public int referencedAssemblyCount; public NativeAssemblyNameStructHandler_16_0.Il2CppAssemblyName_16_0 aname; } - internal class NativeStructWrapper : INativeAssemblyStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssembly_20_0* _ => (Il2CppAssembly_20_0*)Pointer; public Il2CppAssembly* AssemblyPointer => (Il2CppAssembly*)Pointer; public INativeAssemblyNameStruct Name @@ -39,9 +43,7 @@ public INativeAssemblyNameStruct Name get => UnityVersionHandler.Wrap((Il2CppAssemblyName*)&_->aname); set => _->aname = *(NativeAssemblyNameStructHandler_16_0.Il2CppAssemblyName_16_0*)Name.AssemblyNamePointer; } - public ref Il2CppImage* Image => throw new NotSupportedException(); + public ref Il2CppImage* Image => throw new System.NotSupportedException(); } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_0.cs similarity index 69% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_0.cs index b01f3c0c..65854098 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_0.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly { - [ApplicableToUnityVersionsSince("2018.1.0")] + [ApplicableToUnityVersionsSince("2018.1.0b5")] public unsafe class NativeAssemblyStructHandler_24_0 : INativeAssemblyStructHandler { - public int Size() => sizeof(Il2CppAssembly_24_0); + private NativeAssemblyStructHandler_24_0() + { + } public INativeAssemblyStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssembly_24_0* _ = (Il2CppAssembly_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeAssemblyStruct CreateNewStruct() public INativeAssemblyStruct Wrap(Il2CppAssembly* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssembly_24_0); internal unsafe struct Il2CppAssembly_24_0 { public Il2CppImage* image; @@ -27,11 +31,10 @@ internal unsafe struct Il2CppAssembly_24_0 public int referencedAssemblyCount; public NativeAssemblyNameStructHandler_24_0.Il2CppAssemblyName_24_0 aname; } - internal class NativeStructWrapper : INativeAssemblyStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssembly_24_0* _ => (Il2CppAssembly_24_0*)Pointer; public Il2CppAssembly* AssemblyPointer => (Il2CppAssembly*)Pointer; public INativeAssemblyNameStruct Name @@ -41,7 +44,5 @@ public INativeAssemblyNameStruct Name } public ref Il2CppImage* Image => ref _->image; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_1.cs similarity index 66% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_1.cs index 202e1d46..3ce09abd 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_1.cs @@ -1,15 +1,20 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly { [ApplicableToUnityVersionsSince("2018.3.0")] + [ApplicableToUnityVersionsSince("2019.1.0")] + [ApplicableToUnityVersionsSince("2020.1.0")] + [ApplicableToUnityVersionsSince("2020.2.0")] public unsafe class NativeAssemblyStructHandler_24_1 : INativeAssemblyStructHandler { - public int Size() => sizeof(Il2CppAssembly_24_1); + private NativeAssemblyStructHandler_24_1() + { + } public INativeAssemblyStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssembly_24_1* _ = (Il2CppAssembly_24_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +22,10 @@ public INativeAssemblyStruct CreateNewStruct() public INativeAssemblyStruct Wrap(Il2CppAssembly* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyStructHandler_24_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssembly_24_1); internal unsafe struct Il2CppAssembly_24_1 { public Il2CppImage* image; @@ -27,11 +34,10 @@ internal unsafe struct Il2CppAssembly_24_1 public int referencedAssemblyCount; public NativeAssemblyNameStructHandler_24_0.Il2CppAssemblyName_24_0 aname; } - internal class NativeStructWrapper : INativeAssemblyStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssembly_24_1* _ => (Il2CppAssembly_24_1*)Pointer; public Il2CppAssembly* AssemblyPointer => (Il2CppAssembly*)Pointer; public INativeAssemblyNameStruct Name @@ -41,7 +47,5 @@ public INativeAssemblyNameStruct Name } public ref Il2CppImage* Image => ref _->image; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_2.cs similarity index 66% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_2.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_2.cs index 06036de7..f017e9f4 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Assembly/Assembly_24_2.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Assembly_24_2.cs @@ -1,15 +1,20 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly { [ApplicableToUnityVersionsSince("2018.4.34")] + [ApplicableToUnityVersionsSince("2019.4.15")] + [ApplicableToUnityVersionsSince("2020.1.11")] + [ApplicableToUnityVersionsSince("2020.2.0b7")] public unsafe class NativeAssemblyStructHandler_24_2 : INativeAssemblyStructHandler { - public int Size() => sizeof(Il2CppAssembly_24_2); + private NativeAssemblyStructHandler_24_2() + { + } public INativeAssemblyStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssembly_24_2* _ = (Il2CppAssembly_24_2*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +22,10 @@ public INativeAssemblyStruct CreateNewStruct() public INativeAssemblyStruct Wrap(Il2CppAssembly* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyStructHandler_24_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssembly_24_2); internal unsafe struct Il2CppAssembly_24_2 { public Il2CppImage* image; @@ -27,11 +34,10 @@ internal unsafe struct Il2CppAssembly_24_2 public int referencedAssemblyCount; public NativeAssemblyNameStructHandler_24_1.Il2CppAssemblyName_24_1 aname; } - internal class NativeStructWrapper : INativeAssemblyStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssembly_24_2* _ => (Il2CppAssembly_24_2*)Pointer; public Il2CppAssembly* AssemblyPointer => (Il2CppAssembly*)Pointer; public INativeAssemblyNameStruct Name @@ -41,7 +47,5 @@ public INativeAssemblyNameStruct Name } public ref Il2CppImage* Image => ref _->image; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Interfaces.cs new file mode 100644 index 00000000..2535f8ee --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/Interfaces.cs @@ -0,0 +1,16 @@ +// +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly +{ + public interface INativeAssemblyStructHandler : INativeStructHandler + { + INativeAssemblyStruct CreateNewStruct(); + unsafe INativeAssemblyStruct Wrap(Il2CppAssembly* pointer); + } + public interface INativeAssemblyStruct : INativeStruct + { + unsafe Il2CppAssembly* AssemblyPointer { get; } + INativeAssemblyNameStruct Name { get; set; } + unsafe ref Il2CppImage* Image { get; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/UnityVersionHandler.cs new file mode 100644 index 00000000..be549872 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Assembly/UnityVersionHandler.cs @@ -0,0 +1,69 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Assembly; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetAssemblyStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(6000, 5, 0, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_104_0.Instance; + } + else if (version >= new UnityVersion(2020, 2, 0, UnityVersionType.Beta, 7)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2020, 2, 0, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2020, 1, 11, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2020, 1, 0, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2019, 4, 15, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2019, 1, 0, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2018, 4, 34, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Beta, 5)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 4)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_20_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 2, UnityVersionType.Patch, 1)) + { + AssemblyStructHandler = NativeAssemblyStructHandler_20_0.Instance; + } + else + { + AssemblyStructHandler = NativeAssemblyStructHandler_16_0.Instance; + } + } + private static INativeAssemblyStructHandler AssemblyStructHandler { get; set; } = NativeAssemblyStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_16_0.cs similarity index 68% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_16_0.cs index c814fc2a..7b5cb6f9 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_16_0.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName { [ApplicableToUnityVersionsSince("5.2.2")] public unsafe class NativeAssemblyNameStructHandler_16_0 : INativeAssemblyNameStructHandler { - public int Size() => sizeof(Il2CppAssemblyName_16_0); + private NativeAssemblyNameStructHandler_16_0() + { + } public INativeAssemblyNameStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssemblyName_16_0* _ = (Il2CppAssemblyName_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeAssemblyNameStruct CreateNewStruct() public INativeAssemblyNameStruct Wrap(Il2CppAssemblyName* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyNameStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssemblyName_16_0); internal unsafe struct Il2CppAssemblyName_16_0 { public int nameIndex; @@ -33,23 +37,20 @@ internal unsafe struct Il2CppAssemblyName_16_0 public int revision; public ulong publicKeyToken; } - internal class NativeStructWrapper : INativeAssemblyNameStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssemblyName_16_0* _ => (Il2CppAssemblyName_16_0*)Pointer; public Il2CppAssemblyName* AssemblyNamePointer => (Il2CppAssemblyName*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->nameIndex; - public ref IntPtr Culture => ref *(IntPtr*)&_->cultureIndex; - public ref IntPtr PublicKey => ref *(IntPtr*)&_->publicKeyIndex; + public ref nint Name => ref *(nint*)&_->nameIndex; + public ref nint Culture => ref *(nint*)&_->cultureIndex; + public ref nint PublicKey => ref *(nint*)&_->publicKeyIndex; public ref int Major => ref _->major; public ref int Minor => ref _->minor; public ref int Build => ref _->build; public ref int Revision => ref _->revision; public ref ulong PublicKeyToken => ref _->publicKeyToken; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_24_0.cs similarity index 62% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_24_0.cs index 2ef5ac77..31d91374 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_24_0.cs @@ -1,14 +1,19 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName { - [ApplicableToUnityVersionsSince("2018.1.0")] + [ApplicableToUnityVersionsSince("2018.1.0b5")] + [ApplicableToUnityVersionsSince("2019.1.0")] + [ApplicableToUnityVersionsSince("2020.1.0")] + [ApplicableToUnityVersionsSince("2020.2.0")] public unsafe class NativeAssemblyNameStructHandler_24_0 : INativeAssemblyNameStructHandler { - public int Size() => sizeof(Il2CppAssemblyName_24_0); + private NativeAssemblyNameStructHandler_24_0() + { + } public INativeAssemblyNameStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssemblyName_24_0* _ = (Il2CppAssemblyName_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +21,10 @@ public INativeAssemblyNameStruct CreateNewStruct() public INativeAssemblyNameStruct Wrap(Il2CppAssemblyName* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyNameStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssemblyName_24_0); internal unsafe struct Il2CppAssemblyName_24_0 { public byte* name; @@ -33,23 +40,20 @@ internal unsafe struct Il2CppAssemblyName_24_0 public int revision; public ulong public_key_token; } - internal class NativeStructWrapper : INativeAssemblyNameStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssemblyName_24_0* _ => (Il2CppAssemblyName_24_0*)Pointer; public Il2CppAssemblyName* AssemblyNamePointer => (Il2CppAssemblyName*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Culture => ref *(IntPtr*)&_->culture; - public ref IntPtr PublicKey => ref *(IntPtr*)&_->public_key; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Culture => ref *(nint*)&_->culture; + public ref nint PublicKey => ref *(nint*)&_->public_key; public ref int Major => ref _->major; public ref int Minor => ref _->minor; public ref int Build => ref _->build; public ref int Revision => ref _->revision; public ref ulong PublicKeyToken => ref _->public_key_token; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_24_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_24_1.cs similarity index 64% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_24_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_24_1.cs index ea761c5d..961fb43a 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/AssemblyName/AssemblyName_24_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/AssemblyName_24_1.cs @@ -1,14 +1,19 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName { [ApplicableToUnityVersionsSince("2018.4.34")] + [ApplicableToUnityVersionsSince("2019.4.15")] + [ApplicableToUnityVersionsSince("2020.1.11")] + [ApplicableToUnityVersionsSince("2020.2.0b7")] public unsafe class NativeAssemblyNameStructHandler_24_1 : INativeAssemblyNameStructHandler { - public int Size() => sizeof(Il2CppAssemblyName_24_1); + private NativeAssemblyNameStructHandler_24_1() + { + } public INativeAssemblyNameStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppAssemblyName_24_1* _ = (Il2CppAssemblyName_24_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +21,10 @@ public INativeAssemblyNameStruct CreateNewStruct() public INativeAssemblyNameStruct Wrap(Il2CppAssemblyName* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeAssemblyNameStructHandler_24_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppAssemblyName_24_1); internal unsafe struct Il2CppAssemblyName_24_1 { public byte* name; @@ -32,23 +39,20 @@ internal unsafe struct Il2CppAssemblyName_24_1 public int revision; public ulong public_key_token; } - internal class NativeStructWrapper : INativeAssemblyNameStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppAssemblyName_24_1* _ => (Il2CppAssemblyName_24_1*)Pointer; public Il2CppAssemblyName* AssemblyNamePointer => (Il2CppAssemblyName*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Culture => ref *(IntPtr*)&_->culture; - public ref IntPtr PublicKey => ref *(IntPtr*)&_->public_key; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Culture => ref *(nint*)&_->culture; + public ref nint PublicKey => ref *(nint*)&_->public_key; public ref int Major => ref _->major; public ref int Minor => ref _->minor; public ref int Build => ref _->build; public ref int Revision => ref _->revision; public ref ulong PublicKeyToken => ref _->public_key_token; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/Interfaces.cs new file mode 100644 index 00000000..22dd79a4 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/Interfaces.cs @@ -0,0 +1,21 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName +{ + public interface INativeAssemblyNameStructHandler : INativeStructHandler + { + INativeAssemblyNameStruct CreateNewStruct(); + unsafe INativeAssemblyNameStruct Wrap(Il2CppAssemblyName* pointer); + } + public interface INativeAssemblyNameStruct : INativeStruct + { + unsafe Il2CppAssemblyName* AssemblyNamePointer { get; } + ref nint Name { get; } + ref nint Culture { get; } + ref nint PublicKey { get; } + ref int Major { get; } + ref int Minor { get; } + ref int Build { get; } + ref int Revision { get; } + ref ulong PublicKeyToken { get; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/UnityVersionHandler.cs new file mode 100644 index 00000000..bffe6d28 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/AssemblyName/UnityVersionHandler.cs @@ -0,0 +1,49 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.AssemblyName; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetAssemblyNameStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2020, 2, 0, UnityVersionType.Beta, 7)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2020, 2, 0, UnityVersionType.Alpha, 0)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(2020, 1, 11, UnityVersionType.Alpha, 0)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2020, 1, 0, UnityVersionType.Alpha, 0)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(2019, 4, 15, UnityVersionType.Alpha, 0)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2019, 1, 0, UnityVersionType.Alpha, 0)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(2018, 4, 34, UnityVersionType.Alpha, 0)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Beta, 5)) + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_24_0.Instance; + } + else + { + AssemblyNameStructHandler = NativeAssemblyNameStructHandler_16_0.Instance; + } + } + private static INativeAssemblyNameStructHandler AssemblyNameStructHandler { get; set; } = NativeAssemblyNameStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_104_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_104_0.cs new file mode 100644 index 00000000..d9e0a1c9 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_104_0.cs @@ -0,0 +1,203 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("6000.5.0")] + public unsafe class NativeClassStructHandler_104_0 : INativeClassStructHandler + { + private NativeClassStructHandler_104_0() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_104_0* _ = (Il2CppClass_104_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_104_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_104_0); + internal unsafe struct Il2CppClass_104_0 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 byval_arg; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public Il2CppMetadataTypeHandle typeMetadataHandle; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppMethodInfo** methods; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public void* events; + public void* properties; + public void* nestedTypes; + public void* unity_user_data; + public Il2CppGCHandle initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished_or_no_cctor; + public ulong cctor_thread; + public Il2CppMetadataGenericContainerHandle genericContainerHandle; + public uint instance_size; + public uint stack_slot_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_initialized = 1, + initialized = (1 << BIT_initialized), + BIT_enumtype = 2, + enumtype = (1 << BIT_enumtype), + BIT_nullabletype = 3, + nullabletype = (1 << BIT_nullabletype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_init_pending = 7, + size_init_pending = (1 << BIT_size_init_pending), + } + internal enum Bitfield1 : byte + { + BIT_size_inited = 0, + size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 1, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 2, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 3, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 4, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 5, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + BIT_is_byref_like = 6, + is_byref_like = (1 << BIT_is_byref_like), + BIT_has_inline_array = 7, + has_inline_array = (1 << BIT_has_inline_array), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_104_0._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_104_0._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_104_0* _ => (Il2CppClass_104_0*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_104_0)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => ByValArg.ValueType && ThisArg.ValueType; + set { } + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_104_0.Bitfield1.BIT_size_inited); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_104_0.Bitfield1.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_104_0.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_104_0.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_104_0.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_104_0.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_104_0.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_105_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_105_0.cs new file mode 100644 index 00000000..94ca7463 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_105_0.cs @@ -0,0 +1,203 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("6000.5.0a5")] + public unsafe class NativeClassStructHandler_105_0 : INativeClassStructHandler + { + private NativeClassStructHandler_105_0() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_105_0* _ = (Il2CppClass_105_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_105_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_105_0); + internal unsafe struct Il2CppClass_105_0 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 byval_arg; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public Il2CppMetadataTypeHandle typeMetadataHandle; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppMethodInfo** methods; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* init_data; + public Il2CppClass** typeHierarchy; + public void* events; + public void* properties; + public void* nestedTypes; + public void* unity_user_data; + public Il2CppGCHandle initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished_or_no_cctor; + public ulong cctor_thread; + public Il2CppMetadataGenericContainerHandle genericContainerHandle; + public uint instance_size; + public uint stack_slot_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_initialized = 1, + initialized = (1 << BIT_initialized), + BIT_enumtype = 2, + enumtype = (1 << BIT_enumtype), + BIT_nullabletype = 3, + nullabletype = (1 << BIT_nullabletype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_init_pending = 7, + size_init_pending = (1 << BIT_size_init_pending), + } + internal enum Bitfield1 : byte + { + BIT_size_inited = 0, + size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 1, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 2, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 3, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 4, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 5, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + BIT_is_byref_like = 6, + is_byref_like = (1 << BIT_is_byref_like), + BIT_has_inline_array = 7, + has_inline_array = (1 << BIT_has_inline_array), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_105_0._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_105_0._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_105_0* _ => (Il2CppClass_105_0*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_105_0)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => ByValArg.ValueType && ThisArg.ValueType; + set { } + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_105_0.Bitfield1.BIT_size_inited); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_105_0.Bitfield1.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_105_0.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_105_0.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_105_0.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_105_0.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_105_0.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_16_0.cs similarity index 88% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_16_0.cs index 30614dcf..de8396bb 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_16_0.cs @@ -1,26 +1,31 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativeClassStructHandler_16_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_16_0); + private NativeClassStructHandler_16_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_16_0* _ = (Il2CppClass_16_0*)ptr; *_ = default; Marshal.FreeHGlobal(ptr); - throw new NotSupportedException("The native struct 'Il2CppClass_16_0' has a vtable field which is not currently supported!"); + throw new System.NotSupportedException("The native struct 'Il2CppClass_16_0' has a vtable field which is not currently supported!"); return new NativeStructWrapper(ptr); } public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_16_0); internal unsafe struct Il2CppClass_16_0 { public Il2CppImage* image; @@ -92,7 +97,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -100,23 +104,21 @@ internal enum Bitfield1 : byte BIT_is_blittable = 1, is_blittable = (1 << BIT_is_blittable), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_16_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_16_0._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_16_0* _ => (Il2CppClass_16_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_16_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_16_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -125,8 +127,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -184,7 +186,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_19_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_19_0.cs similarity index 87% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_19_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_19_0.cs index 93594b5e..d430d6d3 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_19_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_19_0.cs @@ -1,26 +1,31 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("5.3.2")] + [ApplicableToUnityVersionsSince("5.3.1p3")] + [ApplicableToUnityVersionsSince("5.4.0b3")] public unsafe class NativeClassStructHandler_19_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_19_0); + private NativeClassStructHandler_19_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_19_0* _ = (Il2CppClass_19_0*)ptr; *_ = default; Marshal.FreeHGlobal(ptr); - throw new NotSupportedException("The native struct 'Il2CppClass_19_0' has a vtable field which is not currently supported!"); + throw new System.NotSupportedException("The native struct 'Il2CppClass_19_0' has a vtable field which is not currently supported!"); return new NativeStructWrapper(ptr); } public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_19_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_19_0); internal unsafe struct Il2CppClass_19_0 { public Il2CppImage* image; @@ -93,7 +98,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -101,23 +105,21 @@ internal enum Bitfield1 : byte BIT_is_blittable = 1, is_blittable = (1 << BIT_is_blittable), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_19_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_19_0._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_19_0* _ => (Il2CppClass_19_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_19_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_19_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -126,8 +128,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -185,7 +187,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_20_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_20_0.cs similarity index 87% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_20_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_20_0.cs index dfffeddf..1f308bfe 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_20_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_20_0.cs @@ -1,26 +1,31 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("5.3.3")] + [ApplicableToUnityVersionsSince("5.3.2p2")] + [ApplicableToUnityVersionsSince("5.4.0b8")] public unsafe class NativeClassStructHandler_20_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_20_0); + private NativeClassStructHandler_20_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_20_0* _ = (Il2CppClass_20_0*)ptr; *_ = default; Marshal.FreeHGlobal(ptr); - throw new NotSupportedException("The native struct 'Il2CppClass_20_0' has a vtable field which is not currently supported!"); + throw new System.NotSupportedException("The native struct 'Il2CppClass_20_0' has a vtable field which is not currently supported!"); return new NativeStructWrapper(ptr); } public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_20_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_20_0); internal unsafe struct Il2CppClass_20_0 { public Il2CppImage* image; @@ -93,7 +98,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -103,23 +107,21 @@ internal enum Bitfield1 : byte BIT_is_import = 2, is_import = (1 << BIT_is_import), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_20_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_20_0._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_20_0* _ => (Il2CppClass_20_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_20_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_20_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -128,8 +130,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -187,7 +189,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_21_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_0.cs similarity index 87% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_21_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_0.cs index 4beed49f..1b3c2161 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_21_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_0.cs @@ -1,26 +1,31 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("5.3.6")] + [ApplicableToUnityVersionsSince("5.3.5p1")] + [ApplicableToUnityVersionsSince("5.4.0b19")] public unsafe class NativeClassStructHandler_21_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_21_0); + private NativeClassStructHandler_21_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_21_0* _ = (Il2CppClass_21_0*)ptr; *_ = default; Marshal.FreeHGlobal(ptr); - throw new NotSupportedException("The native struct 'Il2CppClass_21_0' has a vtable field which is not currently supported!"); + throw new System.NotSupportedException("The native struct 'Il2CppClass_21_0' has a vtable field which is not currently supported!"); return new NativeStructWrapper(ptr); } public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_21_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_21_0); internal unsafe struct Il2CppClass_21_0 { public Il2CppImage* image; @@ -41,7 +46,7 @@ internal unsafe struct Il2CppClass_21_0 public Il2CppMethodInfo** methods; public Il2CppClass** nestedTypes; public Il2CppClass** implementedInterfaces; - public VirtualInvokeData* vtable; + public Il2CppMethodInfo** vtable; public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; public void* static_fields; public void* rgctx_data; @@ -93,7 +98,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -103,23 +107,21 @@ internal enum Bitfield1 : byte BIT_is_import_or_windows_runtime = 2, is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_21_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_21_0._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_21_0* _ => (Il2CppClass_21_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_21_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_21_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -128,8 +130,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -187,7 +189,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_21_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_1.cs similarity index 87% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_21_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_1.cs index 5bbd149c..0131b4b6 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_21_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_1.cs @@ -1,26 +1,31 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("5.4.4")] + [ApplicableToUnityVersionsSince("5.3.5p3")] + [ApplicableToUnityVersionsSince("5.4.0b22")] public unsafe class NativeClassStructHandler_21_1 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_21_1); + private NativeClassStructHandler_21_1() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_21_1* _ = (Il2CppClass_21_1*)ptr; *_ = default; Marshal.FreeHGlobal(ptr); - throw new NotSupportedException("The native struct 'Il2CppClass_21_1' has a vtable field which is not currently supported!"); + throw new System.NotSupportedException("The native struct 'Il2CppClass_21_1' has a vtable field which is not currently supported!"); return new NativeStructWrapper(ptr); } public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_21_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_21_1); internal unsafe struct Il2CppClass_21_1 { public Il2CppImage* image; @@ -69,7 +74,6 @@ internal unsafe struct Il2CppClass_21_1 public ushort interfaces_count; public ushort interface_offsets_count; public byte typeHierarchyDepth; - public byte genericRecursionDepth; public byte rank; public byte minimumAlignment; public byte packingSize; @@ -94,7 +98,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -104,23 +107,21 @@ internal enum Bitfield1 : byte BIT_is_import_or_windows_runtime = 2, is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_21_1._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_21_1._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_21_1* _ => (Il2CppClass_21_1*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_21_1)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_21_1)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -129,8 +130,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -188,7 +189,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_2.cs new file mode 100644 index 00000000..945efab6 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_21_2.cs @@ -0,0 +1,193 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("5.4.3p4")] + public unsafe class NativeClassStructHandler_21_2 : INativeClassStructHandler + { + private NativeClassStructHandler_21_2() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_21_2* _ = (Il2CppClass_21_2*)ptr; + *_ = default; + Marshal.FreeHGlobal(ptr); + throw new System.NotSupportedException("The native struct 'Il2CppClass_21_2' has a vtable field which is not currently supported!"); + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_21_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_21_2); + internal unsafe struct Il2CppClass_21_2 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public Il2CppTypeStruct* byval_arg; + public Il2CppTypeStruct* this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public void* typeDefinition; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public VirtualInvokeData* vtable; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public uint cctor_started; + public uint cctor_finished; + public ulong cctor_thread; + public int genericContainerIndex; + public int customAttributeIndex; + public uint instance_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_valuetype = 0, + valuetype = (1 << BIT_valuetype), + BIT_initialized = 1, + initialized = (1 << BIT_initialized), + BIT_enumtype = 2, + enumtype = (1 << BIT_enumtype), + BIT_is_generic = 3, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 4, + has_references = (1 << BIT_has_references), + BIT_init_pending = 5, + init_pending = (1 << BIT_init_pending), + BIT_size_inited = 6, + size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 7, + has_finalize = (1 << BIT_has_finalize), + } + internal enum Bitfield1 : byte + { + BIT_has_cctor = 0, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 1, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 2, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_21_2._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_21_2._bitfield1)).ToInt32(); + private Il2CppClass* _klassDummy; + public nint Pointer { get; } + private Il2CppClass_21_2* _ => (Il2CppClass_21_2*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_21_2)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _klassDummy; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_valuetype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_valuetype, value); + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_has_finalize); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_21_2.Bitfield0.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => false; + set { } + } + public bool InitializedAndNoError + { + get => true; + set { } + } + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_22_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_22_0.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_22_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_22_0.cs index c3457d0d..cd8ff3a5 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_22_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_22_0.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { [ApplicableToUnityVersionsSince("5.5.0")] public unsafe class NativeClassStructHandler_22_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_22_0); + private NativeClassStructHandler_22_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_22_0* _ = (Il2CppClass_22_0*)ptr; *_ = default; _->byval_arg = UnityVersionHandler.NewType().TypePointer; @@ -19,8 +21,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_22_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_22_0); internal unsafe struct Il2CppClass_22_0 { public Il2CppImage* image; @@ -92,7 +96,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -104,23 +107,21 @@ internal enum Bitfield1 : byte BIT_is_vtable_initialized = 3, is_vtable_initialized = (1 << BIT_is_vtable_initialized), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_22_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_22_0._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_22_0* _ => (Il2CppClass_22_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_22_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_22_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -129,8 +130,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -188,7 +189,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_22_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_22_1.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_22_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_22_1.cs index 2267aca9..cd26678b 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_22_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_22_1.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("5.5.1")] + [ApplicableToUnityVersionsSince("5.5.0p2")] public unsafe class NativeClassStructHandler_22_1 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_22_1); + private NativeClassStructHandler_22_1() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_22_1* _ = (Il2CppClass_22_1*)ptr; *_ = default; _->byval_arg = UnityVersionHandler.NewType().TypePointer; @@ -19,8 +21,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_22_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_22_1); internal unsafe struct Il2CppClass_22_1 { public Il2CppImage* image; @@ -93,7 +97,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -105,23 +108,21 @@ internal enum Bitfield1 : byte BIT_is_vtable_initialized = 3, is_vtable_initialized = (1 << BIT_is_vtable_initialized), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_22_1._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_22_1._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_22_1* _ => (Il2CppClass_22_1*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_22_1)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_22_1)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -130,8 +131,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -189,7 +190,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_23_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_23_0.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_23_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_23_0.cs index eed8c233..1dbdabe9 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_23_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_23_0.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { [ApplicableToUnityVersionsSince("5.6.0")] public unsafe class NativeClassStructHandler_23_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_23_0); + private NativeClassStructHandler_23_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_23_0* _ = (Il2CppClass_23_0*)ptr; *_ = default; _->byval_arg = UnityVersionHandler.NewType().TypePointer; @@ -19,8 +21,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_23_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_23_0); internal unsafe struct Il2CppClass_23_0 { public Il2CppImage* image; @@ -69,7 +73,6 @@ internal unsafe struct Il2CppClass_23_0 public ushort interfaces_count; public ushort interface_offsets_count; public byte typeHierarchyDepth; - public byte genericRecursionDepth; public byte rank; public byte minimumAlignment; public byte packingSize; @@ -94,7 +97,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -106,23 +108,21 @@ internal enum Bitfield1 : byte BIT_is_vtable_initialized = 3, is_vtable_initialized = (1 << BIT_is_vtable_initialized), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_23_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_23_0._bitfield1)).ToInt32(); private Il2CppClass* _klassDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_23_0* _ => (Il2CppClass_23_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_23_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_23_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -131,8 +131,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -190,7 +190,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_23_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_23_1.cs new file mode 100644 index 00000000..445d25be --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_23_1.cs @@ -0,0 +1,195 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("5.6.0b3")] + public unsafe class NativeClassStructHandler_23_1 : INativeClassStructHandler + { + private NativeClassStructHandler_23_1() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_23_1* _ = (Il2CppClass_23_1*)ptr; + *_ = default; + _->byval_arg = UnityVersionHandler.NewType().TypePointer; + _->this_arg = UnityVersionHandler.NewType().TypePointer; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_23_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_23_1); + internal unsafe struct Il2CppClass_23_1 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public Il2CppTypeStruct* byval_arg; + public Il2CppTypeStruct* this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public void* typeDefinition; + public void* interopData; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public uint cctor_started; + public uint cctor_finished; + public ulong cctor_thread; + public int genericContainerIndex; + public int customAttributeIndex; + public uint instance_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_valuetype = 0, + valuetype = (1 << BIT_valuetype), + BIT_initialized = 1, + initialized = (1 << BIT_initialized), + BIT_enumtype = 2, + enumtype = (1 << BIT_enumtype), + BIT_is_generic = 3, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 4, + has_references = (1 << BIT_has_references), + BIT_init_pending = 5, + init_pending = (1 << BIT_init_pending), + BIT_size_inited = 6, + size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 7, + has_finalize = (1 << BIT_has_finalize), + } + internal enum Bitfield1 : byte + { + BIT_has_cctor = 0, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 1, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 2, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 3, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_23_1._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_23_1._bitfield1)).ToInt32(); + private Il2CppClass* _klassDummy; + public nint Pointer { get; } + private Il2CppClass_23_1* _ => (Il2CppClass_23_1*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_23_1)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap(_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap(_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _klassDummy; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_valuetype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_valuetype, value); + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_has_finalize); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_23_1.Bitfield0.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_23_1.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_23_1.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => true; + set { } + } + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_0.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_0.cs index 6f53a551..637a0e5f 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_0.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2018.1.0")] + [ApplicableToUnityVersionsSince("2018.1.0b5")] public unsafe class NativeClassStructHandler_24_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_24_0); + private NativeClassStructHandler_24_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_24_0* _ = (Il2CppClass_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_0); internal unsafe struct Il2CppClass_24_0 { public Il2CppImage* image; @@ -93,7 +97,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -105,22 +108,20 @@ internal enum Bitfield1 : byte BIT_is_vtable_initialized = 3, is_vtable_initialized = (1 << BIT_is_vtable_initialized), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_0._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_24_0* _ => (Il2CppClass_24_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_24_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -129,8 +130,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -188,7 +189,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_1.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_1.cs index e2888fac..bcf52b55 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_1.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { [ApplicableToUnityVersionsSince("2018.2.0")] public unsafe class NativeClassStructHandler_24_1 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_24_1); + private NativeClassStructHandler_24_1() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_24_1* _ = (Il2CppClass_24_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_24_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_1); internal unsafe struct Il2CppClass_24_1 { public Il2CppImage* image; @@ -94,7 +98,6 @@ internal enum Bitfield0 : byte BIT_has_finalize = 7, has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { BIT_has_cctor = 0, @@ -108,22 +111,20 @@ internal enum Bitfield1 : byte BIT_has_initialization_error = 4, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_1._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_1._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_24_1* _ => (Il2CppClass_24_1*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_24_1)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_1)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -132,8 +133,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -191,7 +192,5 @@ public bool InitializedAndNoError set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_2.cs similarity index 82% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_2.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_2.cs index ddaaba9a..5769a1a5 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_2.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_2.cs @@ -1,15 +1,18 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { [ApplicableToUnityVersionsSince("2018.3.0")] + [ApplicableToUnityVersionsSince("2019.1.0")] public unsafe class NativeClassStructHandler_24_2 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_24_2); + private NativeClassStructHandler_24_2() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_24_2* _ = (Il2CppClass_24_2*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +20,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_24_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_2); internal unsafe struct Il2CppClass_24_2 { public Il2CppImage* image; @@ -76,55 +81,50 @@ internal unsafe struct Il2CppClass_24_2 public Bitfield1 _bitfield1; internal enum Bitfield0 : byte { - BIT_initialized_and_no_error = 0, - initialized_and_no_error = (1 << BIT_initialized_and_no_error), - BIT_valuetype = 1, + BIT_valuetype = 0, valuetype = (1 << BIT_valuetype), - BIT_initialized = 2, + BIT_initialized = 1, initialized = (1 << BIT_initialized), - BIT_enumtype = 3, + BIT_enumtype = 2, enumtype = (1 << BIT_enumtype), - BIT_is_generic = 4, + BIT_is_generic = 3, is_generic = (1 << BIT_is_generic), - BIT_has_references = 5, + BIT_has_references = 4, has_references = (1 << BIT_has_references), - BIT_init_pending = 6, + BIT_init_pending = 5, init_pending = (1 << BIT_init_pending), - BIT_size_inited = 7, + BIT_size_inited = 6, size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 7, + has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { - BIT_has_finalize = 0, - has_finalize = (1 << BIT_has_finalize), - BIT_has_cctor = 1, + BIT_has_cctor = 0, has_cctor = (1 << BIT_has_cctor), - BIT_is_blittable = 2, + BIT_is_blittable = 1, is_blittable = (1 << BIT_is_blittable), - BIT_is_import_or_windows_runtime = 3, + BIT_is_import_or_windows_runtime = 2, is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), - BIT_is_vtable_initialized = 4, + BIT_is_vtable_initialized = 3, is_vtable_initialized = (1 << BIT_is_vtable_initialized), - BIT_has_initialization_error = 5, + BIT_has_initialization_error = 4, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_2._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_2._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_24_2* _ => (Il2CppClass_24_2*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_24_2)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_2)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -133,8 +133,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -178,8 +178,8 @@ public bool SizeInited } public bool HasFinalize { - get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_24_2.Bitfield1.BIT_has_finalize); - set => this.SetBit(_bitfield1offset, (int)Il2CppClass_24_2.Bitfield1.BIT_has_finalize, value); + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_2.Bitfield0.BIT_has_finalize); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_2.Bitfield0.BIT_has_finalize, value); } public bool IsVtableInitialized { @@ -188,11 +188,9 @@ public bool IsVtableInitialized } public bool InitializedAndNoError { - get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_2.Bitfield0.BIT_initialized_and_no_error); - set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_2.Bitfield0.BIT_initialized_and_no_error, value); + get => true; + set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_3.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_3.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_3.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_3.cs index f9388c3c..6c4645a4 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_3.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_3.cs @@ -1,15 +1,18 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2018.3.8")] + [ApplicableToUnityVersionsSince("2018.3.0b11")] + [ApplicableToUnityVersionsSince("2019.1.0a8")] public unsafe class NativeClassStructHandler_24_3 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_24_3); + private NativeClassStructHandler_24_3() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_24_3* _ = (Il2CppClass_24_3*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +20,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_24_3 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_3); internal unsafe struct Il2CppClass_24_3 { public Il2CppImage* image; @@ -71,7 +76,6 @@ internal unsafe struct Il2CppClass_24_3 public byte genericRecursionDepth; public byte rank; public byte minimumAlignment; - public byte naturalAligment; public byte packingSize; public Bitfield0 _bitfield0; public Bitfield1 _bitfield1; @@ -94,7 +98,6 @@ internal enum Bitfield0 : byte BIT_size_inited = 7, size_inited = (1 << BIT_size_inited), } - internal enum Bitfield1 : byte { BIT_has_finalize = 0, @@ -110,22 +113,20 @@ internal enum Bitfield1 : byte BIT_has_initialization_error = 5, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_3._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_3._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_24_3* _ => (Il2CppClass_24_3*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_24_3)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_3)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -134,8 +135,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -193,7 +194,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_3.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_4.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_4.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_4.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_4.cs index a5b4e6f3..f7efc51e 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_24_4.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_4.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2019.1.0")] + [ApplicableToUnityVersionsSince("2018.3.8")] public unsafe class NativeClassStructHandler_24_4 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_24_4); + private NativeClassStructHandler_24_4() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_24_4* _ = (Il2CppClass_24_4*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_24_4 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_4); internal unsafe struct Il2CppClass_24_4 { public Il2CppImage* image; @@ -45,11 +49,10 @@ internal unsafe struct Il2CppClass_24_4 public void* static_fields; public void* rgctx_data; public Il2CppClass** typeHierarchy; - public void* unity_user_data; public uint initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished; - public IntPtr cctor_thread; + public ulong cctor_thread; public int genericContainerIndex; public uint instance_size; public uint actualSize; @@ -95,7 +98,6 @@ internal enum Bitfield0 : byte BIT_size_inited = 7, size_inited = (1 << BIT_size_inited), } - internal enum Bitfield1 : byte { BIT_has_finalize = 0, @@ -111,22 +113,20 @@ internal enum Bitfield1 : byte BIT_has_initialization_error = 5, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_4._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_4._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_24_4* _ => (Il2CppClass_24_4*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_24_4)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_4)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -135,8 +135,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -194,7 +194,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_4.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_5.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_5.cs new file mode 100644 index 00000000..3b079ae7 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_5.cs @@ -0,0 +1,199 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("2019.1.0a11")] + [ApplicableToUnityVersionsSince("2019.2.0")] + public unsafe class NativeClassStructHandler_24_5 : INativeClassStructHandler + { + private NativeClassStructHandler_24_5() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_24_5* _ = (Il2CppClass_24_5*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_24_5 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_5); + internal unsafe struct Il2CppClass_24_5 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_16_0.Il2CppType_16_0 byval_arg; + public NativeTypeStructHandler_16_0.Il2CppType_16_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public void* typeDefinition; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public void* unity_user_data; + public uint initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished; + public ulong cctor_thread; + public int genericContainerIndex; + public uint instance_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_valuetype = 1, + valuetype = (1 << BIT_valuetype), + BIT_initialized = 2, + initialized = (1 << BIT_initialized), + BIT_enumtype = 3, + enumtype = (1 << BIT_enumtype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_inited = 7, + size_inited = (1 << BIT_size_inited), + } + internal enum Bitfield1 : byte + { + BIT_has_finalize = 0, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 1, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 2, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 3, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 4, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + BIT_has_initialization_error = 5, + has_initialization_error = (1 << BIT_has_initialization_error), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_5._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_5._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_24_5* _ => (Il2CppClass_24_5*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_5)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_valuetype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_valuetype, value); + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_24_5.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_24_5.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_24_5.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_24_5.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_5.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_6.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_6.cs new file mode 100644 index 00000000..debe7f9a --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_6.cs @@ -0,0 +1,200 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("2019.1.0b5")] + [ApplicableToUnityVersionsSince("2019.2.0a6")] + public unsafe class NativeClassStructHandler_24_6 : INativeClassStructHandler + { + private NativeClassStructHandler_24_6() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_24_6* _ = (Il2CppClass_24_6*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_24_6 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_6); + internal unsafe struct Il2CppClass_24_6 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_16_0.Il2CppType_16_0 byval_arg; + public NativeTypeStructHandler_16_0.Il2CppType_16_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public void* typeDefinition; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public void* unity_user_data; + public uint initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished; + public ulong cctor_thread; + public int genericContainerIndex; + public uint instance_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte naturalAligment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_valuetype = 1, + valuetype = (1 << BIT_valuetype), + BIT_initialized = 2, + initialized = (1 << BIT_initialized), + BIT_enumtype = 3, + enumtype = (1 << BIT_enumtype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_inited = 7, + size_inited = (1 << BIT_size_inited), + } + internal enum Bitfield1 : byte + { + BIT_has_finalize = 0, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 1, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 2, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 3, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 4, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + BIT_has_initialization_error = 5, + has_initialization_error = (1 << BIT_has_initialization_error), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_6._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_6._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_24_6* _ => (Il2CppClass_24_6*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_6)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_valuetype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_valuetype, value); + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_24_6.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_24_6.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_24_6.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_24_6.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_6.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_7.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_7.cs new file mode 100644 index 00000000..8cb596a2 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_24_7.cs @@ -0,0 +1,199 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("2020.2.0")] + public unsafe class NativeClassStructHandler_24_7 : INativeClassStructHandler + { + private NativeClassStructHandler_24_7() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_24_7* _ = (Il2CppClass_24_7*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_24_7 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_24_7); + internal unsafe struct Il2CppClass_24_7 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_16_0.Il2CppType_16_0 byval_arg; + public NativeTypeStructHandler_16_0.Il2CppType_16_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public Il2CppMetadataTypeHandle typeMetadataHandle; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public void* unity_user_data; + public uint initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished; + public ulong cctor_thread; + public Il2CppMetadataGenericContainerHandle genericContainerHandle; + public uint instance_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte naturalAligment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_valuetype = 1, + valuetype = (1 << BIT_valuetype), + BIT_initialized = 2, + initialized = (1 << BIT_initialized), + BIT_enumtype = 3, + enumtype = (1 << BIT_enumtype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_inited = 7, + size_inited = (1 << BIT_size_inited), + } + internal enum Bitfield1 : byte + { + BIT_has_finalize = 0, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 1, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 2, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 3, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 4, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + BIT_has_initialization_error = 5, + has_initialization_error = (1 << BIT_has_initialization_error), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_24_7._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_24_7._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_24_7* _ => (Il2CppClass_24_7*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_24_7)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_valuetype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_valuetype, value); + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_24_7.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_24_7.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_24_7.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_24_7.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_24_7.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_0.cs similarity index 84% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_0.cs index fbb2793e..15ada696 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_0.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2020.2.0")] + [ApplicableToUnityVersionsSince("2020.3.20")] public unsafe class NativeClassStructHandler_27_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_27_0); + private NativeClassStructHandler_27_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_27_0* _ = (Il2CppClass_27_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_27_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_27_0); internal unsafe struct Il2CppClass_27_0 { public Il2CppImage* image; @@ -49,7 +53,7 @@ internal unsafe struct Il2CppClass_27_0 public uint initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished; - public IntPtr cctor_thread; + public ulong cctor_thread; public Il2CppMetadataGenericContainerHandle genericContainerHandle; public uint instance_size; public uint actualSize; @@ -92,41 +96,40 @@ internal enum Bitfield0 : byte has_references = (1 << BIT_has_references), BIT_init_pending = 6, init_pending = (1 << BIT_init_pending), - BIT_size_inited = 7, - size_inited = (1 << BIT_size_inited), + BIT_size_init_pending = 7, + size_init_pending = (1 << BIT_size_init_pending), } - internal enum Bitfield1 : byte { - BIT_has_finalize = 0, + BIT_size_inited = 0, + size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 1, has_finalize = (1 << BIT_has_finalize), - BIT_has_cctor = 1, + BIT_has_cctor = 2, has_cctor = (1 << BIT_has_cctor), - BIT_is_blittable = 2, + BIT_is_blittable = 3, is_blittable = (1 << BIT_is_blittable), - BIT_is_import_or_windows_runtime = 3, + BIT_is_import_or_windows_runtime = 4, is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), - BIT_is_vtable_initialized = 4, + BIT_is_vtable_initialized = 5, is_vtable_initialized = (1 << BIT_is_vtable_initialized), - BIT_has_initialization_error = 5, + BIT_has_initialization_error = 6, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_27_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_27_0._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_27_0* _ => (Il2CppClass_27_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_27_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_27_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -135,8 +138,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -175,8 +178,8 @@ public bool HasReferences } public bool SizeInited { - get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_0.Bitfield0.BIT_size_inited); - set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_0.Bitfield0.BIT_size_inited, value); + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_0.Bitfield1.BIT_size_inited); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_0.Bitfield1.BIT_size_inited, value); } public bool HasFinalize { @@ -194,7 +197,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_0.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_1.cs similarity index 78% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_1.cs index 7288f061..daea850b 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_1.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2020.3.20")] + [ApplicableToUnityVersionsSince("2021.1.0")] public unsafe class NativeClassStructHandler_27_1 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_27_1); + private NativeClassStructHandler_27_1() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_27_1* _ = (Il2CppClass_27_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,16 +19,18 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_27_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_27_1); internal unsafe struct Il2CppClass_27_1 { public Il2CppImage* image; public void* gc_desc; public byte* name; public byte* namespaze; - public NativeTypeStructHandler_16_0.Il2CppType_16_0 byval_arg; - public NativeTypeStructHandler_16_0.Il2CppType_16_0 this_arg; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 byval_arg; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 this_arg; public Il2CppClass* element_class; public Il2CppClass* castClass; public Il2CppClass* declaringType; @@ -49,7 +53,7 @@ internal unsafe struct Il2CppClass_27_1 public uint initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished; - public IntPtr cctor_thread; + public ulong cctor_thread; public Il2CppMetadataGenericContainerHandle genericContainerHandle; public uint instance_size; public uint actualSize; @@ -80,55 +84,48 @@ internal enum Bitfield0 : byte { BIT_initialized_and_no_error = 0, initialized_and_no_error = (1 << BIT_initialized_and_no_error), - BIT_valuetype = 1, - valuetype = (1 << BIT_valuetype), - BIT_initialized = 2, + BIT_initialized = 1, initialized = (1 << BIT_initialized), - BIT_enumtype = 3, + BIT_enumtype = 2, enumtype = (1 << BIT_enumtype), - BIT_is_generic = 4, + BIT_is_generic = 3, is_generic = (1 << BIT_is_generic), - BIT_has_references = 5, + BIT_has_references = 4, has_references = (1 << BIT_has_references), - BIT_init_pending = 6, + BIT_init_pending = 5, init_pending = (1 << BIT_init_pending), - BIT_size_init_pending = 7, - size_init_pending = (1 << BIT_size_init_pending), + BIT_size_inited = 6, + size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 7, + has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { - BIT_size_inited = 0, - size_inited = (1 << BIT_size_inited), - BIT_has_finalize = 1, - has_finalize = (1 << BIT_has_finalize), - BIT_has_cctor = 2, + BIT_has_cctor = 0, has_cctor = (1 << BIT_has_cctor), - BIT_is_blittable = 3, + BIT_is_blittable = 1, is_blittable = (1 << BIT_is_blittable), - BIT_is_import_or_windows_runtime = 4, + BIT_is_import_or_windows_runtime = 2, is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), - BIT_is_vtable_initialized = 5, + BIT_is_vtable_initialized = 3, is_vtable_initialized = (1 << BIT_is_vtable_initialized), - BIT_has_initialization_error = 6, + BIT_has_initialization_error = 4, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_27_1._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_27_1._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_27_1* _ => (Il2CppClass_27_1*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_27_1)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_27_1)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -137,8 +134,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -152,8 +149,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; public bool ValueType { - get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_1.Bitfield0.BIT_valuetype); - set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_1.Bitfield0.BIT_valuetype, value); + get => ByValArg.ValueType && ThisArg.ValueType; + set { } } public bool Initialized { @@ -177,13 +174,13 @@ public bool HasReferences } public bool SizeInited { - get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_1.Bitfield1.BIT_size_inited); - set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_1.Bitfield1.BIT_size_inited, value); + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_1.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_1.Bitfield0.BIT_size_inited, value); } public bool HasFinalize { - get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_1.Bitfield1.BIT_has_finalize); - set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_1.Bitfield1.BIT_has_finalize, value); + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_1.Bitfield0.BIT_has_finalize); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_1.Bitfield0.BIT_has_finalize, value); } public bool IsVtableInitialized { @@ -196,7 +193,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_1.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_2.cs similarity index 84% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_2.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_2.cs index f64e2199..a3249a77 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_2.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_2.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2021.1.0")] + [ApplicableToUnityVersionsSince("2021.1.24")] public unsafe class NativeClassStructHandler_27_2 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_27_2); + private NativeClassStructHandler_27_2() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_27_2* _ = (Il2CppClass_27_2*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_27_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_27_2); internal unsafe struct Il2CppClass_27_2 { public Il2CppImage* image; @@ -49,7 +53,7 @@ internal unsafe struct Il2CppClass_27_2 public uint initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished; - public IntPtr cctor_thread; + public ulong cctor_thread; public Il2CppMetadataGenericContainerHandle genericContainerHandle; public uint instance_size; public uint actualSize; @@ -90,41 +94,40 @@ internal enum Bitfield0 : byte has_references = (1 << BIT_has_references), BIT_init_pending = 5, init_pending = (1 << BIT_init_pending), - BIT_size_inited = 6, + BIT_size_init_pending = 6, + size_init_pending = (1 << BIT_size_init_pending), + BIT_size_inited = 7, size_inited = (1 << BIT_size_inited), - BIT_has_finalize = 7, - has_finalize = (1 << BIT_has_finalize), } - internal enum Bitfield1 : byte { - BIT_has_cctor = 0, + BIT_has_finalize = 0, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 1, has_cctor = (1 << BIT_has_cctor), - BIT_is_blittable = 1, + BIT_is_blittable = 2, is_blittable = (1 << BIT_is_blittable), - BIT_is_import_or_windows_runtime = 2, + BIT_is_import_or_windows_runtime = 3, is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), - BIT_is_vtable_initialized = 3, + BIT_is_vtable_initialized = 4, is_vtable_initialized = (1 << BIT_is_vtable_initialized), - BIT_has_initialization_error = 4, + BIT_has_initialization_error = 5, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_27_2._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_27_2._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_27_2* _ => (Il2CppClass_27_2*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_27_2)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_27_2)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -133,8 +136,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -178,8 +181,8 @@ public bool SizeInited } public bool HasFinalize { - get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_2.Bitfield0.BIT_has_finalize); - set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_2.Bitfield0.BIT_has_finalize, value); + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_2.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_2.Bitfield1.BIT_has_finalize, value); } public bool IsVtableInitialized { @@ -192,7 +195,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_2.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_3.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_3.cs similarity index 88% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_3.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_3.cs index 72afe19d..2dbe748f 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_27_3.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_3.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2021.1.24")] + [ApplicableToUnityVersionsSince("2021.2.0")] public unsafe class NativeClassStructHandler_27_3 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_27_3); + private NativeClassStructHandler_27_3() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_27_3* _ = (Il2CppClass_27_3*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_27_3 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_27_3); internal unsafe struct Il2CppClass_27_3 { public Il2CppImage* image; @@ -49,7 +53,7 @@ internal unsafe struct Il2CppClass_27_3 public uint initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished; - public IntPtr cctor_thread; + public ulong cctor_thread; public Il2CppMetadataGenericContainerHandle genericContainerHandle; public uint instance_size; public uint actualSize; @@ -84,18 +88,17 @@ internal enum Bitfield0 : byte initialized = (1 << BIT_initialized), BIT_enumtype = 2, enumtype = (1 << BIT_enumtype), - BIT_is_generic = 3, + BIT_nullabletype = 3, + nullabletype = (1 << BIT_nullabletype), + BIT_is_generic = 4, is_generic = (1 << BIT_is_generic), - BIT_has_references = 4, + BIT_has_references = 5, has_references = (1 << BIT_has_references), - BIT_init_pending = 5, + BIT_init_pending = 6, init_pending = (1 << BIT_init_pending), - BIT_size_init_pending = 6, - size_init_pending = (1 << BIT_size_init_pending), BIT_size_inited = 7, size_inited = (1 << BIT_size_inited), } - internal enum Bitfield1 : byte { BIT_has_finalize = 0, @@ -111,22 +114,20 @@ internal enum Bitfield1 : byte BIT_has_initialization_error = 5, has_initialization_error = (1 << BIT_has_initialization_error), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_27_3._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_27_3._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_27_3* _ => (Il2CppClass_27_3*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_27_3)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_27_3)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -135,8 +136,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -194,7 +195,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_3.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_4.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_4.cs new file mode 100644 index 00000000..d57a7a52 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_4.cs @@ -0,0 +1,197 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("2021.2.0a12")] + public unsafe class NativeClassStructHandler_27_4 : INativeClassStructHandler + { + private NativeClassStructHandler_27_4() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_27_4* _ = (Il2CppClass_27_4*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_27_4 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_27_4); + internal unsafe struct Il2CppClass_27_4 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 byval_arg; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public Il2CppMetadataTypeHandle typeMetadataHandle; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public void* unity_user_data; + public uint initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished_or_no_cctor; + public ulong cctor_thread; + public Il2CppMetadataGenericContainerHandle genericContainerHandle; + public uint instance_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte naturalAligment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_initialized = 1, + initialized = (1 << BIT_initialized), + BIT_enumtype = 2, + enumtype = (1 << BIT_enumtype), + BIT_nullabletype = 3, + nullabletype = (1 << BIT_nullabletype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_inited = 7, + size_inited = (1 << BIT_size_inited), + } + internal enum Bitfield1 : byte + { + BIT_has_finalize = 0, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 1, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 2, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 3, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 4, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_27_4._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_27_4._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_27_4* _ => (Il2CppClass_27_4*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_27_4)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => ByValArg.ValueType && ThisArg.ValueType; + set { } + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_4.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_4.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_4.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_4.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_4.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_5.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_5.cs new file mode 100644 index 00000000..ac9830f5 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_27_5.cs @@ -0,0 +1,200 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("2021.2.0a21")] + [ApplicableToUnityVersionsSince("2022.1.0")] + public unsafe class NativeClassStructHandler_27_5 : INativeClassStructHandler + { + private NativeClassStructHandler_27_5() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_27_5* _ = (Il2CppClass_27_5*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_27_5 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_27_5); + internal unsafe struct Il2CppClass_27_5 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 byval_arg; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public Il2CppMetadataTypeHandle typeMetadataHandle; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* rgctx_data; + public Il2CppClass** typeHierarchy; + public void* unity_user_data; + public uint initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished_or_no_cctor; + public ulong cctor_thread; + public Il2CppMetadataGenericContainerHandle genericContainerHandle; + public uint instance_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte naturalAligment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_initialized = 1, + initialized = (1 << BIT_initialized), + BIT_enumtype = 2, + enumtype = (1 << BIT_enumtype), + BIT_nullabletype = 3, + nullabletype = (1 << BIT_nullabletype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_inited = 7, + size_inited = (1 << BIT_size_inited), + } + internal enum Bitfield1 : byte + { + BIT_has_finalize = 0, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 1, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 2, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 3, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 4, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + BIT_is_byref_like = 5, + is_byref_like = (1 << BIT_is_byref_like), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_27_5._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_27_5._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_27_5* _ => (Il2CppClass_27_5*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_27_5)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => ByValArg.ValueType && ThisArg.ValueType; + set { } + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_size_inited); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_5.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_5.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_27_5.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_27_5.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_27_5.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_0.cs similarity index 89% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_0.cs index fd38878f..806c5016 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_0.cs @@ -1,15 +1,18 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2021.2.0")] + [ApplicableToUnityVersionsSince("2021.2.0b13")] + [ApplicableToUnityVersionsSince("2022.1.0a10")] public unsafe class NativeClassStructHandler_29_0 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_29_0); + private NativeClassStructHandler_29_0() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_29_0* _ = (Il2CppClass_29_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +20,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_29_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_29_0); internal unsafe struct Il2CppClass_29_0 { public Il2CppImage* image; @@ -49,7 +54,7 @@ internal unsafe struct Il2CppClass_29_0 public uint initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished_or_no_cctor; - public IntPtr cctor_thread; + public ulong cctor_thread; public Il2CppMetadataGenericContainerHandle genericContainerHandle; public uint instance_size; public uint actualSize; @@ -95,7 +100,6 @@ internal enum Bitfield0 : byte BIT_size_init_pending = 7, size_init_pending = (1 << BIT_size_init_pending), } - internal enum Bitfield1 : byte { BIT_size_inited = 0, @@ -113,22 +117,20 @@ internal enum Bitfield1 : byte BIT_is_byref_like = 6, is_byref_like = (1 << BIT_is_byref_like), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_29_0._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_29_0._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_29_0* _ => (Il2CppClass_29_0*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_29_0)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_29_0)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -137,8 +139,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -196,7 +198,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_29_0.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_1.cs similarity index 90% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_1.cs index 1764e56f..a46b49aa 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_1.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { [ApplicableToUnityVersionsSince("2022.2.0")] public unsafe class NativeClassStructHandler_29_1 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_29_1); + private NativeClassStructHandler_29_1() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_29_1* _ = (Il2CppClass_29_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +19,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_29_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_29_1); internal unsafe struct Il2CppClass_29_1 { public Il2CppImage* image; @@ -49,7 +53,7 @@ internal unsafe struct Il2CppClass_29_1 public uint initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished_or_no_cctor; - public IntPtr cctor_thread; + public ulong cctor_thread; public Il2CppMetadataGenericContainerHandle genericContainerHandle; public uint instance_size; public uint stack_slot_size; @@ -95,7 +99,6 @@ internal enum Bitfield0 : byte BIT_size_init_pending = 7, size_init_pending = (1 << BIT_size_init_pending), } - internal enum Bitfield1 : byte { BIT_size_inited = 0, @@ -113,22 +116,20 @@ internal enum Bitfield1 : byte BIT_is_byref_like = 6, is_byref_like = (1 << BIT_is_byref_like), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_29_1._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_29_1._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_29_1* _ => (Il2CppClass_29_1*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_29_1)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_29_1)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -137,8 +138,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -196,7 +197,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_29_1.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_2.cs similarity index 89% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_2.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_2.cs index 68d97a4c..b2e86fc1 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Class/Class_29_2.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_29_2.cs @@ -1,15 +1,18 @@ -using System; +// using System.Runtime.InteropServices; -using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Class +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class { - [ApplicableToUnityVersionsSince("2023.1.0")] + [ApplicableToUnityVersionsSince("2023.1.0a15")] + [ApplicableToUnityVersionsSince("6000.4.0")] public unsafe class NativeClassStructHandler_29_2 : INativeClassStructHandler { - public int Size() => sizeof(Il2CppClass_29_2); + private NativeClassStructHandler_29_2() + { + } public INativeClassStruct CreateNewStruct(int vTableSlots) { - IntPtr ptr = Marshal.AllocHGlobal(Size() + sizeof(VirtualInvokeData) * vTableSlots); + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); Il2CppClass_29_2* _ = (Il2CppClass_29_2*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -17,8 +20,10 @@ public INativeClassStruct CreateNewStruct(int vTableSlots) public INativeClassStruct Wrap(Il2CppClass* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeClassStructHandler_29_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_29_2); internal unsafe struct Il2CppClass_29_2 { public Il2CppImage* image; @@ -49,7 +54,7 @@ internal unsafe struct Il2CppClass_29_2 public Il2CppGCHandle initializationExceptionGCHandle; public uint cctor_started; public uint cctor_finished_or_no_cctor; - public IntPtr cctor_thread; + public ulong cctor_thread; public Il2CppMetadataGenericContainerHandle genericContainerHandle; public uint instance_size; public uint stack_slot_size; @@ -95,7 +100,6 @@ internal enum Bitfield0 : byte BIT_size_init_pending = 7, size_init_pending = (1 << BIT_size_init_pending), } - internal enum Bitfield1 : byte { BIT_size_inited = 0, @@ -113,22 +117,20 @@ internal enum Bitfield1 : byte BIT_is_byref_like = 6, is_byref_like = (1 << BIT_is_byref_like), } - } - internal class NativeStructWrapper : INativeClassStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_29_2._bitfield0)).ToInt32(); private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_29_2._bitfield1)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppClass_29_2* _ => (Il2CppClass_29_2*)Pointer; - public IntPtr VTable => IntPtr.Add(Pointer, sizeof(Il2CppClass_29_2)); + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_29_2)); public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); public ref uint InstanceSize => ref _->instance_size; - public ref ushort VtableCount => ref _->vtable_count; + public ref ushort VTableCount => ref _->vtable_count; public ref ushort InterfaceCount => ref _->interfaces_count; public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; @@ -137,8 +139,8 @@ internal class NativeStructWrapper : INativeClassStruct public ref ushort MethodCount => ref _->method_count; public ref ushort FieldCount => ref _->field_count; public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr Namespace => ref *(IntPtr*)&_->namespaze; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; public ref Il2CppImage* Image => ref _->image; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppClass* ElementClass => ref _->element_class; @@ -196,7 +198,5 @@ public bool InitializedAndNoError set => this.SetBit(_bitfield0offset, (int)Il2CppClass_29_2.Bitfield0.BIT_initialized_and_no_error, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_39_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_39_0.cs new file mode 100644 index 00000000..6e5cdd61 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Class_39_0.cs @@ -0,0 +1,201 @@ +// +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + [ApplicableToUnityVersionsSince("6000.3.3")] + public unsafe class NativeClassStructHandler_39_0 : INativeClassStructHandler + { + private NativeClassStructHandler_39_0() + { + } + public INativeClassStruct CreateNewStruct(int vTableSlots) + { + nint ptr = Marshal.AllocHGlobal(Size + sizeof(VirtualInvokeData) * vTableSlots); + Il2CppClass_39_0* _ = (Il2CppClass_39_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeClassStruct Wrap(Il2CppClass* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeClassStructHandler_39_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppClass_39_0); + internal unsafe struct Il2CppClass_39_0 + { + public Il2CppImage* image; + public void* gc_desc; + public byte* name; + public byte* namespaze; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 byval_arg; + public NativeTypeStructHandler_27_0.Il2CppType_27_0 this_arg; + public Il2CppClass* element_class; + public Il2CppClass* castClass; + public Il2CppClass* declaringType; + public Il2CppClass* parent; + public void* generic_class; + public Il2CppMetadataTypeHandle typeMetadataHandle; + public void* interopData; + public Il2CppClass* klass; + public Il2CppFieldInfo* fields; + public Il2CppEventInfo* events; + public Il2CppPropertyInfo* properties; + public Il2CppMethodInfo** methods; + public Il2CppClass** nestedTypes; + public Il2CppClass** implementedInterfaces; + public Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; + public void* static_fields; + public void* init_data; + public Il2CppClass** typeHierarchy; + public void* unity_user_data; + public Il2CppGCHandle initializationExceptionGCHandle; + public uint cctor_started; + public uint cctor_finished_or_no_cctor; + public ulong cctor_thread; + public Il2CppMetadataGenericContainerHandle genericContainerHandle; + public uint instance_size; + public uint stack_slot_size; + public uint actualSize; + public uint element_size; + public int native_size; + public uint static_fields_size; + public uint thread_static_fields_size; + public int thread_static_fields_offset; + public uint flags; + public uint token; + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + public byte typeHierarchyDepth; + public byte genericRecursionDepth; + public byte rank; + public byte minimumAlignment; + public byte packingSize; + public Bitfield0 _bitfield0; + public Bitfield1 _bitfield1; + internal enum Bitfield0 : byte + { + BIT_initialized_and_no_error = 0, + initialized_and_no_error = (1 << BIT_initialized_and_no_error), + BIT_initialized = 1, + initialized = (1 << BIT_initialized), + BIT_enumtype = 2, + enumtype = (1 << BIT_enumtype), + BIT_nullabletype = 3, + nullabletype = (1 << BIT_nullabletype), + BIT_is_generic = 4, + is_generic = (1 << BIT_is_generic), + BIT_has_references = 5, + has_references = (1 << BIT_has_references), + BIT_init_pending = 6, + init_pending = (1 << BIT_init_pending), + BIT_size_init_pending = 7, + size_init_pending = (1 << BIT_size_init_pending), + } + internal enum Bitfield1 : byte + { + BIT_size_inited = 0, + size_inited = (1 << BIT_size_inited), + BIT_has_finalize = 1, + has_finalize = (1 << BIT_has_finalize), + BIT_has_cctor = 2, + has_cctor = (1 << BIT_has_cctor), + BIT_is_blittable = 3, + is_blittable = (1 << BIT_is_blittable), + BIT_is_import_or_windows_runtime = 4, + is_import_or_windows_runtime = (1 << BIT_is_import_or_windows_runtime), + BIT_is_vtable_initialized = 5, + is_vtable_initialized = (1 << BIT_is_vtable_initialized), + BIT_is_byref_like = 6, + is_byref_like = (1 << BIT_is_byref_like), + } + } + internal class NativeStructWrapper : INativeClassStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppClass_39_0._bitfield0)).ToInt32(); + private static int _bitfield1offset = Marshal.OffsetOf(nameof(Il2CppClass_39_0._bitfield1)).ToInt32(); + public nint Pointer { get; } + private Il2CppClass_39_0* _ => (Il2CppClass_39_0*)Pointer; + public unsafe VirtualInvokeData* VTable => (VirtualInvokeData*)nint.Add(Pointer, sizeof(Il2CppClass_39_0)); + public Il2CppClass* ClassPointer => (Il2CppClass*)Pointer; + public INativeTypeStruct ByValArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->byval_arg); + public INativeTypeStruct ThisArg => UnityVersionHandler.Wrap((Il2CppTypeStruct*)&_->this_arg); + public ref uint InstanceSize => ref _->instance_size; + public ref ushort VTableCount => ref _->vtable_count; + public ref ushort InterfaceCount => ref _->interfaces_count; + public ref ushort InterfaceOffsetsCount => ref _->interface_offsets_count; + public ref byte TypeHierarchyDepth => ref _->typeHierarchyDepth; + public ref int NativeSize => ref _->native_size; + public ref uint ActualSize => ref _->actualSize; + public ref ushort MethodCount => ref _->method_count; + public ref ushort FieldCount => ref _->field_count; + public ref Il2CppClassAttributes Flags => ref *(Il2CppClassAttributes*)&_->flags; + public ref nint Name => ref *(nint*)&_->name; + public ref nint Namespace => ref *(nint*)&_->namespaze; + public ref Il2CppImage* Image => ref _->image; + public ref Il2CppClass* Parent => ref _->parent; + public ref Il2CppClass* ElementClass => ref _->element_class; + public ref Il2CppClass* CastClass => ref _->castClass; + public ref Il2CppClass* DeclaringType => ref _->declaringType; + public ref Il2CppClass* Class => ref _->klass; + public ref Il2CppFieldInfo* Fields => ref _->fields; + public ref Il2CppMethodInfo** Methods => ref _->methods; + public ref Il2CppClass** ImplementedInterfaces => ref _->implementedInterfaces; + public ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets => ref _->interfaceOffsets; + public ref Il2CppClass** TypeHierarchy => ref _->typeHierarchy; + public bool ValueType + { + get => ByValArg.ValueType && ThisArg.ValueType; + set { } + } + public bool Initialized + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_initialized); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_initialized, value); + } + public bool EnumType + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_enumtype); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_enumtype, value); + } + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_is_generic, value); + } + public bool HasReferences + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_has_references); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_has_references, value); + } + public bool SizeInited + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_39_0.Bitfield1.BIT_size_inited); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_39_0.Bitfield1.BIT_size_inited, value); + } + public bool HasFinalize + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_39_0.Bitfield1.BIT_has_finalize); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_39_0.Bitfield1.BIT_has_finalize, value); + } + public bool IsVtableInitialized + { + get => this.CheckBit(_bitfield1offset, (int)Il2CppClass_39_0.Bitfield1.BIT_is_vtable_initialized); + set => this.SetBit(_bitfield1offset, (int)Il2CppClass_39_0.Bitfield1.BIT_is_vtable_initialized, value); + } + public bool InitializedAndNoError + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_initialized_and_no_error); + set => this.SetBit(_bitfield0offset, (int)Il2CppClass_39_0.Bitfield0.BIT_initialized_and_no_error, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Interfaces.cs new file mode 100644 index 00000000..a6c2283f --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/Interfaces.cs @@ -0,0 +1,49 @@ +// +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Class +{ + public interface INativeClassStructHandler : INativeStructHandler + { + INativeClassStruct CreateNewStruct(int vTableSlots); + unsafe INativeClassStruct Wrap(Il2CppClass* pointer); + } + public interface INativeClassStruct : INativeStruct + { + unsafe VirtualInvokeData* VTable { get; } + unsafe Il2CppClass* ClassPointer { get; } + INativeTypeStruct ByValArg { get; } + INativeTypeStruct ThisArg { get; } + ref uint InstanceSize { get; } + ref ushort VTableCount { get; } + ref ushort InterfaceCount { get; } + ref ushort InterfaceOffsetsCount { get; } + ref byte TypeHierarchyDepth { get; } + ref int NativeSize { get; } + ref uint ActualSize { get; } + ref ushort MethodCount { get; } + ref ushort FieldCount { get; } + ref Il2CppClassAttributes Flags { get; } + ref nint Name { get; } + ref nint Namespace { get; } + unsafe ref Il2CppImage* Image { get; } + unsafe ref Il2CppClass* Parent { get; } + unsafe ref Il2CppClass* ElementClass { get; } + unsafe ref Il2CppClass* CastClass { get; } + unsafe ref Il2CppClass* DeclaringType { get; } + unsafe ref Il2CppClass* Class { get; } + unsafe ref Il2CppFieldInfo* Fields { get; } + unsafe ref Il2CppMethodInfo** Methods { get; } + unsafe ref Il2CppClass** ImplementedInterfaces { get; } + unsafe ref Il2CppRuntimeInterfaceOffsetPair* InterfaceOffsets { get; } + unsafe ref Il2CppClass** TypeHierarchy { get; } + bool ValueType { get; set; } + bool Initialized { get; set; } + bool EnumType { get; set; } + bool IsGeneric { get; set; } + bool HasReferences { get; set; } + bool SizeInited { get; set; } + bool HasFinalize { get; set; } + bool IsVtableInitialized { get; set; } + bool InitializedAndNoError { get; set; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/UnityVersionHandler.cs new file mode 100644 index 00000000..7d4134b3 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Class/UnityVersionHandler.cs @@ -0,0 +1,181 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Class; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetClassStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(6000, 5, 0, UnityVersionType.Alpha, 5)) + { + ClassStructHandler = NativeClassStructHandler_105_0.Instance; + } + else if (version >= new UnityVersion(6000, 5, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_104_0.Instance; + } + else if (version >= new UnityVersion(6000, 4, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_29_2.Instance; + } + else if (version >= new UnityVersion(6000, 3, 3, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_39_0.Instance; + } + else if (version >= new UnityVersion(2023, 1, 0, UnityVersionType.Alpha, 15)) + { + ClassStructHandler = NativeClassStructHandler_29_2.Instance; + } + else if (version >= new UnityVersion(2022, 2, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_29_1.Instance; + } + else if (version >= new UnityVersion(2022, 1, 0, UnityVersionType.Alpha, 10)) + { + ClassStructHandler = NativeClassStructHandler_29_0.Instance; + } + else if (version >= new UnityVersion(2022, 1, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_27_5.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Beta, 13)) + { + ClassStructHandler = NativeClassStructHandler_29_0.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Alpha, 21)) + { + ClassStructHandler = NativeClassStructHandler_27_5.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Alpha, 12)) + { + ClassStructHandler = NativeClassStructHandler_27_4.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_27_3.Instance; + } + else if (version >= new UnityVersion(2021, 1, 24, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_27_2.Instance; + } + else if (version >= new UnityVersion(2021, 1, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_27_1.Instance; + } + else if (version >= new UnityVersion(2020, 3, 20, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_27_0.Instance; + } + else if (version >= new UnityVersion(2020, 2, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_24_7.Instance; + } + else if (version >= new UnityVersion(2019, 2, 0, UnityVersionType.Alpha, 6)) + { + ClassStructHandler = NativeClassStructHandler_24_6.Instance; + } + else if (version >= new UnityVersion(2019, 2, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_24_5.Instance; + } + else if (version >= new UnityVersion(2019, 1, 0, UnityVersionType.Beta, 5)) + { + ClassStructHandler = NativeClassStructHandler_24_6.Instance; + } + else if (version >= new UnityVersion(2019, 1, 0, UnityVersionType.Alpha, 11)) + { + ClassStructHandler = NativeClassStructHandler_24_5.Instance; + } + else if (version >= new UnityVersion(2019, 1, 0, UnityVersionType.Alpha, 8)) + { + ClassStructHandler = NativeClassStructHandler_24_3.Instance; + } + else if (version >= new UnityVersion(2019, 1, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2018, 3, 8, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_24_4.Instance; + } + else if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Beta, 11)) + { + ClassStructHandler = NativeClassStructHandler_24_3.Instance; + } + else if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2018, 2, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Beta, 5)) + { + ClassStructHandler = NativeClassStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(5, 6, 0, UnityVersionType.Beta, 3)) + { + ClassStructHandler = NativeClassStructHandler_23_1.Instance; + } + else if (version >= new UnityVersion(5, 6, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_23_0.Instance; + } + else if (version >= new UnityVersion(5, 5, 0, UnityVersionType.Patch, 2)) + { + ClassStructHandler = NativeClassStructHandler_22_1.Instance; + } + else if (version >= new UnityVersion(5, 5, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_22_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 3, UnityVersionType.Patch, 4)) + { + ClassStructHandler = NativeClassStructHandler_21_2.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 22)) + { + ClassStructHandler = NativeClassStructHandler_21_1.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 19)) + { + ClassStructHandler = NativeClassStructHandler_21_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 8)) + { + ClassStructHandler = NativeClassStructHandler_20_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 3)) + { + ClassStructHandler = NativeClassStructHandler_19_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + ClassStructHandler = NativeClassStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 5, UnityVersionType.Patch, 3)) + { + ClassStructHandler = NativeClassStructHandler_21_1.Instance; + } + else if (version >= new UnityVersion(5, 3, 5, UnityVersionType.Patch, 1)) + { + ClassStructHandler = NativeClassStructHandler_21_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 2, UnityVersionType.Patch, 2)) + { + ClassStructHandler = NativeClassStructHandler_20_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 1, UnityVersionType.Patch, 3)) + { + ClassStructHandler = NativeClassStructHandler_19_0.Instance; + } + else + { + ClassStructHandler = NativeClassStructHandler_16_0.Instance; + } + } + private static INativeClassStructHandler ClassStructHandler { get; set; } = NativeClassStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_16_0.cs similarity index 70% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_16_0.cs index 41a3ef8e..56caf8f8 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_16_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.EventInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.EventInfo { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativeEventInfoStructHandler_16_0 : INativeEventInfoStructHandler { - public int Size() => sizeof(Il2CppEventInfo_16_0); + private NativeEventInfoStructHandler_16_0() + { + } public INativeEventInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppEventInfo_16_0* _ = (Il2CppEventInfo_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeEventInfoStruct CreateNewStruct() public INativeEventInfoStruct Wrap(Il2CppEventInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeEventInfoStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppEventInfo_16_0); internal unsafe struct Il2CppEventInfo_16_0 { public byte* name; @@ -28,21 +33,18 @@ internal unsafe struct Il2CppEventInfo_16_0 public Il2CppMethodInfo* raise; public int customAttributeIndex; } - internal class NativeStructWrapper : INativeEventInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppEventInfo_16_0* _ => (Il2CppEventInfo_16_0*)Pointer; public Il2CppEventInfo* EventInfoPointer => (Il2CppEventInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppTypeStruct* EventType => ref _->eventType; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppMethodInfo* Add => ref _->add; public ref Il2CppMethodInfo* Remove => ref _->remove; public ref Il2CppMethodInfo* Raise => ref _->raise; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_19_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_19_0.cs similarity index 68% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_19_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_19_0.cs index b2a0b52e..bdc199e9 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_19_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_19_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.EventInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.EventInfo { - [ApplicableToUnityVersionsSince("5.3.2")] + [ApplicableToUnityVersionsSince("5.3.1p3")] + [ApplicableToUnityVersionsSince("5.4.0b3")] public unsafe class NativeEventInfoStructHandler_19_0 : INativeEventInfoStructHandler { - public int Size() => sizeof(Il2CppEventInfo_19_0); + private NativeEventInfoStructHandler_19_0() + { + } public INativeEventInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppEventInfo_19_0* _ = (Il2CppEventInfo_19_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeEventInfoStruct CreateNewStruct() public INativeEventInfoStruct Wrap(Il2CppEventInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeEventInfoStructHandler_19_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppEventInfo_19_0); internal unsafe struct Il2CppEventInfo_19_0 { public byte* name; @@ -29,21 +34,18 @@ internal unsafe struct Il2CppEventInfo_19_0 public int customAttributeIndex; public uint token; } - internal class NativeStructWrapper : INativeEventInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppEventInfo_19_0* _ => (Il2CppEventInfo_19_0*)Pointer; public Il2CppEventInfo* EventInfoPointer => (Il2CppEventInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppTypeStruct* EventType => ref _->eventType; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppMethodInfo* Add => ref _->add; public ref Il2CppMethodInfo* Remove => ref _->remove; public ref Il2CppMethodInfo* Raise => ref _->raise; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_24_0.cs similarity index 71% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_24_0.cs index c5158fff..4de97f15 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/EventInfo/EventInfo_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/EventInfo_24_0.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.EventInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.EventInfo { [ApplicableToUnityVersionsSince("2018.3.0")] public unsafe class NativeEventInfoStructHandler_24_0 : INativeEventInfoStructHandler { - public int Size() => sizeof(Il2CppEventInfo_24_0); + private NativeEventInfoStructHandler_24_0() + { + } public INativeEventInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppEventInfo_24_0* _ = (Il2CppEventInfo_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeEventInfoStruct CreateNewStruct() public INativeEventInfoStruct Wrap(Il2CppEventInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeEventInfoStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppEventInfo_24_0); internal unsafe struct Il2CppEventInfo_24_0 { public byte* name; @@ -28,21 +32,18 @@ internal unsafe struct Il2CppEventInfo_24_0 public Il2CppMethodInfo* raise; public uint token; } - internal class NativeStructWrapper : INativeEventInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppEventInfo_24_0* _ => (Il2CppEventInfo_24_0*)Pointer; public Il2CppEventInfo* EventInfoPointer => (Il2CppEventInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppTypeStruct* EventType => ref _->eventType; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppMethodInfo* Add => ref _->add; public ref Il2CppMethodInfo* Remove => ref _->remove; public ref Il2CppMethodInfo* Raise => ref _->raise; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/Interfaces.cs new file mode 100644 index 00000000..44082efa --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/Interfaces.cs @@ -0,0 +1,19 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.EventInfo +{ + public interface INativeEventInfoStructHandler : INativeStructHandler + { + INativeEventInfoStruct CreateNewStruct(); + unsafe INativeEventInfoStruct Wrap(Il2CppEventInfo* pointer); + } + public interface INativeEventInfoStruct : INativeStruct + { + unsafe Il2CppEventInfo* EventInfoPointer { get; } + ref nint Name { get; } + unsafe ref Il2CppTypeStruct* EventType { get; } + unsafe ref Il2CppClass* Parent { get; } + unsafe ref Il2CppMethodInfo* Add { get; } + unsafe ref Il2CppMethodInfo* Remove { get; } + unsafe ref Il2CppMethodInfo* Raise { get; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/UnityVersionHandler.cs new file mode 100644 index 00000000..8d2d52fa --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/EventInfo/UnityVersionHandler.cs @@ -0,0 +1,33 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.EventInfo; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetEventInfoStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + EventInfoStructHandler = NativeEventInfoStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 3)) + { + EventInfoStructHandler = NativeEventInfoStructHandler_19_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + EventInfoStructHandler = NativeEventInfoStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 1, UnityVersionType.Patch, 3)) + { + EventInfoStructHandler = NativeEventInfoStructHandler_19_0.Instance; + } + else + { + EventInfoStructHandler = NativeEventInfoStructHandler_16_0.Instance; + } + } + private static INativeEventInfoStructHandler EventInfoStructHandler { get; set; } = NativeEventInfoStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_16_0.cs similarity index 76% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_16_0.cs index 3a5b50ea..b1ef932a 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_16_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Exception +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativeExceptionStructHandler_16_0 : INativeExceptionStructHandler { - public int Size() => sizeof(Il2CppException_16_0); + private NativeExceptionStructHandler_16_0() + { + } public INativeExceptionStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppException_16_0* _ = (Il2CppException_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeExceptionStruct CreateNewStruct() public INativeExceptionStruct Wrap(Il2CppException* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeExceptionStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppException_16_0); internal unsafe struct Il2CppException_16_0 { public Il2CppObject _object; @@ -33,11 +38,10 @@ internal unsafe struct Il2CppException_16_0 public Il2CppString* source; public Il2CppObject* _data; } - internal class NativeStructWrapper : INativeExceptionStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppException_16_0* _ => (Il2CppException_16_0*)Pointer; public Il2CppException* ExceptionPointer => (Il2CppException*)Pointer; public ref Il2CppException* InnerException => ref *(Il2CppException**)&_->inner_ex; @@ -47,7 +51,5 @@ internal class NativeStructWrapper : INativeExceptionStruct public ref Il2CppString* StackTrace => ref _->stack_trace; public ref Il2CppString* RemoteStackTrace => ref _->remote_stack_trace; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_20_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_20_0.cs similarity index 74% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_20_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_20_0.cs index cf7f829d..b66fc1bc 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_20_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_20_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Exception +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception { - [ApplicableToUnityVersionsSince("5.3.3")] + [ApplicableToUnityVersionsSince("5.3.2p2")] + [ApplicableToUnityVersionsSince("5.4.0b8")] public unsafe class NativeExceptionStructHandler_20_0 : INativeExceptionStructHandler { - public int Size() => sizeof(Il2CppException_20_0); + private NativeExceptionStructHandler_20_0() + { + } public INativeExceptionStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppException_20_0* _ = (Il2CppException_20_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeExceptionStruct CreateNewStruct() public INativeExceptionStruct Wrap(Il2CppException* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeExceptionStructHandler_20_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppException_20_0); internal unsafe struct Il2CppException_20_0 { public Il2CppObject _object; @@ -33,11 +38,10 @@ internal unsafe struct Il2CppException_20_0 public Il2CppString* source; public Il2CppObject* _data; } - internal class NativeStructWrapper : INativeExceptionStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppException_20_0* _ => (Il2CppException_20_0*)Pointer; public Il2CppException* ExceptionPointer => (Il2CppException*)Pointer; public ref Il2CppException* InnerException => ref *(Il2CppException**)&_->inner_ex; @@ -47,7 +51,5 @@ internal class NativeStructWrapper : INativeExceptionStruct public ref Il2CppString* StackTrace => ref _->stack_trace; public ref Il2CppString* RemoteStackTrace => ref _->remote_stack_trace; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_21_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_21_0.cs similarity index 74% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_21_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_21_0.cs index 06cbb443..0eacd21d 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_21_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_21_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Exception +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception { - [ApplicableToUnityVersionsSince("5.3.5")] + [ApplicableToUnityVersionsSince("5.3.4p3")] + [ApplicableToUnityVersionsSince("5.4.0b15")] public unsafe class NativeExceptionStructHandler_21_0 : INativeExceptionStructHandler { - public int Size() => sizeof(Il2CppException_21_0); + private NativeExceptionStructHandler_21_0() + { + } public INativeExceptionStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppException_21_0* _ = (Il2CppException_21_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeExceptionStruct CreateNewStruct() public INativeExceptionStruct Wrap(Il2CppException* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeExceptionStructHandler_21_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppException_21_0); internal unsafe struct Il2CppException_21_0 { public Il2CppObject _object; @@ -33,11 +38,10 @@ internal unsafe struct Il2CppException_21_0 public Il2CppString* source; public Il2CppObject* _data; } - internal class NativeStructWrapper : INativeExceptionStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppException_21_0* _ => (Il2CppException_21_0*)Pointer; public Il2CppException* ExceptionPointer => (Il2CppException*)Pointer; public ref Il2CppException* InnerException => ref _->inner_ex; @@ -47,7 +51,5 @@ internal class NativeStructWrapper : INativeExceptionStruct public ref Il2CppString* StackTrace => ref _->stack_trace; public ref Il2CppString* RemoteStackTrace => ref _->remote_stack_trace; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_22_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_22_0.cs new file mode 100644 index 00000000..b0b7276a --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_22_0.cs @@ -0,0 +1,58 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception +{ + [ApplicableToUnityVersionsSince("5.5.0")] + public unsafe class NativeExceptionStructHandler_22_0 : INativeExceptionStructHandler + { + private NativeExceptionStructHandler_22_0() + { + } + public INativeExceptionStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppException_22_0* _ = (Il2CppException_22_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeExceptionStruct Wrap(Il2CppException* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeExceptionStructHandler_22_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppException_22_0); + internal unsafe struct Il2CppException_22_0 + { + public Il2CppObject _object; + public Il2CppString* className; + public Il2CppString* message; + public Il2CppObject* _data; + public Il2CppException* inner_ex; + public Il2CppString* _helpURL; + public void* trace_ips; + public Il2CppString* stack_trace; + public Il2CppString* remote_stack_trace; + public int remote_stack_index; + public Il2CppObject* _dynamicMethods; + public int hresult; + public Il2CppString* source; + public Il2CppObject* safeSerializationManager; + public void* captured_traces; + public void* native_trace_ips; + } + internal class NativeStructWrapper : INativeExceptionStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppException_22_0* _ => (Il2CppException_22_0*)Pointer; + public Il2CppException* ExceptionPointer => (Il2CppException*)Pointer; + public ref Il2CppException* InnerException => ref _->inner_ex; + public ref Il2CppString* Message => ref _->message; + public ref Il2CppString* HelpLink => ref _->_helpURL; + public ref Il2CppString* ClassName => ref _->className; + public ref Il2CppString* StackTrace => ref _->stack_trace; + public ref Il2CppString* RemoteStackTrace => ref _->remote_stack_trace; + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_23_0.cs similarity index 66% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_23_0.cs index 9ed8b731..0a412ae8 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_23_0.cs @@ -1,24 +1,29 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Exception +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception { - [ApplicableToUnityVersionsSince("2019.3.0")] - public unsafe class NativeExceptionStructHandler_24_0 : INativeExceptionStructHandler + [ApplicableToUnityVersionsSince("5.6.0b3")] + [ApplicableToUnityVersionsSince("2018.1.0b5")] + public unsafe class NativeExceptionStructHandler_23_0 : INativeExceptionStructHandler { - public int Size() => sizeof(Il2CppException_24_0); + private NativeExceptionStructHandler_23_0() + { + } public INativeExceptionStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); - Il2CppException_24_0* _ = (Il2CppException_24_0*)ptr; + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppException_23_0* _ = (Il2CppException_23_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); } public INativeExceptionStruct Wrap(Il2CppException* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } - internal unsafe struct Il2CppException_24_0 + public static NativeExceptionStructHandler_23_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppException_23_0); + internal unsafe struct Il2CppException_23_0 { public Il2CppObject _object; public Il2CppString* className; @@ -37,12 +42,11 @@ internal unsafe struct Il2CppException_24_0 public void* captured_traces; public void* native_trace_ips; } - internal class NativeStructWrapper : INativeExceptionStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } - private Il2CppException_24_0* _ => (Il2CppException_24_0*)Pointer; + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppException_23_0* _ => (Il2CppException_23_0*)Pointer; public Il2CppException* ExceptionPointer => (Il2CppException*)Pointer; public ref Il2CppException* InnerException => ref _->inner_ex; public ref Il2CppString* Message => ref _->message; @@ -51,7 +55,5 @@ internal class NativeStructWrapper : INativeExceptionStruct public ref Il2CppString* StackTrace => ref _->stack_trace; public ref Il2CppString* RemoteStackTrace => ref _->remote_stack_trace; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_23_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_23_1.cs new file mode 100644 index 00000000..761f0f05 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_23_1.cs @@ -0,0 +1,58 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception +{ + [ApplicableToUnityVersionsSince("5.6.0b6")] + public unsafe class NativeExceptionStructHandler_23_1 : INativeExceptionStructHandler + { + private NativeExceptionStructHandler_23_1() + { + } + public INativeExceptionStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppException_23_1* _ = (Il2CppException_23_1*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeExceptionStruct Wrap(Il2CppException* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeExceptionStructHandler_23_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppException_23_1); + internal unsafe struct Il2CppException_23_1 + { + public Il2CppObject Object; + public Il2CppString* className; + public Il2CppString* message; + public Il2CppObject* _data; + public Il2CppException* inner_ex; + public Il2CppString* _helpURL; + public void* trace_ips; + public Il2CppString* stack_trace; + public Il2CppString* remote_stack_trace; + public int remote_stack_index; + public Il2CppObject* _dynamicMethods; + public il2cpp_hresult_t hresult; + public Il2CppString* source; + public Il2CppObject* safeSerializationManager; + public void* captured_traces; + public void* native_trace_ips; + } + internal class NativeStructWrapper : INativeExceptionStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppException_23_1* _ => (Il2CppException_23_1*)Pointer; + public Il2CppException* ExceptionPointer => (Il2CppException*)Pointer; + public ref Il2CppException* InnerException => ref _->inner_ex; + public ref Il2CppString* Message => ref _->message; + public ref Il2CppString* HelpLink => ref _->_helpURL; + public ref Il2CppString* ClassName => ref _->className; + public ref Il2CppString* StackTrace => ref _->stack_trace; + public ref Il2CppString* RemoteStackTrace => ref _->remote_stack_trace; + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_29_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_27_0.cs similarity index 67% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_29_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_27_0.cs index e1bc326a..a27826dc 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Exception/Exception_29_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Exception_27_0.cs @@ -1,24 +1,28 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Exception +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception { - [ApplicableToUnityVersionsSince("2021.2.0")] - public unsafe class NativeExceptionStructHandler_29_0 : INativeExceptionStructHandler + [ApplicableToUnityVersionsSince("2021.2.0a19")] + public unsafe class NativeExceptionStructHandler_27_0 : INativeExceptionStructHandler { - public int Size() => sizeof(Il2CppException_29_0); + private NativeExceptionStructHandler_27_0() + { + } public INativeExceptionStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); - Il2CppException_29_0* _ = (Il2CppException_29_0*)ptr; + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppException_27_0* _ = (Il2CppException_27_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); } public INativeExceptionStruct Wrap(Il2CppException* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } - internal unsafe struct Il2CppException_29_0 + public static NativeExceptionStructHandler_27_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppException_27_0); + internal unsafe struct Il2CppException_27_0 { public Il2CppObject _object; public Il2CppString* className; @@ -38,12 +42,11 @@ internal unsafe struct Il2CppException_29_0 public void* native_trace_ips; public int caught_in_unmanaged; } - internal class NativeStructWrapper : INativeExceptionStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } - private Il2CppException_29_0* _ => (Il2CppException_29_0*)Pointer; + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppException_27_0* _ => (Il2CppException_27_0*)Pointer; public Il2CppException* ExceptionPointer => (Il2CppException*)Pointer; public ref Il2CppException* InnerException => ref _->inner_ex; public ref Il2CppString* Message => ref _->message; @@ -52,7 +55,5 @@ internal class NativeStructWrapper : INativeExceptionStruct public ref Il2CppString* StackTrace => ref _->stack_trace; public ref Il2CppString* RemoteStackTrace => ref _->remote_stack_trace; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Interfaces.cs new file mode 100644 index 00000000..8224a51f --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/Interfaces.cs @@ -0,0 +1,19 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Exception +{ + public interface INativeExceptionStructHandler : INativeStructHandler + { + INativeExceptionStruct CreateNewStruct(); + unsafe INativeExceptionStruct Wrap(Il2CppException* pointer); + } + public interface INativeExceptionStruct : INativeStruct + { + unsafe Il2CppException* ExceptionPointer { get; } + unsafe ref Il2CppException* InnerException { get; } + unsafe ref Il2CppString* Message { get; } + unsafe ref Il2CppString* HelpLink { get; } + unsafe ref Il2CppString* ClassName { get; } + unsafe ref Il2CppString* StackTrace { get; } + unsafe ref Il2CppString* RemoteStackTrace { get; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/UnityVersionHandler.cs new file mode 100644 index 00000000..79075aa4 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Exception/UnityVersionHandler.cs @@ -0,0 +1,57 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Exception; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetExceptionStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Alpha, 19)) + { + ExceptionStructHandler = NativeExceptionStructHandler_27_0.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Beta, 5)) + { + ExceptionStructHandler = NativeExceptionStructHandler_23_0.Instance; + } + else if (version >= new UnityVersion(5, 6, 0, UnityVersionType.Beta, 6)) + { + ExceptionStructHandler = NativeExceptionStructHandler_23_1.Instance; + } + else if (version >= new UnityVersion(5, 6, 0, UnityVersionType.Beta, 3)) + { + ExceptionStructHandler = NativeExceptionStructHandler_23_0.Instance; + } + else if (version >= new UnityVersion(5, 5, 0, UnityVersionType.Alpha, 0)) + { + ExceptionStructHandler = NativeExceptionStructHandler_22_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 15)) + { + ExceptionStructHandler = NativeExceptionStructHandler_21_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 8)) + { + ExceptionStructHandler = NativeExceptionStructHandler_20_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + ExceptionStructHandler = NativeExceptionStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 4, UnityVersionType.Patch, 3)) + { + ExceptionStructHandler = NativeExceptionStructHandler_21_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 2, UnityVersionType.Patch, 2)) + { + ExceptionStructHandler = NativeExceptionStructHandler_20_0.Instance; + } + else + { + ExceptionStructHandler = NativeExceptionStructHandler_16_0.Instance; + } + } + private static INativeExceptionStructHandler ExceptionStructHandler { get; set; } = NativeExceptionStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_16_0.cs similarity index 66% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_16_0.cs index 9cdfd89f..16ce6c24 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_16_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.FieldInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.FieldInfo { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativeFieldInfoStructHandler_16_0 : INativeFieldInfoStructHandler { - public int Size() => sizeof(Il2CppFieldInfo_16_0); + private NativeFieldInfoStructHandler_16_0() + { + } public INativeFieldInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppFieldInfo_16_0* _ = (Il2CppFieldInfo_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeFieldInfoStruct CreateNewStruct() public INativeFieldInfoStruct Wrap(Il2CppFieldInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeFieldInfoStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppFieldInfo_16_0); internal unsafe struct Il2CppFieldInfo_16_0 { public byte* name; @@ -26,19 +31,16 @@ internal unsafe struct Il2CppFieldInfo_16_0 public int offset; public int customAttributeIndex; } - internal class NativeStructWrapper : INativeFieldInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppFieldInfo_16_0* _ => (Il2CppFieldInfo_16_0*)Pointer; public Il2CppFieldInfo* FieldInfoPointer => (Il2CppFieldInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppTypeStruct* Type => ref _->type; public ref Il2CppClass* Parent => ref _->parent; public ref int Offset => ref _->offset; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_19_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_19_0.cs similarity index 64% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_19_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_19_0.cs index b84d6d4e..7acdf102 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_19_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_19_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.FieldInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.FieldInfo { - [ApplicableToUnityVersionsSince("5.3.2")] + [ApplicableToUnityVersionsSince("5.3.1p3")] + [ApplicableToUnityVersionsSince("5.4.0b3")] public unsafe class NativeFieldInfoStructHandler_19_0 : INativeFieldInfoStructHandler { - public int Size() => sizeof(Il2CppFieldInfo_19_0); + private NativeFieldInfoStructHandler_19_0() + { + } public INativeFieldInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppFieldInfo_19_0* _ = (Il2CppFieldInfo_19_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeFieldInfoStruct CreateNewStruct() public INativeFieldInfoStruct Wrap(Il2CppFieldInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeFieldInfoStructHandler_19_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppFieldInfo_19_0); internal unsafe struct Il2CppFieldInfo_19_0 { public byte* name; @@ -27,19 +32,16 @@ internal unsafe struct Il2CppFieldInfo_19_0 public int customAttributeIndex; public uint token; } - internal class NativeStructWrapper : INativeFieldInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppFieldInfo_19_0* _ => (Il2CppFieldInfo_19_0*)Pointer; public Il2CppFieldInfo* FieldInfoPointer => (Il2CppFieldInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppTypeStruct* Type => ref _->type; public ref Il2CppClass* Parent => ref _->parent; public ref int Offset => ref _->offset; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_24_0.cs similarity index 68% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_24_0.cs index f94f88e6..0c51d75f 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/FieldInfo/FieldInfo_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/FieldInfo_24_0.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.FieldInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.FieldInfo { [ApplicableToUnityVersionsSince("2018.3.0")] public unsafe class NativeFieldInfoStructHandler_24_0 : INativeFieldInfoStructHandler { - public int Size() => sizeof(Il2CppFieldInfo_24_0); + private NativeFieldInfoStructHandler_24_0() + { + } public INativeFieldInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppFieldInfo_24_0* _ = (Il2CppFieldInfo_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeFieldInfoStruct CreateNewStruct() public INativeFieldInfoStruct Wrap(Il2CppFieldInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeFieldInfoStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppFieldInfo_24_0); internal unsafe struct Il2CppFieldInfo_24_0 { public byte* name; @@ -26,19 +30,16 @@ internal unsafe struct Il2CppFieldInfo_24_0 public int offset; public uint token; } - internal class NativeStructWrapper : INativeFieldInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppFieldInfo_24_0* _ => (Il2CppFieldInfo_24_0*)Pointer; public Il2CppFieldInfo* FieldInfoPointer => (Il2CppFieldInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppTypeStruct* Type => ref _->type; public ref Il2CppClass* Parent => ref _->parent; public ref int Offset => ref _->offset; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/Interfaces.cs new file mode 100644 index 00000000..860e9c5c --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/Interfaces.cs @@ -0,0 +1,17 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.FieldInfo +{ + public interface INativeFieldInfoStructHandler : INativeStructHandler + { + INativeFieldInfoStruct CreateNewStruct(); + unsafe INativeFieldInfoStruct Wrap(Il2CppFieldInfo* pointer); + } + public interface INativeFieldInfoStruct : INativeStruct + { + unsafe Il2CppFieldInfo* FieldInfoPointer { get; } + ref nint Name { get; } + unsafe ref Il2CppTypeStruct* Type { get; } + unsafe ref Il2CppClass* Parent { get; } + ref int Offset { get; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/UnityVersionHandler.cs new file mode 100644 index 00000000..e9726a68 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/FieldInfo/UnityVersionHandler.cs @@ -0,0 +1,33 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.FieldInfo; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetFieldInfoStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + FieldInfoStructHandler = NativeFieldInfoStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 3)) + { + FieldInfoStructHandler = NativeFieldInfoStructHandler_19_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + FieldInfoStructHandler = NativeFieldInfoStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 1, UnityVersionType.Patch, 3)) + { + FieldInfoStructHandler = NativeFieldInfoStructHandler_19_0.Instance; + } + else + { + FieldInfoStructHandler = NativeFieldInfoStructHandler_16_0.Instance; + } + } + private static INativeFieldInfoStructHandler FieldInfoStructHandler { get; set; } = NativeFieldInfoStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_16_0.cs similarity index 60% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_16_0.cs index aefc0cf6..f7c98cb7 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_16_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativeImageStructHandler_16_0 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_16_0); + private NativeImageStructHandler_16_0() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppImage_16_0* _ = (Il2CppImage_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeImageStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_16_0); internal unsafe struct Il2CppImage_16_0 { public byte* name; @@ -27,21 +32,18 @@ internal unsafe struct Il2CppImage_16_0 public int entryPointIndex; public void* nameToClassHashTable; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private byte _dynamicDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppImage_16_0* _ => (Il2CppImage_16_0*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => false; - public ref Il2CppAssembly* Assembly => throw new NotSupportedException(); + public ref Il2CppAssembly* Assembly => throw new System.NotSupportedException(); public ref byte Dynamic => ref _dynamicDummy; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => throw new NotSupportedException(); + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => throw new System.NotSupportedException(); } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_19_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_19_0.cs similarity index 58% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_19_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_19_0.cs index 335ce2f9..dc25ce7a 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_19_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_19_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { - [ApplicableToUnityVersionsSince("5.3.2")] + [ApplicableToUnityVersionsSince("5.3.1p3")] + [ApplicableToUnityVersionsSince("5.4.0b3")] public unsafe class NativeImageStructHandler_19_0 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_19_0); + private NativeImageStructHandler_19_0() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppImage_19_0* _ = (Il2CppImage_19_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeImageStructHandler_19_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_19_0); internal unsafe struct Il2CppImage_19_0 { public byte* name; @@ -28,21 +33,18 @@ internal unsafe struct Il2CppImage_19_0 public void* nameToClassHashTable; public uint token; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private byte _dynamicDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppImage_19_0* _ => (Il2CppImage_19_0*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => false; - public ref Il2CppAssembly* Assembly => throw new NotSupportedException(); + public ref Il2CppAssembly* Assembly => throw new System.NotSupportedException(); public ref byte Dynamic => ref _dynamicDummy; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => throw new NotSupportedException(); + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => throw new System.NotSupportedException(); } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_0.cs similarity index 62% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_0.cs index 002c3f76..8b6efe91 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { [ApplicableToUnityVersionsSince("2017.1.0")] + [ApplicableToUnityVersionsSince("2017.2.0")] public unsafe class NativeImageStructHandler_24_0 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_24_0); + private NativeImageStructHandler_24_0() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppImage_24_0* _ = (Il2CppImage_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeImageStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_24_0); internal unsafe struct Il2CppImage_24_0 { public byte* name; @@ -30,21 +35,18 @@ internal unsafe struct Il2CppImage_24_0 public void* nameToClassHashTable; public uint token; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private byte _dynamicDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppImage_24_0* _ => (Il2CppImage_24_0*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => false; - public ref Il2CppAssembly* Assembly => throw new NotSupportedException(); + public ref Il2CppAssembly* Assembly => throw new System.NotSupportedException(); public ref byte Dynamic => ref _dynamicDummy; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => throw new NotSupportedException(); + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => throw new System.NotSupportedException(); } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_1.cs similarity index 60% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_1.cs index 583b1558..03727d7c 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_1.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { - [ApplicableToUnityVersionsSince("2017.1.3")] + [ApplicableToUnityVersionsSince("2017.1.2p1")] + [ApplicableToUnityVersionsSince("2017.2.0p2")] public unsafe class NativeImageStructHandler_24_1 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_24_1); + private NativeImageStructHandler_24_1() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppImage_24_1* _ = (Il2CppImage_24_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeImageStructHandler_24_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_24_1); internal unsafe struct Il2CppImage_24_1 { public byte* name; @@ -31,21 +36,18 @@ internal unsafe struct Il2CppImage_24_1 public void* nameToClassHashTable; public uint token; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private byte _dynamicDummy; - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppImage_24_1* _ => (Il2CppImage_24_1*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => true; - public ref Il2CppAssembly* Assembly => throw new NotSupportedException(); + public ref Il2CppAssembly* Assembly => throw new System.NotSupportedException(); public ref byte Dynamic => ref _dynamicDummy; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => ref *(IntPtr*)&_->nameNoExt; + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => ref *(nint*)&_->nameNoExt; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_2.cs similarity index 64% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_2.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_2.cs index 7f13bf7c..018d3e43 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_2.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_2.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { [ApplicableToUnityVersionsSince("2018.1.0")] public unsafe class NativeImageStructHandler_24_2 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_24_2); + private NativeImageStructHandler_24_2() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppImage_24_2* _ = (Il2CppImage_24_2*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeImageStructHandler_24_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_24_2); internal unsafe struct Il2CppImage_24_2 { public byte* name; @@ -30,22 +34,19 @@ internal unsafe struct Il2CppImage_24_2 public int entryPointIndex; public void* nameToClassHashTable; public uint token; - public byte dynamic; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private byte _dynamicDummy; + public nint Pointer { get; } private Il2CppImage_24_2* _ => (Il2CppImage_24_2*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => true; public ref Il2CppAssembly* Assembly => ref _->assembly; - public ref byte Dynamic => ref _->dynamic; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => ref *(IntPtr*)&_->nameNoExt; + public ref byte Dynamic => ref _dynamicDummy; + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => ref *(nint*)&_->nameNoExt; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_3.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_3.cs similarity index 66% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_3.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_3.cs index 0c761322..da01dae9 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_3.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_3.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { - [ApplicableToUnityVersionsSince("2018.3.0")] + [ApplicableToUnityVersionsSince("2018.1.0b5")] public unsafe class NativeImageStructHandler_24_3 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_24_3); + private NativeImageStructHandler_24_3() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppImage_24_3* _ = (Il2CppImage_24_3*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeImageStructHandler_24_3 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_24_3); internal unsafe struct Il2CppImage_24_3 { public byte* name; @@ -27,27 +31,22 @@ internal unsafe struct Il2CppImage_24_3 public uint typeCount; public int exportedTypeStart; public uint exportedTypeCount; - public int customAttributeStart; - public uint customAttributeCount; public int entryPointIndex; public void* nameToClassHashTable; public uint token; public byte dynamic; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppImage_24_3* _ => (Il2CppImage_24_3*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => true; public ref Il2CppAssembly* Assembly => ref _->assembly; public ref byte Dynamic => ref _->dynamic; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => ref *(IntPtr*)&_->nameNoExt; + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => ref *(nint*)&_->nameNoExt; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_4.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_4.cs similarity index 68% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_4.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_4.cs index 76647007..c662c641 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_24_4.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_4.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { - [ApplicableToUnityVersionsSince("2019.1.0")] + [ApplicableToUnityVersionsSince("2018.3.0")] public unsafe class NativeImageStructHandler_24_4 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_24_4); + private NativeImageStructHandler_24_4() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppImage_24_4* _ = (Il2CppImage_24_4*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeImageStructHandler_24_4 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_24_4); internal unsafe struct Il2CppImage_24_4 { public byte* name; @@ -31,24 +35,20 @@ internal unsafe struct Il2CppImage_24_4 public uint customAttributeCount; public int entryPointIndex; public void* nameToClassHashTable; - public void* codeGenModule; public uint token; public byte dynamic; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppImage_24_4* _ => (Il2CppImage_24_4*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => true; public ref Il2CppAssembly* Assembly => ref _->assembly; public ref byte Dynamic => ref _->dynamic; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => ref *(IntPtr*)&_->nameNoExt; + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => ref *(nint*)&_->nameNoExt; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_5.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_5.cs new file mode 100644 index 00000000..5d6fa7e6 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_5.cs @@ -0,0 +1,55 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image +{ + [ApplicableToUnityVersionsSince("2019.1.0a12")] + public unsafe class NativeImageStructHandler_24_5 : INativeImageStructHandler + { + private NativeImageStructHandler_24_5() + { + } + public INativeImageStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppImage_24_5* _ = (Il2CppImage_24_5*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeImageStruct Wrap(Il2CppImage* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeImageStructHandler_24_5 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_24_5); + internal unsafe struct Il2CppImage_24_5 + { + public byte* name; + public byte* nameNoExt; + public Il2CppAssembly* assembly; + public int typeStart; + public uint typeCount; + public int exportedTypeStart; + public uint exportedTypeCount; + public int customAttributeStart; + public uint customAttributeCount; + public int entryPointIndex; + public void* nameToClassHashTable; + public void* codeGenModule; + public uint token; + public byte dynamic; + } + internal class NativeStructWrapper : INativeImageStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppImage_24_5* _ => (Il2CppImage_24_5*)Pointer; + public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; + public bool HasNameNoExt => true; + public ref Il2CppAssembly* Assembly => ref _->assembly; + public ref byte Dynamic => ref _->dynamic; + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => ref *(nint*)&_->nameNoExt; + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_27_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_6.cs similarity index 62% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_27_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_6.cs index 80d72a3a..f8cbb24f 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Image/Image_27_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Image_24_6.cs @@ -1,15 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Image +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image { [ApplicableToUnityVersionsSince("2020.2.0")] - public unsafe class NativeImageStructHandler_27_0 : INativeImageStructHandler + public unsafe class NativeImageStructHandler_24_6 : INativeImageStructHandler { - public int Size() => sizeof(Il2CppImage_27_0); + private NativeImageStructHandler_24_6() + { + } public INativeImageStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); - Il2CppImage_27_0* _ = (Il2CppImage_27_0*)ptr; + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppImage_24_6* _ = (Il2CppImage_24_6*)ptr; *_ = default; Il2CppImageGlobalMetadata* metadata = (Il2CppImageGlobalMetadata*)Marshal.AllocHGlobal(sizeof(Il2CppImageGlobalMetadata)); metadata->image = (Il2CppImage*)_; @@ -19,9 +21,11 @@ public INativeImageStruct CreateNewStruct() public INativeImageStruct Wrap(Il2CppImage* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } - internal unsafe struct Il2CppImage_27_0 + public static NativeImageStructHandler_24_6 Instance { get; } = new(); + public int Size => sizeof(Il2CppImage_24_6); + internal unsafe struct Il2CppImage_24_6 { public byte* name; public byte* nameNoExt; @@ -35,20 +39,17 @@ internal unsafe struct Il2CppImage_27_0 public uint token; public byte dynamic; } - internal class NativeStructWrapper : INativeImageStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } - private Il2CppImage_27_0* _ => (Il2CppImage_27_0*)Pointer; + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppImage_24_6* _ => (Il2CppImage_24_6*)Pointer; public Il2CppImage* ImagePointer => (Il2CppImage*)Pointer; public bool HasNameNoExt => true; public ref Il2CppAssembly* Assembly => ref _->assembly; public ref byte Dynamic => ref _->dynamic; - public ref IntPtr Name => ref *(IntPtr*)&_->name; - public ref IntPtr NameNoExt => ref *(IntPtr*)&_->nameNoExt; + public ref nint Name => ref *(nint*)&_->name; + public ref nint NameNoExt => ref *(nint*)&_->nameNoExt; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Interfaces.cs new file mode 100644 index 00000000..05493033 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/Interfaces.cs @@ -0,0 +1,18 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Image +{ + public interface INativeImageStructHandler : INativeStructHandler + { + INativeImageStruct CreateNewStruct(); + unsafe INativeImageStruct Wrap(Il2CppImage* pointer); + } + public interface INativeImageStruct : INativeStruct + { + unsafe Il2CppImage* ImagePointer { get; } + bool HasNameNoExt { get; } + unsafe ref Il2CppAssembly* Assembly { get; } + ref byte Dynamic { get; } + ref nint Name { get; } + ref nint NameNoExt { get; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/UnityVersionHandler.cs new file mode 100644 index 00000000..7cf66958 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Image/UnityVersionHandler.cs @@ -0,0 +1,65 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Image; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetImageStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2020, 2, 0, UnityVersionType.Alpha, 0)) + { + ImageStructHandler = NativeImageStructHandler_24_6.Instance; + } + else if (version >= new UnityVersion(2019, 1, 0, UnityVersionType.Alpha, 12)) + { + ImageStructHandler = NativeImageStructHandler_24_5.Instance; + } + else if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + ImageStructHandler = NativeImageStructHandler_24_4.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Beta, 5)) + { + ImageStructHandler = NativeImageStructHandler_24_3.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Alpha, 0)) + { + ImageStructHandler = NativeImageStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2017, 2, 0, UnityVersionType.Patch, 2)) + { + ImageStructHandler = NativeImageStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2017, 2, 0, UnityVersionType.Alpha, 0)) + { + ImageStructHandler = NativeImageStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(2017, 1, 2, UnityVersionType.Patch, 1)) + { + ImageStructHandler = NativeImageStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2017, 1, 0, UnityVersionType.Alpha, 0)) + { + ImageStructHandler = NativeImageStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 3)) + { + ImageStructHandler = NativeImageStructHandler_19_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + ImageStructHandler = NativeImageStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 1, UnityVersionType.Patch, 3)) + { + ImageStructHandler = NativeImageStructHandler_19_0.Instance; + } + else + { + ImageStructHandler = NativeImageStructHandler_16_0.Instance; + } + } + private static INativeImageStructHandler ImageStructHandler { get; set; } = NativeImageStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/Interfaces.cs new file mode 100644 index 00000000..3c66b046 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/Interfaces.cs @@ -0,0 +1,27 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo +{ + public interface INativeMethodInfoStructHandler : INativeStructHandler + { + INativeMethodInfoStruct CreateNewStruct(); + unsafe INativeMethodInfoStruct Wrap(Il2CppMethodInfo* pointer); + } + public interface INativeMethodInfoStruct : INativeStruct + { + unsafe Il2CppMethodInfo* MethodInfoPointer { get; } + ref nint Name { get; } + ref ushort Slot { get; } + ref nint MethodPointer { get; } + ref nint VirtualMethodPointer { get; } + unsafe ref Il2CppClass* Class { get; } + ref nint InvokerMethod { get; } + unsafe ref Il2CppTypeStruct* ReturnType { get; } + ref Il2CppMethodFlags Flags { get; } + ref byte ParametersCount { get; } + unsafe ref Il2CppParameterInfo* Parameters { get; } + ref uint Token { get; } + bool IsGeneric { get; set; } + bool IsInflated { get; set; } + bool IsMarshalledFromNative { get; set; } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_16_0.cs similarity index 78% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_16_0.cs index 3fba3bed..f667ccf4 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_16_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativeMethodInfoStructHandler_16_0 : INativeMethodInfoStructHandler { - public int Size() => sizeof(Il2CppMethodInfo_16_0); + private NativeMethodInfoStructHandler_16_0() + { + } public INativeMethodInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppMethodInfo_16_0* _ = (Il2CppMethodInfo_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeMethodInfoStruct CreateNewStruct() public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeMethodInfoStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_16_0); internal unsafe struct Il2CppMethodInfo_16_0 { public void* method; @@ -42,22 +47,20 @@ internal enum Bitfield0 : byte BIT_is_inflated = 1, is_inflated = (1 << BIT_is_inflated), } - } - internal class NativeStructWrapper : INativeMethodInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_16_0._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppMethodInfo_16_0* _ => (Il2CppMethodInfo_16_0*)Pointer; public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref ushort Slot => ref _->slot; - public ref IntPtr MethodPointer => ref *(IntPtr*)&_->method; - public ref IntPtr VirtualMethodPointer => ref *(IntPtr*)&_->method; + public ref nint MethodPointer => ref *(nint*)&_->method; + public ref nint VirtualMethodPointer => ref *(nint*)&_->method; public ref Il2CppClass* Class => ref _->declaring_type; - public ref IntPtr InvokerMethod => ref *(IntPtr*)&_->invoker_method; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; public ref Il2CppTypeStruct* ReturnType => ref _->return_type; public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; public ref byte ParametersCount => ref _->parameters_count; @@ -79,7 +82,5 @@ public bool IsMarshalledFromNative set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_21_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_21_0.cs similarity index 76% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_21_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_21_0.cs index a6fd6b18..737e94f2 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_21_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_21_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo { - [ApplicableToUnityVersionsSince("5.3.6")] + [ApplicableToUnityVersionsSince("5.3.5p3")] + [ApplicableToUnityVersionsSince("5.4.0b22")] public unsafe class NativeMethodInfoStructHandler_21_0 : INativeMethodInfoStructHandler { - public int Size() => sizeof(Il2CppMethodInfo_21_0); + private NativeMethodInfoStructHandler_21_0() + { + } public INativeMethodInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppMethodInfo_21_0* _ = (Il2CppMethodInfo_21_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeMethodInfoStruct CreateNewStruct() public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeMethodInfoStructHandler_21_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_21_0); internal unsafe struct Il2CppMethodInfo_21_0 { public void* methodPointer; @@ -42,22 +47,20 @@ internal enum Bitfield0 : byte BIT_is_inflated = 1, is_inflated = (1 << BIT_is_inflated), } - } - internal class NativeStructWrapper : INativeMethodInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_21_0._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppMethodInfo_21_0* _ => (Il2CppMethodInfo_21_0*)Pointer; public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref ushort Slot => ref _->slot; - public ref IntPtr MethodPointer => ref *(IntPtr*)&_->methodPointer; - public ref IntPtr VirtualMethodPointer => ref *(IntPtr*)&_->methodPointer; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->methodPointer; public ref Il2CppClass* Class => ref _->declaring_type; - public ref IntPtr InvokerMethod => ref *(IntPtr*)&_->invoker_method; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; public ref Il2CppTypeStruct* ReturnType => ref _->return_type; public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; public ref byte ParametersCount => ref _->parameters_count; @@ -79,7 +82,5 @@ public bool IsMarshalledFromNative set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_0.cs similarity index 73% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_0.cs index 28828708..6f4c3021 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_0.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo { - [ApplicableToUnityVersionsSince("2018.1.0")] + [ApplicableToUnityVersionsSince("2018.1.0b5")] public unsafe class NativeMethodInfoStructHandler_24_0 : INativeMethodInfoStructHandler { - public int Size() => sizeof(Il2CppMethodInfo_24_0); + private NativeMethodInfoStructHandler_24_0() + { + } public INativeMethodInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppMethodInfo_24_0* _ = (Il2CppMethodInfo_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeMethodInfoStruct CreateNewStruct() public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeMethodInfoStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_24_0); internal unsafe struct Il2CppMethodInfo_24_0 { public void* methodPointer; @@ -43,25 +47,21 @@ internal enum Bitfield0 : byte is_inflated = (1 << BIT_is_inflated), BIT_wrapper_type = 2, wrapper_type = (1 << BIT_wrapper_type), - BIT_is_marshaled_from_native = 3, - is_marshaled_from_native = (1 << BIT_is_marshaled_from_native), } - } - internal class NativeStructWrapper : INativeMethodInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_24_0._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppMethodInfo_24_0* _ => (Il2CppMethodInfo_24_0*)Pointer; public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref ushort Slot => ref _->slot; - public ref IntPtr MethodPointer => ref *(IntPtr*)&_->methodPointer; - public ref IntPtr VirtualMethodPointer => ref *(IntPtr*)&_->methodPointer; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->methodPointer; public ref Il2CppClass* Class => ref _->klass; - public ref IntPtr InvokerMethod => ref *(IntPtr*)&_->invoker_method; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; public ref Il2CppTypeStruct* ReturnType => ref _->return_type; public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; public ref byte ParametersCount => ref _->parameters_count; @@ -79,11 +79,9 @@ public bool IsInflated } public bool IsMarshalledFromNative { - get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_24_0.Bitfield0.BIT_is_marshaled_from_native); - set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_24_0.Bitfield0.BIT_is_marshaled_from_native, value); + get => false; + set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_24_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_1.cs similarity index 78% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_24_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_1.cs index 26d69c24..43dfca87 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_24_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_1.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo { - [ApplicableToUnityVersionsSince("2018.3.0")] + [ApplicableToUnityVersionsSince("2018.1.0f1")] public unsafe class NativeMethodInfoStructHandler_24_1 : INativeMethodInfoStructHandler { - public int Size() => sizeof(Il2CppMethodInfo_24_1); + private NativeMethodInfoStructHandler_24_1() + { + } public INativeMethodInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppMethodInfo_24_1* _ = (Il2CppMethodInfo_24_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeMethodInfoStruct CreateNewStruct() public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeMethodInfoStructHandler_24_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_24_1); internal unsafe struct Il2CppMethodInfo_24_1 { public void* methodPointer; @@ -28,6 +32,7 @@ internal unsafe struct Il2CppMethodInfo_24_1 public Il2CppParameterInfo* parameters; public void* runtime_data; public void* generic_data; + public int customAttributeIndex; public uint token; public ushort flags; public ushort iflags; @@ -45,22 +50,20 @@ internal enum Bitfield0 : byte BIT_is_marshaled_from_native = 3, is_marshaled_from_native = (1 << BIT_is_marshaled_from_native), } - } - internal class NativeStructWrapper : INativeMethodInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_24_1._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppMethodInfo_24_1* _ => (Il2CppMethodInfo_24_1*)Pointer; public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref ushort Slot => ref _->slot; - public ref IntPtr MethodPointer => ref *(IntPtr*)&_->methodPointer; - public ref IntPtr VirtualMethodPointer => ref *(IntPtr*)&_->methodPointer; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->methodPointer; public ref Il2CppClass* Class => ref _->klass; - public ref IntPtr InvokerMethod => ref *(IntPtr*)&_->invoker_method; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; public ref Il2CppTypeStruct* ReturnType => ref _->return_type; public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; public ref byte ParametersCount => ref _->parameters_count; @@ -82,7 +85,5 @@ public bool IsMarshalledFromNative set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_24_1.Bitfield0.BIT_is_marshaled_from_native, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_2.cs new file mode 100644 index 00000000..aa568f13 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_24_2.cs @@ -0,0 +1,88 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo +{ + [ApplicableToUnityVersionsSince("2018.3.0")] + public unsafe class NativeMethodInfoStructHandler_24_2 : INativeMethodInfoStructHandler + { + private NativeMethodInfoStructHandler_24_2() + { + } + public INativeMethodInfoStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppMethodInfo_24_2* _ = (Il2CppMethodInfo_24_2*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeMethodInfoStructHandler_24_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_24_2); + internal unsafe struct Il2CppMethodInfo_24_2 + { + public void* methodPointer; + public void* invoker_method; + public byte* name; + public Il2CppClass* klass; + public Il2CppTypeStruct* return_type; + public Il2CppParameterInfo* parameters; + public void* runtime_data; + public void* generic_data; + public uint token; + public ushort flags; + public ushort iflags; + public ushort slot; + public byte parameters_count; + public Bitfield0 _bitfield0; + internal enum Bitfield0 : byte + { + BIT_is_generic = 0, + is_generic = (1 << BIT_is_generic), + BIT_is_inflated = 1, + is_inflated = (1 << BIT_is_inflated), + BIT_wrapper_type = 2, + wrapper_type = (1 << BIT_wrapper_type), + BIT_is_marshaled_from_native = 3, + is_marshaled_from_native = (1 << BIT_is_marshaled_from_native), + } + } + internal class NativeStructWrapper : INativeMethodInfoStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_24_2._bitfield0)).ToInt32(); + public nint Pointer { get; } + private Il2CppMethodInfo_24_2* _ => (Il2CppMethodInfo_24_2*)Pointer; + public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; + public ref nint Name => ref *(nint*)&_->name; + public ref ushort Slot => ref _->slot; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->methodPointer; + public ref Il2CppClass* Class => ref _->klass; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; + public ref Il2CppTypeStruct* ReturnType => ref _->return_type; + public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; + public ref byte ParametersCount => ref _->parameters_count; + public ref Il2CppParameterInfo* Parameters => ref _->parameters; + public ref uint Token => ref _->token; + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_24_2.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_24_2.Bitfield0.BIT_is_generic, value); + } + public bool IsInflated + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_24_2.Bitfield0.BIT_is_inflated); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_24_2.Bitfield0.BIT_is_inflated, value); + } + public bool IsMarshalledFromNative + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_24_2.Bitfield0.BIT_is_marshaled_from_native); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_24_2.Bitfield0.BIT_is_marshaled_from_native, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_27_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_27_0.cs new file mode 100644 index 00000000..8be8f2c2 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_27_0.cs @@ -0,0 +1,89 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo +{ + [ApplicableToUnityVersionsSince("2021.2.0")] + public unsafe class NativeMethodInfoStructHandler_27_0 : INativeMethodInfoStructHandler + { + private NativeMethodInfoStructHandler_27_0() + { + } + public INativeMethodInfoStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppMethodInfo_27_0* _ = (Il2CppMethodInfo_27_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeMethodInfoStructHandler_27_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_27_0); + internal unsafe struct Il2CppMethodInfo_27_0 + { + public void* methodPointer; + public void* virtualMethodPointer; + public void* invoker_method; + public byte* name; + public Il2CppClass* klass; + public Il2CppTypeStruct* return_type; + public Il2CppParameterInfo* parameters; + public void* runtime_data; + public void* generic_data; + public uint token; + public ushort flags; + public ushort iflags; + public ushort slot; + public byte parameters_count; + public Bitfield0 _bitfield0; + internal enum Bitfield0 : byte + { + BIT_is_generic = 0, + is_generic = (1 << BIT_is_generic), + BIT_is_inflated = 1, + is_inflated = (1 << BIT_is_inflated), + BIT_wrapper_type = 2, + wrapper_type = (1 << BIT_wrapper_type), + BIT_is_marshaled_from_native = 3, + is_marshaled_from_native = (1 << BIT_is_marshaled_from_native), + } + } + internal class NativeStructWrapper : INativeMethodInfoStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_27_0._bitfield0)).ToInt32(); + public nint Pointer { get; } + private Il2CppMethodInfo_27_0* _ => (Il2CppMethodInfo_27_0*)Pointer; + public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; + public ref nint Name => ref *(nint*)&_->name; + public ref ushort Slot => ref _->slot; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->virtualMethodPointer; + public ref Il2CppClass* Class => ref _->klass; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; + public ref Il2CppTypeStruct* ReturnType => ref _->return_type; + public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; + public ref byte ParametersCount => ref _->parameters_count; + public ref Il2CppParameterInfo* Parameters => ref _->parameters; + public ref uint Token => ref _->token; + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_27_0.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_27_0.Bitfield0.BIT_is_generic, value); + } + public bool IsInflated + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_27_0.Bitfield0.BIT_is_inflated); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_27_0.Bitfield0.BIT_is_inflated, value); + } + public bool IsMarshalledFromNative + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_27_0.Bitfield0.BIT_is_marshaled_from_native); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_27_0.Bitfield0.BIT_is_marshaled_from_native, value); + } + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_28_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_28_0.cs new file mode 100644 index 00000000..c223e495 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_28_0.cs @@ -0,0 +1,87 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo +{ + [ApplicableToUnityVersionsSince("2021.2.0b7")] + public unsafe class NativeMethodInfoStructHandler_28_0 : INativeMethodInfoStructHandler + { + private NativeMethodInfoStructHandler_28_0() + { + } + public INativeMethodInfoStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppMethodInfo_28_0* _ = (Il2CppMethodInfo_28_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeMethodInfoStructHandler_28_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_28_0); + internal unsafe struct Il2CppMethodInfo_28_0 + { + public void* methodPointer; + public void* virtualMethodPointer; + public void* invoker_method; + public byte* name; + public Il2CppClass* klass; + public Il2CppTypeStruct* return_type; + public Il2CppParameterInfo* parameters; + public void* runtime_data; + public void* generic_data; + public uint token; + public ushort flags; + public ushort iflags; + public ushort slot; + public byte parameters_count; + public Bitfield0 _bitfield0; + internal enum Bitfield0 : byte + { + BIT_is_generic = 0, + is_generic = (1 << BIT_is_generic), + BIT_is_inflated = 1, + is_inflated = (1 << BIT_is_inflated), + BIT_wrapper_type = 2, + wrapper_type = (1 << BIT_wrapper_type), + } + } + internal class NativeStructWrapper : INativeMethodInfoStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_28_0._bitfield0)).ToInt32(); + public nint Pointer { get; } + private Il2CppMethodInfo_28_0* _ => (Il2CppMethodInfo_28_0*)Pointer; + public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; + public ref nint Name => ref *(nint*)&_->name; + public ref ushort Slot => ref _->slot; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->virtualMethodPointer; + public ref Il2CppClass* Class => ref _->klass; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; + public ref Il2CppTypeStruct* ReturnType => ref _->return_type; + public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; + public ref byte ParametersCount => ref _->parameters_count; + public ref Il2CppParameterInfo* Parameters => ref _->parameters; + public ref uint Token => ref _->token; + public bool IsGeneric + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_28_0.Bitfield0.BIT_is_generic); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_28_0.Bitfield0.BIT_is_generic, value); + } + public bool IsInflated + { + get => this.CheckBit(_bitfield0offset, (int)Il2CppMethodInfo_28_0.Bitfield0.BIT_is_inflated); + set => this.SetBit(_bitfield0offset, (int)Il2CppMethodInfo_28_0.Bitfield0.BIT_is_inflated, value); + } + public bool IsMarshalledFromNative + { + get => false; + set { } + } + } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_0.cs similarity index 73% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_0.cs index 5c1e50d0..ede252ea 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_0.cs @@ -1,14 +1,18 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo { - [ApplicableToUnityVersionsSince("2021.2.0")] + [ApplicableToUnityVersionsSince("2021.2.0b8")] + [ApplicableToUnityVersionsSince("2022.1.0")] + [ApplicableToUnityVersionsSince("2022.1.0b7")] public unsafe class NativeMethodInfoStructHandler_29_0 : INativeMethodInfoStructHandler { - public int Size() => sizeof(Il2CppMethodInfo_29_0); + private NativeMethodInfoStructHandler_29_0() + { + } public INativeMethodInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppMethodInfo_29_0* _ = (Il2CppMethodInfo_29_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +20,10 @@ public INativeMethodInfoStruct CreateNewStruct() public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeMethodInfoStructHandler_29_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_29_0); internal unsafe struct Il2CppMethodInfo_29_0 { public void* methodPointer; @@ -26,7 +32,7 @@ internal unsafe struct Il2CppMethodInfo_29_0 public byte* name; public Il2CppClass* klass; public Il2CppTypeStruct* return_type; - public Il2CppTypeStruct** parameters; + public Il2CppParameterInfo* parameters; public void* runtime_data; public void* generic_data; public uint token; @@ -45,29 +51,25 @@ internal enum Bitfield0 : byte wrapper_type = (1 << BIT_wrapper_type), BIT_has_full_generic_sharing_signature = 3, has_full_generic_sharing_signature = (1 << BIT_has_full_generic_sharing_signature), - BIT_indirect_call_via_invokers = 4, - indirect_call_via_invokers = (1 << BIT_indirect_call_via_invokers), } - } - internal class NativeStructWrapper : INativeMethodInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_29_0._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppMethodInfo_29_0* _ => (Il2CppMethodInfo_29_0*)Pointer; public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref ushort Slot => ref _->slot; - public ref IntPtr MethodPointer => ref *(IntPtr*)&_->methodPointer; - public ref IntPtr VirtualMethodPointer => ref *(IntPtr*)&_->virtualMethodPointer; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->virtualMethodPointer; public ref Il2CppClass* Class => ref _->klass; - public ref IntPtr InvokerMethod => ref *(IntPtr*)&_->invoker_method; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; public ref Il2CppTypeStruct* ReturnType => ref _->return_type; public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; public ref byte ParametersCount => ref _->parameters_count; - public ref Il2CppParameterInfo* Parameters => ref *(Il2CppParameterInfo**)&_->parameters; + public ref Il2CppParameterInfo* Parameters => ref _->parameters; public ref uint Token => ref _->token; public bool IsGeneric { @@ -85,7 +87,5 @@ public bool IsMarshalledFromNative set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_1.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_1.cs similarity index 72% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_1.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_1.cs index 4ebb747b..24afa685 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_1.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_1.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo { - [ApplicableToUnityVersionsSince("2022.1.0")] + [ApplicableToUnityVersionsSince("2021.2.0b13")] + [ApplicableToUnityVersionsSince("2022.1.0a10")] public unsafe class NativeMethodInfoStructHandler_29_1 : INativeMethodInfoStructHandler { - public int Size() => sizeof(Il2CppMethodInfo_29_1); + private NativeMethodInfoStructHandler_29_1() + { + } public INativeMethodInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppMethodInfo_29_1* _ = (Il2CppMethodInfo_29_1*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativeMethodInfoStruct CreateNewStruct() public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeMethodInfoStructHandler_29_1 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_29_1); internal unsafe struct Il2CppMethodInfo_29_1 { public void* methodPointer; @@ -26,7 +31,7 @@ internal unsafe struct Il2CppMethodInfo_29_1 public byte* name; public Il2CppClass* klass; public Il2CppTypeStruct* return_type; - public Il2CppTypeStruct** parameters; + public Il2CppParameterInfo* parameters; public void* runtime_data; public void* generic_data; public uint token; @@ -45,27 +50,27 @@ internal enum Bitfield0 : byte wrapper_type = (1 << BIT_wrapper_type), BIT_has_full_generic_sharing_signature = 3, has_full_generic_sharing_signature = (1 << BIT_has_full_generic_sharing_signature), + BIT_indirect_call_via_invokers = 4, + indirect_call_via_invokers = (1 << BIT_indirect_call_via_invokers), } - } - internal class NativeStructWrapper : INativeMethodInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_29_1._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppMethodInfo_29_1* _ => (Il2CppMethodInfo_29_1*)Pointer; public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref ushort Slot => ref _->slot; - public ref IntPtr MethodPointer => ref *(IntPtr*)&_->methodPointer; - public ref IntPtr VirtualMethodPointer => ref *(IntPtr*)&_->virtualMethodPointer; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->virtualMethodPointer; public ref Il2CppClass* Class => ref _->klass; - public ref IntPtr InvokerMethod => ref *(IntPtr*)&_->invoker_method; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; public ref Il2CppTypeStruct* ReturnType => ref _->return_type; public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; public ref byte ParametersCount => ref _->parameters_count; - public ref Il2CppParameterInfo* Parameters => ref *(Il2CppParameterInfo**)&_->parameters; + public ref Il2CppParameterInfo* Parameters => ref _->parameters; public ref uint Token => ref _->token; public bool IsGeneric { @@ -83,7 +88,5 @@ public bool IsMarshalledFromNative set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_2.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_2.cs similarity index 76% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_2.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_2.cs index 0cd0a703..ba16e672 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/MethodInfo/MethodInfo_29_2.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/MethodInfo_29_2.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo { - [ApplicableToUnityVersionsSince("2023.2.0")] + [ApplicableToUnityVersionsSince("2023.2.0a22")] public unsafe class NativeMethodInfoStructHandler_29_2 : INativeMethodInfoStructHandler { - public int Size() => sizeof(Il2CppMethodInfo_29_2); + private NativeMethodInfoStructHandler_29_2() + { + } public INativeMethodInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppMethodInfo_29_2* _ = (Il2CppMethodInfo_29_2*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeMethodInfoStruct CreateNewStruct() public INativeMethodInfoStruct Wrap(Il2CppMethodInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeMethodInfoStructHandler_29_2 Instance { get; } = new(); + public int Size => sizeof(Il2CppMethodInfo_29_2); internal unsafe struct Il2CppMethodInfo_29_2 { public void* methodPointer; @@ -26,7 +30,7 @@ internal unsafe struct Il2CppMethodInfo_29_2 public byte* name; public Il2CppClass* klass; public Il2CppTypeStruct* return_type; - public Il2CppTypeStruct** parameters; + public Il2CppParameterInfo* parameters; public void* runtime_data; public void* generic_data; public uint token; @@ -48,26 +52,24 @@ internal enum Bitfield0 : byte BIT_is_unmanaged_callers_only = 4, is_unmanaged_callers_only = (1 << BIT_is_unmanaged_callers_only), } - } - internal class NativeStructWrapper : INativeMethodInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppMethodInfo_29_2._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppMethodInfo_29_2* _ => (Il2CppMethodInfo_29_2*)Pointer; public Il2CppMethodInfo* MethodInfoPointer => (Il2CppMethodInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref ushort Slot => ref _->slot; - public ref IntPtr MethodPointer => ref *(IntPtr*)&_->methodPointer; - public ref IntPtr VirtualMethodPointer => ref *(IntPtr*)&_->virtualMethodPointer; + public ref nint MethodPointer => ref *(nint*)&_->methodPointer; + public ref nint VirtualMethodPointer => ref *(nint*)&_->virtualMethodPointer; public ref Il2CppClass* Class => ref _->klass; - public ref IntPtr InvokerMethod => ref *(IntPtr*)&_->invoker_method; + public ref nint InvokerMethod => ref *(nint*)&_->invoker_method; public ref Il2CppTypeStruct* ReturnType => ref _->return_type; public ref Il2CppMethodFlags Flags => ref *(Il2CppMethodFlags*)&_->flags; public ref byte ParametersCount => ref _->parameters_count; - public ref Il2CppParameterInfo* Parameters => ref *(Il2CppParameterInfo**)&_->parameters; + public ref Il2CppParameterInfo* Parameters => ref _->parameters; public ref uint Token => ref _->token; public bool IsGeneric { @@ -85,7 +87,5 @@ public bool IsMarshalledFromNative set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/UnityVersionHandler.cs new file mode 100644 index 00000000..9926a1bc --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/MethodInfo/UnityVersionHandler.cs @@ -0,0 +1,73 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.MethodInfo; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetMethodInfoStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2023, 2, 0, UnityVersionType.Alpha, 22)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_29_2.Instance; + } + else if (version >= new UnityVersion(2022, 1, 0, UnityVersionType.Beta, 7)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_29_0.Instance; + } + else if (version >= new UnityVersion(2022, 1, 0, UnityVersionType.Alpha, 10)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_29_1.Instance; + } + else if (version >= new UnityVersion(2022, 1, 0, UnityVersionType.Alpha, 0)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_29_0.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Beta, 13)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_29_1.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Beta, 8)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_29_0.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Beta, 7)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_28_0.Instance; + } + else if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Alpha, 0)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_27_0.Instance; + } + else if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_24_2.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Final, 1)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_24_1.Instance; + } + else if (version >= new UnityVersion(2018, 1, 0, UnityVersionType.Beta, 5)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 22)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_21_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 5, UnityVersionType.Patch, 3)) + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_21_0.Instance; + } + else + { + MethodInfoStructHandler = NativeMethodInfoStructHandler_16_0.Instance; + } + } + private static INativeMethodInfoStructHandler MethodInfoStructHandler { get; set; } = NativeMethodInfoStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/Interfaces.cs new file mode 100644 index 00000000..143a4f0a --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/Interfaces.cs @@ -0,0 +1,18 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.ParameterInfo +{ + public interface INativeParameterInfoStructHandler : INativeStructHandler + { + INativeParameterInfoStruct CreateNewStruct(); + unsafe INativeParameterInfoStruct Wrap(Il2CppParameterInfo* pointer); + } + public interface INativeParameterInfoStruct : INativeStruct + { + unsafe Il2CppParameterInfo* ParameterInfoPointer { get; } + bool HasNamePosToken { get; } + ref nint Name { get; } + ref int Position { get; } + ref uint Token { get; } + unsafe ref Il2CppTypeStruct* ParameterType { get; } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_16_0.cs new file mode 100644 index 00000000..6bb9e29e --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_16_0.cs @@ -0,0 +1,46 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.ParameterInfo +{ + [ApplicableToUnityVersionsSince("5.2.2")] + public unsafe class NativeParameterInfoStructHandler_16_0 : INativeParameterInfoStructHandler + { + private NativeParameterInfoStructHandler_16_0() + { + } + public INativeParameterInfoStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppParameterInfo_16_0* _ = (Il2CppParameterInfo_16_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeParameterInfoStruct Wrap(Il2CppParameterInfo* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeParameterInfoStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppParameterInfo_16_0); + internal unsafe struct Il2CppParameterInfo_16_0 + { + public byte* name; + public int position; + public uint token; + public int customAttributeIndex; + public Il2CppTypeStruct* parameter_type; + } + internal class NativeStructWrapper : INativeParameterInfoStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppParameterInfo_16_0* _ => (Il2CppParameterInfo_16_0*)Pointer; + public Il2CppParameterInfo* ParameterInfoPointer => (Il2CppParameterInfo*)Pointer; + public bool HasNamePosToken => true; + public ref nint Name => ref *(nint*)&_->name; + public ref int Position => ref _->position; + public ref uint Token => ref _->token; + public ref Il2CppTypeStruct* ParameterType => ref _->parameter_type; + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_24_0.cs new file mode 100644 index 00000000..156a1ab3 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_24_0.cs @@ -0,0 +1,45 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.ParameterInfo +{ + [ApplicableToUnityVersionsSince("2018.3.0")] + public unsafe class NativeParameterInfoStructHandler_24_0 : INativeParameterInfoStructHandler + { + private NativeParameterInfoStructHandler_24_0() + { + } + public INativeParameterInfoStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppParameterInfo_24_0* _ = (Il2CppParameterInfo_24_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeParameterInfoStruct Wrap(Il2CppParameterInfo* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeParameterInfoStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppParameterInfo_24_0); + internal unsafe struct Il2CppParameterInfo_24_0 + { + public byte* name; + public int position; + public uint token; + public Il2CppTypeStruct* parameter_type; + } + internal class NativeStructWrapper : INativeParameterInfoStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppParameterInfo_24_0* _ => (Il2CppParameterInfo_24_0*)Pointer; + public Il2CppParameterInfo* ParameterInfoPointer => (Il2CppParameterInfo*)Pointer; + public bool HasNamePosToken => true; + public ref nint Name => ref *(nint*)&_->name; + public ref int Position => ref _->position; + public ref uint Token => ref _->token; + public ref Il2CppTypeStruct* ParameterType => ref _->parameter_type; + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_27_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_27_0.cs new file mode 100644 index 00000000..05b339e9 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/ParameterInfo_27_0.cs @@ -0,0 +1,42 @@ +// +using System.Runtime.InteropServices; +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.ParameterInfo +{ + [ApplicableToUnityVersionsSince("2021.2.0")] + public unsafe class NativeParameterInfoStructHandler_27_0 : INativeParameterInfoStructHandler + { + private NativeParameterInfoStructHandler_27_0() + { + } + public INativeParameterInfoStruct CreateNewStruct() + { + nint ptr = Marshal.AllocHGlobal(Size); + Il2CppParameterInfo_27_0* _ = (Il2CppParameterInfo_27_0*)ptr; + *_ = default; + return new NativeStructWrapper(ptr); + } + public INativeParameterInfoStruct Wrap(Il2CppParameterInfo* ptr) + { + if (ptr == null) return null; + return new NativeStructWrapper((nint)ptr); + } + public static NativeParameterInfoStructHandler_27_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppParameterInfo_27_0); + internal unsafe struct Il2CppParameterInfo_27_0 + { + public Il2CppTypeStruct* parameter_type; + } + internal class NativeStructWrapper : INativeParameterInfoStruct + { + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } + private Il2CppParameterInfo_27_0* _ => (Il2CppParameterInfo_27_0*)Pointer; + public Il2CppParameterInfo* ParameterInfoPointer => (Il2CppParameterInfo*)Pointer; + public bool HasNamePosToken => false; + public ref nint Name => throw new System.NotSupportedException(); + public ref int Position => throw new System.NotSupportedException(); + public ref uint Token => throw new System.NotSupportedException(); + public ref Il2CppTypeStruct* ParameterType => ref _->parameter_type; + } + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/UnityVersionHandler.cs new file mode 100644 index 00000000..116bdf5c --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/ParameterInfo/UnityVersionHandler.cs @@ -0,0 +1,25 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.ParameterInfo; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetParameterInfoStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2021, 2, 0, UnityVersionType.Alpha, 0)) + { + ParameterInfoStructHandler = NativeParameterInfoStructHandler_27_0.Instance; + } + else if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + ParameterInfoStructHandler = NativeParameterInfoStructHandler_24_0.Instance; + } + else + { + ParameterInfoStructHandler = NativeParameterInfoStructHandler_16_0.Instance; + } + } + private static INativeParameterInfoStructHandler ParameterInfoStructHandler { get; set; } = NativeParameterInfoStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/Interfaces.cs new file mode 100644 index 00000000..bbfd2108 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/Interfaces.cs @@ -0,0 +1,18 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.PropertyInfo +{ + public interface INativePropertyInfoStructHandler : INativeStructHandler + { + INativePropertyInfoStruct CreateNewStruct(); + unsafe INativePropertyInfoStruct Wrap(Il2CppPropertyInfo* pointer); + } + public interface INativePropertyInfoStruct : INativeStruct + { + unsafe Il2CppPropertyInfo* PropertyInfoPointer { get; } + ref nint Name { get; } + unsafe ref Il2CppClass* Parent { get; } + unsafe ref Il2CppMethodInfo* Get { get; } + unsafe ref Il2CppMethodInfo* Set { get; } + ref uint Attrs { get; } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_16_0.cs similarity index 68% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_16_0.cs index d8a147e1..b759f1b6 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_16_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.PropertyInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.PropertyInfo { [ApplicableToUnityVersionsSince("5.2.2")] + [ApplicableToUnityVersionsSince("5.4.0")] public unsafe class NativePropertyInfoStructHandler_16_0 : INativePropertyInfoStructHandler { - public int Size() => sizeof(Il2CppPropertyInfo_16_0); + private NativePropertyInfoStructHandler_16_0() + { + } public INativePropertyInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppPropertyInfo_16_0* _ = (Il2CppPropertyInfo_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativePropertyInfoStruct CreateNewStruct() public INativePropertyInfoStruct Wrap(Il2CppPropertyInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativePropertyInfoStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppPropertyInfo_16_0); internal unsafe struct Il2CppPropertyInfo_16_0 { public Il2CppClass* parent; @@ -27,20 +32,17 @@ internal unsafe struct Il2CppPropertyInfo_16_0 public uint attrs; public int customAttributeIndex; } - internal class NativeStructWrapper : INativePropertyInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppPropertyInfo_16_0* _ => (Il2CppPropertyInfo_16_0*)Pointer; public Il2CppPropertyInfo* PropertyInfoPointer => (Il2CppPropertyInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppMethodInfo* Get => ref _->get; public ref Il2CppMethodInfo* Set => ref _->set; public ref uint Attrs => ref _->attrs; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_19_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_19_0.cs similarity index 66% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_19_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_19_0.cs index 9e54d6e1..e909415f 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_19_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_19_0.cs @@ -1,14 +1,17 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.PropertyInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.PropertyInfo { - [ApplicableToUnityVersionsSince("5.3.2")] + [ApplicableToUnityVersionsSince("5.3.1p3")] + [ApplicableToUnityVersionsSince("5.4.0b3")] public unsafe class NativePropertyInfoStructHandler_19_0 : INativePropertyInfoStructHandler { - public int Size() => sizeof(Il2CppPropertyInfo_19_0); + private NativePropertyInfoStructHandler_19_0() + { + } public INativePropertyInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppPropertyInfo_19_0* _ = (Il2CppPropertyInfo_19_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +19,10 @@ public INativePropertyInfoStruct CreateNewStruct() public INativePropertyInfoStruct Wrap(Il2CppPropertyInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativePropertyInfoStructHandler_19_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppPropertyInfo_19_0); internal unsafe struct Il2CppPropertyInfo_19_0 { public Il2CppClass* parent; @@ -28,20 +33,17 @@ internal unsafe struct Il2CppPropertyInfo_19_0 public int customAttributeIndex; public uint token; } - internal class NativeStructWrapper : INativePropertyInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppPropertyInfo_19_0* _ => (Il2CppPropertyInfo_19_0*)Pointer; public Il2CppPropertyInfo* PropertyInfoPointer => (Il2CppPropertyInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppMethodInfo* Get => ref _->get; public ref Il2CppMethodInfo* Set => ref _->set; public ref uint Attrs => ref _->attrs; } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_24_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_24_0.cs similarity index 70% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_24_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_24_0.cs index 87ccb420..bb25d2ea 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/PropertyInfo/PropertyInfo_24_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/PropertyInfo_24_0.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.PropertyInfo +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.PropertyInfo { [ApplicableToUnityVersionsSince("2018.3.0")] public unsafe class NativePropertyInfoStructHandler_24_0 : INativePropertyInfoStructHandler { - public int Size() => sizeof(Il2CppPropertyInfo_24_0); + private NativePropertyInfoStructHandler_24_0() + { + } public INativePropertyInfoStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppPropertyInfo_24_0* _ = (Il2CppPropertyInfo_24_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativePropertyInfoStruct CreateNewStruct() public INativePropertyInfoStruct Wrap(Il2CppPropertyInfo* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativePropertyInfoStructHandler_24_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppPropertyInfo_24_0); internal unsafe struct Il2CppPropertyInfo_24_0 { public Il2CppClass* parent; @@ -27,20 +31,17 @@ internal unsafe struct Il2CppPropertyInfo_24_0 public uint attrs; public uint token; } - internal class NativeStructWrapper : INativePropertyInfoStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; - public IntPtr Pointer { get; } + public NativeStructWrapper(nint ptr) => Pointer = ptr; + public nint Pointer { get; } private Il2CppPropertyInfo_24_0* _ => (Il2CppPropertyInfo_24_0*)Pointer; public Il2CppPropertyInfo* PropertyInfoPointer => (Il2CppPropertyInfo*)Pointer; - public ref IntPtr Name => ref *(IntPtr*)&_->name; + public ref nint Name => ref *(nint*)&_->name; public ref Il2CppClass* Parent => ref _->parent; public ref Il2CppMethodInfo* Get => ref _->get; public ref Il2CppMethodInfo* Set => ref _->set; public ref uint Attrs => ref _->attrs; } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/UnityVersionHandler.cs new file mode 100644 index 00000000..7a09c668 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/PropertyInfo/UnityVersionHandler.cs @@ -0,0 +1,33 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.PropertyInfo; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetPropertyInfoStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2018, 3, 0, UnityVersionType.Alpha, 0)) + { + PropertyInfoStructHandler = NativePropertyInfoStructHandler_24_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Beta, 3)) + { + PropertyInfoStructHandler = NativePropertyInfoStructHandler_19_0.Instance; + } + else if (version >= new UnityVersion(5, 4, 0, UnityVersionType.Alpha, 0)) + { + PropertyInfoStructHandler = NativePropertyInfoStructHandler_16_0.Instance; + } + else if (version >= new UnityVersion(5, 3, 1, UnityVersionType.Patch, 3)) + { + PropertyInfoStructHandler = NativePropertyInfoStructHandler_19_0.Instance; + } + else + { + PropertyInfoStructHandler = NativePropertyInfoStructHandler_16_0.Instance; + } + } + private static INativePropertyInfoStructHandler PropertyInfoStructHandler { get; set; } = NativePropertyInfoStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Interfaces.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Interfaces.cs new file mode 100644 index 00000000..0f398b1f --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Interfaces.cs @@ -0,0 +1,19 @@ +// +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Type +{ + public interface INativeTypeStructHandler : INativeStructHandler + { + INativeTypeStruct CreateNewStruct(); + unsafe INativeTypeStruct Wrap(Il2CppTypeStruct* pointer); + } + public interface INativeTypeStruct : INativeStruct + { + unsafe Il2CppTypeStruct* TypePointer { get; } + ref nint Data { get; } + ref ushort Attrs { get; } + ref Il2CppTypeEnum Type { get; } + bool ByRef { get; set; } + bool Pinned { get; set; } + bool ValueType { get; set; } + } +} diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Type_16_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Type_16_0.cs similarity index 78% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Type_16_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Type_16_0.cs index 434adf12..912e72df 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Type_16_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Type_16_0.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Type +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Type { [ApplicableToUnityVersionsSince("5.2.2")] public unsafe class NativeTypeStructHandler_16_0 : INativeTypeStructHandler { - public int Size() => sizeof(Il2CppType_16_0); + private NativeTypeStructHandler_16_0() + { + } public INativeTypeStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppType_16_0* _ = (Il2CppType_16_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeTypeStruct CreateNewStruct() public INativeTypeStruct Wrap(Il2CppTypeStruct* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeTypeStructHandler_16_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppType_16_0); internal unsafe struct Il2CppType_16_0 { public void* data; @@ -31,17 +35,15 @@ internal enum Bitfield0 : byte BIT_pinned = 7, pinned = (1 << BIT_pinned), } - } - internal class NativeStructWrapper : INativeTypeStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppType_16_0._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppType_16_0* _ => (Il2CppType_16_0*)Pointer; public Il2CppTypeStruct* TypePointer => (Il2CppTypeStruct*)Pointer; - public ref IntPtr Data => ref *(IntPtr*)&_->data; + public ref nint Data => ref *(nint*)&_->data; public ref ushort Attrs => ref _->attrs; public ref Il2CppTypeEnum Type => ref *(Il2CppTypeEnum*)&_->type; public bool ByRef @@ -60,7 +62,5 @@ public bool ValueType set { } } } - } - } diff --git a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Type_27_0.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Type_27_0.cs similarity index 80% rename from Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Type_27_0.cs rename to Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Type_27_0.cs index 7645de19..3dd58093 100644 --- a/Il2CppInterop.Runtime/Runtime/VersionSpecific/Type/Type_27_0.cs +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/Type_27_0.cs @@ -1,14 +1,16 @@ -using System; +// using System.Runtime.InteropServices; -namespace Il2CppInterop.Runtime.Runtime.VersionSpecific.Type +namespace Il2CppInterop.Runtime.Structs.VersionSpecific.Type { [ApplicableToUnityVersionsSince("2021.1.0")] public unsafe class NativeTypeStructHandler_27_0 : INativeTypeStructHandler { - public int Size() => sizeof(Il2CppType_27_0); + private NativeTypeStructHandler_27_0() + { + } public INativeTypeStruct CreateNewStruct() { - IntPtr ptr = Marshal.AllocHGlobal(Size()); + nint ptr = Marshal.AllocHGlobal(Size); Il2CppType_27_0* _ = (Il2CppType_27_0*)ptr; *_ = default; return new NativeStructWrapper(ptr); @@ -16,8 +18,10 @@ public INativeTypeStruct CreateNewStruct() public INativeTypeStruct Wrap(Il2CppTypeStruct* ptr) { if (ptr == null) return null; - return new NativeStructWrapper((IntPtr)ptr); + return new NativeStructWrapper((nint)ptr); } + public static NativeTypeStructHandler_27_0 Instance { get; } = new(); + public int Size => sizeof(Il2CppType_27_0); internal unsafe struct Il2CppType_27_0 { public void* data; @@ -33,17 +37,15 @@ internal enum Bitfield0 : byte BIT_valuetype = 7, valuetype = (1 << BIT_valuetype), } - } - internal class NativeStructWrapper : INativeTypeStruct { - public NativeStructWrapper(IntPtr ptr) => Pointer = ptr; + public NativeStructWrapper(nint ptr) => Pointer = ptr; private static int _bitfield0offset = Marshal.OffsetOf(nameof(Il2CppType_27_0._bitfield0)).ToInt32(); - public IntPtr Pointer { get; } + public nint Pointer { get; } private Il2CppType_27_0* _ => (Il2CppType_27_0*)Pointer; public Il2CppTypeStruct* TypePointer => (Il2CppTypeStruct*)Pointer; - public ref IntPtr Data => ref *(IntPtr*)&_->data; + public ref nint Data => ref *(nint*)&_->data; public ref ushort Attrs => ref _->attrs; public ref Il2CppTypeEnum Type => ref *(Il2CppTypeEnum*)&_->type; public bool ByRef @@ -62,7 +64,5 @@ public bool ValueType set => this.SetBit(_bitfield0offset, (int)Il2CppType_27_0.Bitfield0.BIT_valuetype, value); } } - } - } diff --git a/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/UnityVersionHandler.cs new file mode 100644 index 00000000..546eda11 --- /dev/null +++ b/Il2CppInterop.Runtime/Structs/VersionSpecific/Type/UnityVersionHandler.cs @@ -0,0 +1,21 @@ +// +using AssetRipper.Primitives; +using Il2CppInterop.Runtime.Structs.VersionSpecific.Type; +namespace Il2CppInterop.Runtime.Structs +{ + public static partial class UnityVersionHandler + { + private static void SetTypeStructHandler(UnityVersion version) + { + if (version >= new UnityVersion(2021, 1, 0, UnityVersionType.Alpha, 0)) + { + TypeStructHandler = NativeTypeStructHandler_27_0.Instance; + } + else + { + TypeStructHandler = NativeTypeStructHandler_16_0.Instance; + } + } + private static INativeTypeStructHandler TypeStructHandler { get; set; } = NativeTypeStructHandler_16_0.Instance; + } +} diff --git a/Il2CppInterop.Runtime/Usings.cs b/Il2CppInterop.Runtime/Usings.cs new file mode 100644 index 00000000..3a84ddb6 --- /dev/null +++ b/Il2CppInterop.Runtime/Usings.cs @@ -0,0 +1 @@ +global using Object = Il2CppSystem.Object; diff --git a/Il2CppInterop.Runtime/XrefScans/XrefInstanceExtensions.cs b/Il2CppInterop.Runtime/XrefScans/XrefInstanceExtensions.cs deleted file mode 100644 index d77b83cf..00000000 --- a/Il2CppInterop.Runtime/XrefScans/XrefInstanceExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using Il2CppInterop.Common.XrefScans; -using Il2CppSystem; -using IntPtr = System.IntPtr; -using InvalidOperationException = System.InvalidOperationException; - -namespace Il2CppInterop.Runtime.XrefScans; - -public static class XrefInstanceExtensions -{ - public static Object? ReadAsObject(this XrefInstance self) - { - if (self.Type != XrefType.Global) throw new InvalidOperationException("Can't read non-global xref as object"); - - var valueAtPointer = Marshal.ReadIntPtr(self.Pointer); - if (valueAtPointer == IntPtr.Zero) - return null; - - return new Object(valueAtPointer); - } - - public static MethodBase TryResolve(this XrefInstance self) - { - if (self.Type != XrefType.Method) throw new InvalidOperationException("Can't resolve non-method xrefs"); - - return XrefScanMethodDb.TryResolvePointer(self.Pointer); - } -} diff --git a/Il2CppInterop.Runtime/XrefScans/XrefScanImpl.cs b/Il2CppInterop.Runtime/XrefScans/XrefScanImpl.cs deleted file mode 100644 index 50157020..00000000 --- a/Il2CppInterop.Runtime/XrefScans/XrefScanImpl.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.InteropServices; -using Il2CppInterop.Common.XrefScans; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using Il2CppSystem.Reflection; -using BindingFlags = System.Reflection.BindingFlags; - -namespace Il2CppInterop.Runtime.XrefScans; - -internal class XrefScanImpl : IXrefScannerImpl -{ - private static Func>? getAssemblies; - - public unsafe (XrefScanUtil.InitMetadataForMethod, IntPtr)? GetMetadataResolver() - { - getAssemblies ??= - typeof(AppDomain).GetMethod("GetAssemblies", BindingFlags.Public | BindingFlags.Static) - ?.CreateDelegate(typeof(Func>)) as - Func>; - - var unityObjectCctor = getAssemblies(AppDomain.CurrentDomain) - .Single(it => it.GetName().Name == "UnityEngine.CoreModule").GetType("UnityEngine.Object") - .GetConstructors(Il2CppSystem.Reflection.BindingFlags.Static | - Il2CppSystem.Reflection.BindingFlags.NonPublic).Single(); - var nativeMethodInfo = IL2CPP.il2cpp_method_get_from_reflection(unityObjectCctor.Pointer); - var ourMetadataInitForMethodPointer = XrefScannerLowLevel.JumpTargets(*(IntPtr*)nativeMethodInfo).First(); - var ourMetadataInitForMethodDelegate = - Marshal.GetDelegateForFunctionPointer(ourMetadataInitForMethodPointer); - return (ourMetadataInitForMethodDelegate, ourMetadataInitForMethodPointer); - } - - public bool XrefGlobalClassFilter(IntPtr movTarget) - { - var valueAtMov = (IntPtr)Marshal.ReadInt64(movTarget); - if (valueAtMov != IntPtr.Zero) - { - var targetClass = (IntPtr)Marshal.ReadInt64(valueAtMov); - return targetClass == Il2CppClassPointerStore.NativeClassPtr || - targetClass == Il2CppClassPointerStore.NativeClassPtr; - } - - return false; - } -} diff --git a/Il2CppInterop.SourceGenerator.Tests/AnalyzerTests.cs b/Il2CppInterop.SourceGenerator.Tests/AnalyzerTests.cs new file mode 100644 index 00000000..a349145e --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/AnalyzerTests.cs @@ -0,0 +1,43 @@ +using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Runtime; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; + +namespace Il2CppInterop.SourceGenerator.Tests; + +public abstract class AnalyzerTests +{ + protected CSharpAnalyzerTest Context { get; private set; } + + [SetUp] + public void Setup() + { + Context = new() + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net100 + }; + Context.TestState.AdditionalReferences.Add(typeof(InjectedTypeAttribute).Assembly); + Context.TestState.AdditionalReferences.Add(typeof(Il2CppSystem.Object).Assembly); + Context.TestState.AdditionalReferences.Add(typeof(RuntimeInvoke).Assembly); + } + + protected async Task TestDiagnostic(string testCode, DiagnosticResult expectedDiagnostic) + { + Context.TestCode = testCode; + Context.ExpectedDiagnostics.Add(expectedDiagnostic); + await Context.RunAsync(TestContext.CurrentContext.CancellationToken); + } + + protected async Task TestDiagnostics(string testCode, params IEnumerable expectedDiagnostics) + { + Context.TestCode = testCode; + Context.ExpectedDiagnostics.AddRange(expectedDiagnostics); + await Context.RunAsync(TestContext.CurrentContext.CancellationToken); + } + + protected async Task TestNoDiagnostics(string testCode) + { + Context.TestCode = testCode; + await Context.RunAsync(TestContext.CurrentContext.CancellationToken); + } +} diff --git a/Il2CppInterop.SourceGenerator.Tests/DiagnosticResultExtensions.cs b/Il2CppInterop.SourceGenerator.Tests/DiagnosticResultExtensions.cs new file mode 100644 index 00000000..82077e05 --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/DiagnosticResultExtensions.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; + +namespace Il2CppInterop.SourceGenerator.Tests; + +internal static class DiagnosticResultExtensions +{ + public static DiagnosticResult WithSpan(this DiagnosticResult result, LinePosition start, LinePosition end) + { + return result.WithSpan(start.Line, start.Character, end.Line, end.Character); + } + + public static DiagnosticResult WithSpan(this DiagnosticResult result, LinePositionSpan span) + { + return result.WithSpan(span.Start, span.End); + } +} diff --git a/Il2CppInterop.SourceGenerator.Tests/Diagnostics/PartialTypeAnalyzerTests.cs b/Il2CppInterop.SourceGenerator.Tests/Diagnostics/PartialTypeAnalyzerTests.cs new file mode 100644 index 00000000..66c005fa --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/Diagnostics/PartialTypeAnalyzerTests.cs @@ -0,0 +1,42 @@ +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; + +namespace Il2CppInterop.SourceGenerator.Tests.Diagnostics; + +/// +/// Tests for the diagnostic, which is raised when a type marked with [InjectedType] is not also marked as partial. +/// +public class PartialTypeAnalyzerTests : AnalyzerTests +{ + [Test] + public Task DiagnosticWhenTypeNotPartial() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public class Sample : Il2CppSystem.Object + { + } + """; + + var expectedDiagnostic = new DiagnosticResult(InjectedTypeAnalyzer.MustBePartial) + .WithSpan(LinePositionSpan.FindInSource(testCode, "Sample")) + .WithArguments("Sample"); + + return TestDiagnostic(testCode, expectedDiagnostic); + } + + [Test] + public Task NoDiagnosticsWhenTypeIsCorrectlyMarkedPartial() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public partial class Sample : Il2CppSystem.Object + { + } + """; + + return TestNoDiagnostics(testCode); + } +} diff --git a/Il2CppInterop.SourceGenerator.Tests/Diagnostics/UninjectedInstanceFieldAnalyzerTests.cs b/Il2CppInterop.SourceGenerator.Tests/Diagnostics/UninjectedInstanceFieldAnalyzerTests.cs new file mode 100644 index 00000000..62692ea6 --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/Diagnostics/UninjectedInstanceFieldAnalyzerTests.cs @@ -0,0 +1,106 @@ +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; + +namespace Il2CppInterop.SourceGenerator.Tests.Diagnostics; + +/// +/// Tests for the diagnostic, which is raised when a struct marked with [InjectedType] contains instance fields that are not marked with [Il2CppField]. +/// +public class UninjectedInstanceFieldAnalyzerTests : AnalyzerTests +{ + [Test] + public Task NoDiagnosticsForClassContainingNoInstanceFields() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public partial class Sample : Il2CppSystem.Object + { + } + """; + + return TestNoDiagnostics(testCode); + } + + [Test] + public Task NoDiagnosticsForClassContainingStaticField() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public partial class Sample : Il2CppSystem.Object + { + public static int value; + } + """; + + return TestNoDiagnostics(testCode); + } + + [Test] + public Task DiagnosticForClassContainingUninjectedInstanceField() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public partial class Sample : Il2CppSystem.Object + { + public int value; + } + """; + + var expectedDiagnostic = new DiagnosticResult(InjectedTypeAnalyzer.ShouldNotHaveUninjectedInstanceFields) + .WithSpan(LinePositionSpan.FindInSource(testCode, "value")) + .WithArguments("Sample", "value"); + + return TestDiagnostic(testCode, expectedDiagnostic); + } + + [Test] + public Task NoDiagnosticsForStructContainingNoInstanceFields() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public partial struct Sample + { + } + """; + + return TestNoDiagnostics(testCode); + } + + [Test] + public Task NoDiagnosticsForStructContainingInstanceFieldsWithIl2CppFieldAttributes() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public partial struct Sample + { + [Il2CppField] + public Il2CppSystem.Int32 intValue; + [Il2CppField] + public Il2CppSystem.String stringValue; + } + """; + + return TestNoDiagnostics(testCode); + } + + [Test] + public Task NoDiagnosticsForStructContainingInstanceFieldsWithoutIl2CppFieldAttributes() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + [InjectedType] + public partial struct Sample + { + public Il2CppSystem.Int32 intValue; + public Il2CppSystem.String stringValue; + } + """; + + return TestNoDiagnostics(testCode); + } +} diff --git a/Il2CppInterop.SourceGenerator.Tests/Generation/InjectedClassTests.cs b/Il2CppInterop.SourceGenerator.Tests/Generation/InjectedClassTests.cs new file mode 100644 index 00000000..72788bce --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/Generation/InjectedClassTests.cs @@ -0,0 +1,63 @@ +namespace Il2CppInterop.SourceGenerator.Tests.Generation; + +public class InjectedClassTests : SourceGenerationTests +{ + [Test] + public Task ClassWithNoMembers() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + + [InjectedType] + public partial class Sample : Il2CppSystem.Object + { + } + """; + + var expectedGeneratedCode = """ + // + #nullable enable + + [global::Il2CppInterop.Common.Attributes.Il2CppType(typeof(Il2CppInternals))] + public partial class Sample : global::Il2CppInterop.Common.IIl2CppType + { + public Sample(global::Il2CppInterop.Common.ObjectPointer pointer) : base(pointer) + { + } + + nint global::Il2CppInterop.Common.IIl2CppType.ObjectClass => global::Il2CppInterop.Common.Il2CppType.GetClassPointer(); + + static Sample global::Il2CppInterop.Common.IIl2CppType.UnboxNative(global::Il2CppInterop.Common.ObjectPointer pointer) => new Sample(pointer); + + static int global::Il2CppInterop.Common.IIl2CppType.Size => nint.Size; + + static Sample? global::Il2CppInterop.Common.IIl2CppType.ReadFromSpan(global::System.ReadOnlySpan span) + { + return global::Il2CppInterop.Common.Il2CppType.ReadReference(span); + } + + static void global::Il2CppInterop.Common.IIl2CppType.WriteToSpan(Sample? value, global::System.Span span) + { + global::Il2CppInterop.Common.Il2CppType.WriteReference(value, span); + } + + static Sample() + { + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Il2CppInternals).TypeHandle); + } + } + + file static class Il2CppInternals + { + + static Il2CppInternals() + { + global::Il2CppInterop.Runtime.Injection.TypeInjector.RegisterTypeInIl2Cpp(); + } + } + + """; + + return TestGeneration(testCode, "Sample.g.cs", expectedGeneratedCode); + } +} diff --git a/Il2CppInterop.SourceGenerator.Tests/Generation/InjectedStructTests.cs b/Il2CppInterop.SourceGenerator.Tests/Generation/InjectedStructTests.cs new file mode 100644 index 00000000..c6f3dd89 --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/Generation/InjectedStructTests.cs @@ -0,0 +1,279 @@ +namespace Il2CppInterop.SourceGenerator.Tests.Generation; + +public class InjectedStructTests : SourceGenerationTests +{ + [Test] + public Task StructWithNoMembers() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + + [InjectedType] + public partial struct Sample + { + } + """; + + var expectedGeneratedCode = """ + // + #nullable enable + + [global::Il2CppInterop.Common.Attributes.Il2CppType(typeof(Il2CppInternals))] + public partial struct Sample : + global::Il2CppSystem.IValueType, + global::Il2CppInterop.Common.IIl2CppType + { + [global::Il2CppInterop.Common.Attributes.Il2CppMethod] + public global::Il2CppSystem.Boolean Equals(global::Il2CppSystem.IObject obj) + { + return this.Equals((object)obj); + } + + [global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = "GetHashCode")] + public global::Il2CppSystem.Int32 GetIl2CppHashCode() + { + return this.GetHashCode(); + } + + [global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = "ToString")] + public global::Il2CppSystem.String ToIl2CppString() + { + return this.ToString(); + } + + nint global::Il2CppInterop.Common.IIl2CppType.ObjectClass => global::Il2CppInterop.Common.Il2CppType.GetClassPointer(); + + readonly global::Il2CppInterop.Common.ObjectPointer global::Il2CppInterop.Common.IIl2CppType.BoxNative() => global::Il2CppInterop.Runtime.NativeBoxing.BoxValueType(in this); + + static int global::Il2CppInterop.Common.IIl2CppType.Size => Il2CppInternals.Size; + + static Sample global::Il2CppInterop.Common.IIl2CppType.ReadFromSpan(global::System.ReadOnlySpan span) + { + #nullable disable + return new Sample + { + }; + #nullable restore + } + + static void global::Il2CppInterop.Common.IIl2CppType.WriteToSpan(Sample value, global::System.Span span) + { + } + + static Sample() + { + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Il2CppInternals).TypeHandle); + } + } + + file static class Il2CppInternals + { + public static readonly int Size; + + static Il2CppInternals() + { + #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + #pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + global::Il2CppInterop.Runtime.Injection.TypeInjector.RegisterTypeInIl2Cpp(); + #pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + Size = global::Il2CppInterop.Common.IL2CPP.il2cpp_class_value_size(global::Il2CppInterop.Common.Il2CppType.GetClassPointer(), out _); + } + } + + """; + + return TestGeneration(testCode, "Sample.g.cs", expectedGeneratedCode); + } + + [Test] + public Task StructContainingPrimitiveFieldsWithAttributes() + { + var testCode = """ + using Il2CppInterop.Common.Attributes; + + [InjectedType] + public partial struct Sample + { + [Il2CppField] + public Il2CppSystem.Int32 intValue; + [Il2CppField] + public Il2CppSystem.String stringValue; + } + """; + + var expectedGeneratedCode = """ + // + #nullable enable + + [global::Il2CppInterop.Common.Attributes.Il2CppType(typeof(Il2CppInternals))] + public partial struct Sample : + global::Il2CppSystem.IValueType, + global::Il2CppInterop.Common.IIl2CppType + { + [global::Il2CppInterop.Common.Attributes.Il2CppMethod] + public global::Il2CppSystem.Boolean Equals(global::Il2CppSystem.IObject obj) + { + return this.Equals((object)obj); + } + + [global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = "GetHashCode")] + public global::Il2CppSystem.Int32 GetIl2CppHashCode() + { + return this.GetHashCode(); + } + + [global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = "ToString")] + public global::Il2CppSystem.String ToIl2CppString() + { + return this.ToString(); + } + + nint global::Il2CppInterop.Common.IIl2CppType.ObjectClass => global::Il2CppInterop.Common.Il2CppType.GetClassPointer(); + + readonly global::Il2CppInterop.Common.ObjectPointer global::Il2CppInterop.Common.IIl2CppType.BoxNative() => global::Il2CppInterop.Runtime.NativeBoxing.BoxValueType(in this); + + static int global::Il2CppInterop.Common.IIl2CppType.Size => Il2CppInternals.Size; + + static Sample global::Il2CppInterop.Common.IIl2CppType.ReadFromSpan(global::System.ReadOnlySpan span) + { + #nullable disable + return new Sample + { + intValue = global::Il2CppInterop.Common.Il2CppType.ReadFromSpanAtOffset(span, Il2CppInternals.FieldOffset_0), + stringValue = global::Il2CppInterop.Common.Il2CppType.ReadFromSpanAtOffset(span, Il2CppInternals.FieldOffset_1), + }; + #nullable restore + } + + static void global::Il2CppInterop.Common.IIl2CppType.WriteToSpan(Sample value, global::System.Span span) + { + global::Il2CppInterop.Common.Il2CppType.WriteToSpanAtOffset(value.intValue, span, Il2CppInternals.FieldOffset_0); + global::Il2CppInterop.Common.Il2CppType.WriteToSpanAtOffset(value.stringValue, span, Il2CppInternals.FieldOffset_1); + } + + static Sample() + { + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Il2CppInternals).TypeHandle); + } + } + + file static class Il2CppInternals + { + public static readonly int Size; + public static readonly int FieldOffset_0; // intValue + public static readonly int FieldOffset_1; // stringValue + + static Il2CppInternals() + { + #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + #pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + global::Il2CppInterop.Runtime.Injection.TypeInjector.RegisterTypeInIl2Cpp(); + #pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + Size = global::Il2CppInterop.Common.IL2CPP.il2cpp_class_value_size(global::Il2CppInterop.Common.Il2CppType.GetClassPointer(), out _); + FieldOffset_0 = global::Il2CppInterop.Runtime.FieldAccess.GetFieldOffset(global::Il2CppInterop.Runtime.FieldAccess.GetFieldInfo(global::Il2CppInterop.Common.Il2CppType.GetClassPointer(), "intValue")); + FieldOffset_1 = global::Il2CppInterop.Runtime.FieldAccess.GetFieldOffset(global::Il2CppInterop.Runtime.FieldAccess.GetFieldInfo(global::Il2CppInterop.Common.Il2CppType.GetClassPointer(), "stringValue")); + } + } + + """; + + return TestGeneration(testCode, "Sample.g.cs", expectedGeneratedCode); + } + + [Test] + public Task StructContainingPrimitiveFieldsWithoutAttributes() + { + // Il2CppField attributes are optional for instance struct fields + var testCode = """ + using Il2CppInterop.Common.Attributes; + + [InjectedType] + public partial struct Sample + { + public Il2CppSystem.Int32 intValue; + public Il2CppSystem.String stringValue; + } + """; + + var expectedGeneratedCode = """ + // + #nullable enable + + [global::Il2CppInterop.Common.Attributes.Il2CppType(typeof(Il2CppInternals))] + public partial struct Sample : + global::Il2CppSystem.IValueType, + global::Il2CppInterop.Common.IIl2CppType + { + [global::Il2CppInterop.Common.Attributes.Il2CppMethod] + public global::Il2CppSystem.Boolean Equals(global::Il2CppSystem.IObject obj) + { + return this.Equals((object)obj); + } + + [global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = "GetHashCode")] + public global::Il2CppSystem.Int32 GetIl2CppHashCode() + { + return this.GetHashCode(); + } + + [global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = "ToString")] + public global::Il2CppSystem.String ToIl2CppString() + { + return this.ToString(); + } + + nint global::Il2CppInterop.Common.IIl2CppType.ObjectClass => global::Il2CppInterop.Common.Il2CppType.GetClassPointer(); + + readonly global::Il2CppInterop.Common.ObjectPointer global::Il2CppInterop.Common.IIl2CppType.BoxNative() => global::Il2CppInterop.Runtime.NativeBoxing.BoxValueType(in this); + + static int global::Il2CppInterop.Common.IIl2CppType.Size => Il2CppInternals.Size; + + static Sample global::Il2CppInterop.Common.IIl2CppType.ReadFromSpan(global::System.ReadOnlySpan span) + { + #nullable disable + return new Sample + { + intValue = global::Il2CppInterop.Common.Il2CppType.ReadFromSpanAtOffset(span, Il2CppInternals.FieldOffset_0), + stringValue = global::Il2CppInterop.Common.Il2CppType.ReadFromSpanAtOffset(span, Il2CppInternals.FieldOffset_1), + }; + #nullable restore + } + + static void global::Il2CppInterop.Common.IIl2CppType.WriteToSpan(Sample value, global::System.Span span) + { + global::Il2CppInterop.Common.Il2CppType.WriteToSpanAtOffset(value.intValue, span, Il2CppInternals.FieldOffset_0); + global::Il2CppInterop.Common.Il2CppType.WriteToSpanAtOffset(value.stringValue, span, Il2CppInternals.FieldOffset_1); + } + + static Sample() + { + global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Il2CppInternals).TypeHandle); + } + } + + file static class Il2CppInternals + { + public static readonly int Size; + public static readonly int FieldOffset_0; // intValue + public static readonly int FieldOffset_1; // stringValue + + static Il2CppInternals() + { + #pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + #pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + global::Il2CppInterop.Runtime.Injection.TypeInjector.RegisterTypeInIl2Cpp(); + #pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + #pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + Size = global::Il2CppInterop.Common.IL2CPP.il2cpp_class_value_size(global::Il2CppInterop.Common.Il2CppType.GetClassPointer(), out _); + FieldOffset_0 = global::Il2CppInterop.Runtime.FieldAccess.GetFieldOffset(global::Il2CppInterop.Runtime.FieldAccess.GetFieldInfo(global::Il2CppInterop.Common.Il2CppType.GetClassPointer(), "intValue")); + FieldOffset_1 = global::Il2CppInterop.Runtime.FieldAccess.GetFieldOffset(global::Il2CppInterop.Runtime.FieldAccess.GetFieldInfo(global::Il2CppInterop.Common.Il2CppType.GetClassPointer(), "stringValue")); + } + } + + """; + + return TestGeneration(testCode, "Sample.g.cs", expectedGeneratedCode); + } +} diff --git a/Il2CppInterop.SourceGenerator.Tests/Il2CppInterop.SourceGenerator.Tests.csproj b/Il2CppInterop.SourceGenerator.Tests/Il2CppInterop.SourceGenerator.Tests.csproj new file mode 100644 index 00000000..ecb66781 --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/Il2CppInterop.SourceGenerator.Tests.csproj @@ -0,0 +1,32 @@ + + + + net10.0 + enable + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/Il2CppInterop.SourceGenerator.Tests/LinePositionSpanExtensions.cs b/Il2CppInterop.SourceGenerator.Tests/LinePositionSpanExtensions.cs new file mode 100644 index 00000000..40189baf --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/LinePositionSpanExtensions.cs @@ -0,0 +1,44 @@ +using Microsoft.CodeAnalysis.Text; + +namespace Il2CppInterop.SourceGenerator.Tests; + +internal static class LinePositionSpanExtensions +{ + extension(LinePositionSpan) + { + public static LinePositionSpan FindInSource(string source, string target, int index = 0) + { + var searchStartPosition = 0; + var targetStartPosition = -1; + while (index >= 0) + { + targetStartPosition = source.IndexOf(target, searchStartPosition, StringComparison.Ordinal); + if (targetStartPosition < 0) + throw new ArgumentException($"Target string '{target}' not found in source.", nameof(target)); + searchStartPosition = targetStartPosition + 1; + index--; + } + + // Calculate line and character positions + int startLine; + int startColumn; + { + var newLineCount = source.AsSpan(0, targetStartPosition).Count('\n'); + var lastNewLinePosition = newLineCount == 0 ? 0 : source.LastIndexOf('\n', targetStartPosition); + startLine = newLineCount + 1; // Line numbers are 1-based + startColumn = targetStartPosition - lastNewLinePosition; + } + + int endLine; + int endColumn; + { + var newLineCount = target.Count('\n'); + var lastNewLinePosition = newLineCount == 0 ? 0 : target.LastIndexOf('\n'); + endLine = startLine + newLineCount; + endColumn = newLineCount == 0 ? startColumn + target.Length : target.Length - lastNewLinePosition; + } + + return new LinePositionSpan(new LinePosition(startLine, startColumn), new LinePosition(endLine, endColumn)); + } + } +} diff --git a/Il2CppInterop.SourceGenerator.Tests/SourceGenerationTests.cs b/Il2CppInterop.SourceGenerator.Tests/SourceGenerationTests.cs new file mode 100644 index 00000000..b7dc796a --- /dev/null +++ b/Il2CppInterop.SourceGenerator.Tests/SourceGenerationTests.cs @@ -0,0 +1,32 @@ +using System.Text; +using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Runtime; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; + +namespace Il2CppInterop.SourceGenerator.Tests; + +public abstract class SourceGenerationTests +{ + protected CSharpSourceGeneratorTest Context { get; private set; } + + [SetUp] + public void Setup() + { + Context = new() + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net100 + }; + Context.TestState.AdditionalReferences.Add(typeof(InjectedTypeAttribute).Assembly); + Context.TestState.AdditionalReferences.Add(typeof(Il2CppSystem.Object).Assembly); + Context.TestState.AdditionalReferences.Add(typeof(RuntimeInvoke).Assembly); + } + + protected async Task TestGeneration(string testCode, string expectedFileName, string expectedGeneratedCode) + { + Context.TestCode = testCode; + Context.TestState.GeneratedSources.Add((typeof(InjectedTypeGenerator), expectedFileName, SourceText.From(expectedGeneratedCode.Replace("\r", null), Encoding.UTF8, SourceHashAlgorithm.Sha256))); + await Context.RunAsync(TestContext.CurrentContext.CancellationToken); + } +} diff --git a/Il2CppInterop.SourceGenerator/EquatableArray.cs b/Il2CppInterop.SourceGenerator/EquatableArray.cs new file mode 100644 index 00000000..6b666c1a --- /dev/null +++ b/Il2CppInterop.SourceGenerator/EquatableArray.cs @@ -0,0 +1,45 @@ +using System.Collections; + +namespace Il2CppInterop.SourceGenerator; + +internal readonly struct EquatableArray : IEquatable>, IReadOnlyList + where T : IEquatable +{ + private readonly IReadOnlyList array; + public EquatableArray(IReadOnlyList array) + { + this.array = array; + } + + public T this[int index] => array[index]; + + public int Count => array.Count; + + public bool Equals(EquatableArray other) + { + if (array.Count != other.array.Count) + { + return false; + } + for (var i = array.Count - 1; i >= 0; i--) + { + if (!array[i].Equals(other.array[i])) + { + return false; + } + } + return true; + } + + public IEnumerator GetEnumerator() + { + return array.GetEnumerator(); + } + + public override int GetHashCode() => array.Count; + + IEnumerator IEnumerable.GetEnumerator() + { + return array.GetEnumerator(); + } +} diff --git a/Il2CppInterop.SourceGenerator/Extensions.cs b/Il2CppInterop.SourceGenerator/Extensions.cs new file mode 100644 index 00000000..6736951d --- /dev/null +++ b/Il2CppInterop.SourceGenerator/Extensions.cs @@ -0,0 +1,74 @@ +using System.CodeDom.Compiler; +using Microsoft.CodeAnalysis; + +namespace Il2CppInterop.SourceGenerator; + +internal static class Extensions +{ + // Converts a Roslyn Accessibility to the keyword(s) needed in emitted source. + // Protected/ProtectedAndInternal are included for completeness but are unusual + // on a top-level injected type. + internal static string GetAccessibilityKeyword(this Accessibility a) => a switch + { + Accessibility.Public => "public", + Accessibility.Internal => "internal", + Accessibility.Private => "private", + Accessibility.Protected => "protected", + Accessibility.ProtectedOrInternal => "protected internal", + Accessibility.ProtectedAndInternal => "private protected", + _ => "public", + }; + + internal static string GetTypeKeyword(this TypeKind kind) => kind switch + { + TypeKind.Class => "class", + TypeKind.Struct => "struct", + TypeKind.Interface => "interface", + _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null), + }; + + extension(INamedTypeSymbol? symbol) + { + public bool IsType(string name, ReadOnlySpan namespaceParts) => + symbol is not null && + symbol.Name == name && + MatchesNamespace(symbol.ContainingNamespace, namespaceParts); + + public bool HasAttribute(string name, ReadOnlySpan namespaceParts) => + ((ISymbol?)symbol).HasAttribute(name, namespaceParts); + } + + extension(ISymbol? symbol) + { + public bool HasAttribute(string name, ReadOnlySpan namespaceParts) + { + if (symbol is null) + return false; + + foreach (var attr in symbol.GetAttributes()) + { + if (attr.AttributeClass.IsType(name, namespaceParts)) + return true; + } + + return false; + } + } + + private static bool MatchesNamespace(INamespaceSymbol? ns, ReadOnlySpan parts) + { + // Walk from innermost to outermost + for (var i = parts.Length - 1; i >= 0; i--) + { + if (ns is null or { IsGlobalNamespace: true } || ns.Name != parts[i]) + return false; + ns = ns.ContainingNamespace; + } + + // Make sure we've consumed all namespaces up to global + return ns is null or { IsGlobalNamespace: true }; + } + + internal static void WriteLineNoTabs(this IndentedTextWriter writer) => writer.WriteLineNoTabs(null); + +} diff --git a/Il2CppInterop.SourceGenerator/Il2CppInterop.SourceGenerator.csproj b/Il2CppInterop.SourceGenerator/Il2CppInterop.SourceGenerator.csproj new file mode 100644 index 00000000..a6e97a68 --- /dev/null +++ b/Il2CppInterop.SourceGenerator/Il2CppInterop.SourceGenerator.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + enable + false + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Il2CppInterop.SourceGenerator/InjectedTypeAnalyzer.cs b/Il2CppInterop.SourceGenerator/InjectedTypeAnalyzer.cs new file mode 100644 index 00000000..8d468c88 --- /dev/null +++ b/Il2CppInterop.SourceGenerator/InjectedTypeAnalyzer.cs @@ -0,0 +1,523 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Il2CppInterop.SourceGenerator; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InjectedTypeAnalyzer : DiagnosticAnalyzer +{ + #region Diagnostic Descriptors + + public static DiagnosticDescriptor MustBePartial { get; } = new( + id: "IL2CPP0001", + title: "Injected type must be partial", + messageFormat: "Type '{0}' is marked with [InjectedType] but is not declared as partial. Add the 'partial' modifier so the source generator can augment it.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Types decorated with [InjectedTypeAttribute] must be partial. " + + "The Il2CppTypeGenerator source generator needs to add members to the type, " + + "which requires the partial keyword.", + helpLinkUri: "https://github.com/BepInEx/Il2CppInterop"); + + public static DiagnosticDescriptor MustInheritIl2CppObject { get; } = new( + id: "IL2CPP0002", + title: "Injected class must inherit from Il2CppSystem.Object", + messageFormat: "Class '{0}' is marked with [InjectedType] but does not inherit from Il2CppSystem.Object", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor CannotHaveStaticConstructor { get; } = new( + id: "IL2CPP0003", + title: "Injected type cannot have a static constructor", + messageFormat: "Type '{0}' is marked with [InjectedType] but has a static constructor. Remove it, as Il2CppInternals handles static initialization.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor CannotOverrideIl2CppFinalize { get; } = new( + id: "IL2CPP0004", + title: "Injected type should not manually override Il2CppFinalize", + messageFormat: "Type '{0}' manually overrides Il2CppFinalize. Use [Il2CppFinalizer] on a method instead.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor CannotHaveManagedFieldOnStructOrInterface { get; } = new( + id: "IL2CPP0005", + title: "Structs and interfaces cannot have [ManagedField] properties", + messageFormat: "Type '{0}' is a {1} and cannot have properties marked with [ManagedField]", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor CannotHaveIl2CppFieldOnStructOrInterface { get; } = new( + id: "IL2CPP0006", + title: "Structs and interfaces cannot have instance properties marked with [Il2CppField]", + messageFormat: "Type '{0}' is a {1} and cannot have instance properties marked with [Il2CppField]", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor ManagedFieldMustBeInstance { get; } = new( + id: "IL2CPP0007", + title: "Properties marked with [ManagedField] must be instance members", + messageFormat: "Property '{0}' is marked with [ManagedField] but is static. [ManagedField] properties must be instance members.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor FieldPropertyMustBePartial { get; } = new( + id: "IL2CPP0008", + title: "Properties marked with [ManagedField] or [Il2CppField] must be partial", + messageFormat: "Property '{0}' is marked with [{1}] but is not partial. Add the 'partial' modifier.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor ShouldNotHaveUninjectedInstanceFields { get; } = new( + id: "IL2CPP0009", + title: "Injected types should not have uninjected instance fields", + messageFormat: "Type '{0}' has instance field '{1}' which is not managed by Il2CppInterop. Use [ManagedField] or [Il2CppField] properties instead.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor Il2CppFinalizerMustHaveNoParameters { get; } = new( + id: "IL2CPP0010", + title: "Methods annotated with [Il2CppFinalizer] should have no parameters", + messageFormat: "Method '{0}' is annotated with [Il2CppFinalizer] but has parameters. The finalizer must be parameterless.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor Il2CppFinalizerShouldReturnVoid { get; } = new( + id: "IL2CPP0011", + title: "Methods annotated with [Il2CppFinalizer] should return nothing", + messageFormat: "Method '{0}' is annotated with [Il2CppFinalizer] but does not return void.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor OnlyObjectPointerConstructorCanCallBase { get; } = new( + id: "IL2CPP0012", + title: "Only the ObjectPointer constructor can call a base constructor", + messageFormat: "Constructor in type '{0}' calls a base constructor, but only the ObjectPointer constructor is allowed to do so.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor Il2CppFieldCannotBeStatic { get; } = new( + id: "IL2CPP0013", + title: "Static fields cannot be annotated with [Il2CppField]", + messageFormat: "Field '{0}' is static and cannot be annotated with [Il2CppField].", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor InstanceFieldsInClassesCannotBeInjected { get; } = new( + id: "IL2CPP0014", + title: "Instance fields in classes cannot be annotated with [Il2CppField]", + messageFormat: "Field '{0}' in class '{1}' is an instance member annotated with [Il2CppField]. [Il2CppField] is only valid on properties in classes.", + category: "Il2CppInterop", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => + [ + MustBePartial, + MustInheritIl2CppObject, + CannotHaveStaticConstructor, + CannotOverrideIl2CppFinalize, + CannotHaveManagedFieldOnStructOrInterface, + CannotHaveIl2CppFieldOnStructOrInterface, + ManagedFieldMustBeInstance, + FieldPropertyMustBePartial, + ShouldNotHaveUninjectedInstanceFields, + Il2CppFinalizerMustHaveNoParameters, + Il2CppFinalizerShouldReturnVoid, + OnlyObjectPointerConstructorCanCallBase, + Il2CppFieldCannotBeStatic, + InstanceFieldsInClassesCannotBeInjected, + ]; + + #endregion + + #region Registration + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterSyntaxNodeAction( + AnalyzeTypeDeclaration, + SyntaxKind.ClassDeclaration, + SyntaxKind.StructDeclaration, + SyntaxKind.InterfaceDeclaration); + } + + #endregion + + #region Analysis + + private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context) + { + var tds = (TypeDeclarationSyntax)context.Node; + + var symbol = context.SemanticModel.GetDeclaredSymbol(tds, context.CancellationToken); + if (symbol is null) + return; + + // Gate everything behind the attribute — no [InjectedType], no diagnostics + if (!symbol.HasAttribute("InjectedTypeAttribute", ["Il2CppInterop", "Common", "Attributes"])) + return; + + CheckMustBePartial(context, tds, symbol); + CheckMustInheritIl2CppObject(context, tds, symbol); + CheckNoStaticConstructor(context, tds, symbol); + CheckNoIl2CppFinalizeOverride(context, tds, symbol); + CheckNoManagedFieldOnStructOrInterface(context, tds, symbol); + CheckNoIl2CppFieldOnStructOrInterface(context, tds, symbol); + CheckManagedFieldMustBeInstance(context, tds); + CheckFieldPropertiesMustBePartial(context, tds); + CheckNoUninjectedInstanceMembers(context, tds, symbol); + CheckIl2CppFinalizerMethods(context, tds); + CheckConstructorsDoNotCallBase(context, tds, symbol); + CheckIl2CppFieldOnStaticProperties(context, tds); + CheckIl2CppFieldOnInstanceFieldsInClass(context, tds, symbol); + } + + #endregion + + #region Helpers + + private static bool HasAttribute( + SyntaxNodeAnalysisContext context, + PropertyDeclarationSyntax prop, + string attributeName, + ReadOnlySpan attributeNamespace) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(prop, context.CancellationToken); + return symbol?.HasAttribute(attributeName, attributeNamespace) ?? false; + } + + #endregion + + #region Checks + + // IL2CPP0001 + private static void CheckMustBePartial( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + if (tds.Modifiers.Any(SyntaxKind.PartialKeyword)) + return; + + context.ReportDiagnostic(Diagnostic.Create( + MustBePartial, + tds.Identifier.GetLocation(), + symbol.Name)); + } + + // IL2CPP0002 + private static void CheckMustInheritIl2CppObject( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + if (tds is not ClassDeclarationSyntax || symbol.TypeKind != TypeKind.Class) + return; + + var baseType = symbol.BaseType; + + var isSystemObject = baseType is null || baseType.IsType("Object", ["System"]); + + var inheritsIl2CppObject = false; + var current = baseType; + while (current is not null) + { + if (current.IsType("Object", ["Il2CppSystem"])) + { + inheritsIl2CppObject = true; + break; + } + current = current.BaseType; + } + + if (isSystemObject || !inheritsIl2CppObject) + { + context.ReportDiagnostic(Diagnostic.Create( + MustInheritIl2CppObject, + tds.Identifier.GetLocation(), + symbol.Name)); + } + } + + // IL2CPP0003 + private static void CheckNoStaticConstructor( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + var staticCtor = tds.Members + .OfType() + .FirstOrDefault(c => c.Modifiers.Any(SyntaxKind.StaticKeyword)); + + if (staticCtor is null) + return; + + context.ReportDiagnostic(Diagnostic.Create( + CannotHaveStaticConstructor, + staticCtor.Identifier.GetLocation(), + symbol.Name)); + } + + // IL2CPP0004 + private static void CheckNoIl2CppFinalizeOverride( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + var finalizeOverride = tds.Members + .OfType() + .FirstOrDefault(m => + m.Identifier.Text == "Il2CppFinalize" && + m.Modifiers.Any(SyntaxKind.OverrideKeyword)); + + if (finalizeOverride is null) + return; + + context.ReportDiagnostic(Diagnostic.Create( + CannotOverrideIl2CppFinalize, + finalizeOverride.Identifier.GetLocation(), + symbol.Name)); + } + + // IL2CPP0005 + private static void CheckNoManagedFieldOnStructOrInterface( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + if (tds is not (StructDeclarationSyntax or InterfaceDeclarationSyntax)) + return; + + var offending = tds.Members + .OfType() + .Where(p => HasAttribute(context, p, "ManagedFieldAttribute", ["Il2CppInterop", "Common", "Attributes"])); + + foreach (var prop in offending) + { + context.ReportDiagnostic(Diagnostic.Create( + CannotHaveManagedFieldOnStructOrInterface, + prop.Identifier.GetLocation(), + symbol.Name, + symbol.TypeKind == TypeKind.Struct ? "struct" : "interface")); + } + } + + // IL2CPP0006 + private static void CheckNoIl2CppFieldOnStructOrInterface( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + if (tds is not (StructDeclarationSyntax or InterfaceDeclarationSyntax)) + return; + + var offending = tds.Members + .OfType() + .Where(p => + !p.Modifiers.Any(SyntaxKind.StaticKeyword) && + HasAttribute(context, p, "Il2CppFieldAttribute", ["Il2CppInterop", "Common", "Attributes"])); + + foreach (var prop in offending) + { + context.ReportDiagnostic(Diagnostic.Create( + CannotHaveIl2CppFieldOnStructOrInterface, + prop.Identifier.GetLocation(), + symbol.Name, + symbol.TypeKind == TypeKind.Struct ? "struct" : "interface")); + } + } + + // IL2CPP0007 + private static void CheckManagedFieldMustBeInstance( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds) + { + var offending = tds.Members + .OfType() + .Where(p => + p.Modifiers.Any(SyntaxKind.StaticKeyword) && + HasAttribute(context, p, "ManagedFieldAttribute", ["Il2CppInterop", "Common", "Attributes"])); + + foreach (var prop in offending) + { + context.ReportDiagnostic(Diagnostic.Create( + ManagedFieldMustBeInstance, + prop.Identifier.GetLocation(), + prop.Identifier.Text)); + } + } + + // IL2CPP0008 + private static void CheckFieldPropertiesMustBePartial( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds) + { + var offending = tds.Members + .OfType() + .Where(p => !p.Modifiers.Any(SyntaxKind.PartialKeyword)); + + foreach (var prop in offending) + { + var hasManagedField = HasAttribute(context, prop, "ManagedFieldAttribute", ["Il2CppInterop", "Common", "Attributes"]); + var hasIl2CppField = HasAttribute(context, prop, "Il2CppFieldAttribute", ["Il2CppInterop", "Common", "Attributes"]); + + if (!hasManagedField && !hasIl2CppField) + continue; + + if (hasManagedField) + { + context.ReportDiagnostic(Diagnostic.Create( + FieldPropertyMustBePartial, + prop.Identifier.GetLocation(), + prop.Identifier.Text, + "ManagedField")); + } + + if (hasIl2CppField) + { + context.ReportDiagnostic(Diagnostic.Create( + FieldPropertyMustBePartial, + prop.Identifier.GetLocation(), + prop.Identifier.Text, + "Il2CppField")); + } + } + } + + // IL2CPP0009 + private static void CheckNoUninjectedInstanceMembers( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + // Instance fields in structs are automatically injected, so we don't need to check for them. + if (tds is not StructDeclarationSyntax) + { + foreach (var field in tds.Members.OfType()) + { + if (field.Modifiers.Any(SyntaxKind.StaticKeyword)) + continue; + + foreach (var variable in field.Declaration.Variables) + { + context.ReportDiagnostic(Diagnostic.Create( + ShouldNotHaveUninjectedInstanceFields, + variable.Identifier.GetLocation(), + symbol.Name, + variable.Identifier.Text)); + } + } + } + } + + // IL2CPP0010 + IL2CPP0011 + private static void CheckIl2CppFinalizerMethods( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds) + { + var finalizerMethods = tds.Members + .OfType() + .Where(m => + { + var sym = context.SemanticModel.GetDeclaredSymbol(m, context.CancellationToken); + return sym?.HasAttribute("Il2CppFinalizerAttribute", ["Il2CppInterop", "Common", "Attributes"]) ?? false; + }); + + foreach (var method in finalizerMethods) + { + if (method.ParameterList.Parameters.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create( + Il2CppFinalizerMustHaveNoParameters, + method.Identifier.GetLocation(), + method.Identifier.Text)); + } + var methodSymbol = context.SemanticModel.GetDeclaredSymbol(method, context.CancellationToken); + if (methodSymbol?.ReturnType.SpecialType != SpecialType.System_Void) + { + context.ReportDiagnostic(Diagnostic.Create( + Il2CppFinalizerShouldReturnVoid, + method.ReturnType.GetLocation(), + method.Identifier.Text)); + } + } + } + + // IL2CPP0012 + private static void CheckConstructorsDoNotCallBase( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + var offending = tds.Members + .OfType() + .Where(c => + c.Initializer is { ThisOrBaseKeyword.RawKind: (int)SyntaxKind.BaseKeyword } && + !IsObjectPointerConstructor(context, c)); + + foreach (var ctor in offending) + { + context.ReportDiagnostic(Diagnostic.Create( + OnlyObjectPointerConstructorCanCallBase, + ctor.Initializer!.GetLocation(), + symbol.Name)); + } + } + + private static bool IsObjectPointerConstructor(SyntaxNodeAnalysisContext context, ConstructorDeclarationSyntax ctor) + { + if (ctor.ParameterList.Parameters.Count != 1) + return false; + + var paramSyntax = ctor.ParameterList.Parameters[0]; + var paramSymbol = context.SemanticModel.GetDeclaredSymbol(paramSyntax, context.CancellationToken); + return (paramSymbol?.Type as INamedTypeSymbol).IsType("ObjectPointer", ["Il2CppInterop", "Common"]); + } + + // IL2CPP0013 + private static void CheckIl2CppFieldOnStaticProperties( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds) + { + var offending = tds.Members + .OfType() + .Where(p => + p.Modifiers.Any(SyntaxKind.StaticKeyword) && + HasAttribute(context, p, "Il2CppFieldAttribute", ["Il2CppInterop", "Common", "Attributes"])); + + foreach (var prop in offending) + { + context.ReportDiagnostic(Diagnostic.Create( + Il2CppFieldCannotBeStatic, + prop.Identifier.GetLocation(), + prop.Identifier.Text)); + } + } + + // IL2CPP0014 + private static void CheckIl2CppFieldOnInstanceFieldsInClass( + SyntaxNodeAnalysisContext context, TypeDeclarationSyntax tds, INamedTypeSymbol symbol) + { + if (tds is not ClassDeclarationSyntax || symbol.TypeKind != TypeKind.Class) + return; + + foreach (var field in tds.Members.OfType() + .Where(f => !f.Modifiers.Any(SyntaxKind.StaticKeyword))) + { + foreach (var variable in field.Declaration.Variables) + { + var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken); + if (fieldSymbol?.HasAttribute("Il2CppFieldAttribute", ["Il2CppInterop", "Common", "Attributes"]) != true) + continue; + + context.ReportDiagnostic(Diagnostic.Create( + InstanceFieldsInClassesCannotBeInjected, + variable.Identifier.GetLocation(), + variable.Identifier.Text, + symbol.Name)); + } + } + } + + #endregion +} diff --git a/Il2CppInterop.SourceGenerator/InjectedTypeGenerator.cs b/Il2CppInterop.SourceGenerator/InjectedTypeGenerator.cs new file mode 100644 index 00000000..a4386ed9 --- /dev/null +++ b/Il2CppInterop.SourceGenerator/InjectedTypeGenerator.cs @@ -0,0 +1,445 @@ +using System.CodeDom.Compiler; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Il2CppInterop.SourceGenerator; + +[Generator] +public sealed class InjectedTypeGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var types = context.SyntaxProvider + .ForAttributeWithMetadataName( + "Il2CppInterop.Common.Attributes.InjectedTypeAttribute", + predicate: static (node, _) => + { + if (node is not TypeDeclarationSyntax tds) + return false; + if (!tds.Modifiers.Any(SyntaxKind.PartialKeyword)) + return false; + // Type must be non-generic + return tds switch + { + ClassDeclarationSyntax s => s.TypeParameterList is null, + InterfaceDeclarationSyntax s => s.TypeParameterList is null, + StructDeclarationSyntax s => s.TypeParameterList is null, + _ => false, + }; + }, + transform: static (ctx, ct) => + { + + var injectedTypeAttr = ctx.Attributes[0]; + var assemblyName = injectedTypeAttr.NamedArguments + .FirstOrDefault(a => a.Key == "Assembly") + .Value.Value as string; + return TypeModel.FromSymbol((INamedTypeSymbol)ctx.TargetSymbol, assemblyName, ct); + }) + .Where(static m => m is not null); + + context.RegisterSourceOutput(types, static (ctx, model) => Emit(ctx, model!)); + } + + + private static void Emit(SourceProductionContext ctx, TypeModel model) + { + using var sw = new StringWriter() + { + NewLine = "\n" + }; + using var writer = new IndentedTextWriter(sw, " "); + + writer.WriteLine("// "); + writer.WriteLine("#nullable enable"); + writer.WriteLine(); + + if (model.Namespace is not null) + { + writer.WriteLine($"namespace {model.Namespace}"); + writer.WriteLine("{"); + writer.Indent++; + } + + EmitPartialType(writer, model); + writer.WriteLineNoTabs(); + EmitInternalsClass(writer, model); + + if (model.Namespace is not null) + { + writer.Indent--; + writer.WriteLine("}"); + } + + var hint = model.Namespace is null + ? $"{model.TypeName}.g.cs" + : $"{model.Namespace}.{model.TypeName}.g.cs"; + + ctx.AddSource(hint, sw.ToString()); + } + + private static void EmitPartialType(IndentedTextWriter writer, TypeModel model) + { + var access = model.DeclaredAccessibility?.GetAccessibilityKeyword(); + + writer.WriteLine("[global::Il2CppInterop.Common.Attributes.Il2CppType(typeof(Il2CppInternals))]"); + + if (model.TypeKind == TypeKind.Struct) + { + writer.WriteLine($"{access} partial struct {model.TypeName} :"); + writer.Indent++; + writer.WriteLine("global::Il2CppSystem.IValueType,"); + writer.WriteLine($"global::Il2CppInterop.Common.IIl2CppType<{model.TypeName}>"); + writer.Indent--; + } + else + { + writer.WriteLine($"{access} partial {model.TypeKind.GetTypeKeyword()} {model.TypeName} : global::Il2CppInterop.Common.IIl2CppType<{model.TypeName}>"); + } + + writer.WriteLine("{"); + writer.Indent++; + + if (model.TypeKind == TypeKind.Struct) + { + EmitValueTypeBridgeMethods(writer); + writer.WriteLineNoTabs(); + } + foreach (var member in model.Members) + { + if (member.IsStatic) + { + EmitStaticIl2CppProperty(writer, member); + } + else if (model.TypeKind == TypeKind.Struct) + { + // It is an error to use [Il2CppField] on an instance property of a value type. + continue; + } + else if (member.Kind == MemberKind.Il2Cpp) + { + EmitIl2CppProperty(writer, member); + } + else + { + EmitManagedProperty(writer, member); + } + + writer.WriteLineNoTabs(); + } + if (model.NeedsObjectPointerConstructor) + { + EmitConstructor(writer, model); + writer.WriteLineNoTabs(); + } + + if (model.FinalizerMethodNames.Count > 0 || model.Members.Any(m => m.Kind == MemberKind.Managed)) + { + EmitFinalizer(writer, model); + writer.WriteLineNoTabs(); + EmitLogErrorFinalizer(writer); + writer.WriteLineNoTabs(); + } + + EmitInterfaceMembers(writer, model); + + writer.Indent--; + writer.WriteLine("}"); + } + + #region Value-type-only emit + + private static void EmitValueTypeBridgeMethods(IndentedTextWriter writer) + { + writer.WriteLine("[global::Il2CppInterop.Common.Attributes.Il2CppMethod]"); + writer.WriteLine("public global::Il2CppSystem.Boolean Equals(global::Il2CppSystem.IObject obj)"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("return this.Equals((object)obj);"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLineNoTabs(); + + writer.WriteLine("[global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = \"GetHashCode\")]"); + writer.WriteLine("public global::Il2CppSystem.Int32 GetIl2CppHashCode()"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("return this.GetHashCode();"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLineNoTabs(); + + writer.WriteLine("[global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = \"ToString\")]"); + writer.WriteLine("public global::Il2CppSystem.String ToIl2CppString()"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("return this.ToString();"); + writer.Indent--; + writer.WriteLine("}"); + } + + #endregion + + #region Class/interface-only emit + + private static void EmitStaticIl2CppProperty(IndentedTextWriter writer, MemberModel member) + { + var accessPrefix = member.Accessibility is { } a ? a.GetAccessibilityKeyword() + " " : ""; + writer.WriteLine($"{accessPrefix}static partial {member.Type} {member.Name}"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine($"get => global::Il2CppInterop.Runtime.FieldAccess.GetStaticFieldValue<{member.Type}>(Il2CppInternals.FieldInfoPtr_{member.Index});"); + writer.WriteLine($"set => global::Il2CppInterop.Runtime.FieldAccess.SetStaticFieldValue(Il2CppInternals.FieldInfoPtr_{member.Index}, value);"); + writer.Indent--; + writer.WriteLine("}"); + } + + private static void EmitIl2CppProperty(IndentedTextWriter writer, MemberModel member) + { + var accessPrefix = member.Accessibility is { } a ? a.GetAccessibilityKeyword() + " " : ""; + writer.WriteLine($"{accessPrefix}partial {member.Type} {member.Name}"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine($"get => global::Il2CppInterop.Runtime.FieldAccess.GetInstanceFieldValue<{member.Type}>(this, Il2CppInternals.FieldOffset_{member.Index});"); + writer.WriteLine($"set => global::Il2CppInterop.Runtime.FieldAccess.SetInstanceFieldValue(this, Il2CppInternals.FieldOffset_{member.Index}, value);"); + writer.Indent--; + writer.WriteLine("}"); + } + + private static void EmitManagedProperty(IndentedTextWriter writer, MemberModel member) + { + // Backing field + writer.WriteLine($"[global::Il2CppInterop.Common.Attributes.Il2CppField(Name = nameof({member.Name}))]"); + writer.WriteLine($"private global::Il2CppSystem.IntPtr {member.Name}__BackingField"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine($"get => global::Il2CppInterop.Runtime.FieldAccess.GetInstanceFieldValue(this, Il2CppInternals.FieldOffset_{member.Index});"); + writer.WriteLine($"set => global::Il2CppInterop.Runtime.FieldAccess.SetInstanceFieldValue(this, Il2CppInternals.FieldOffset_{member.Index}, value);"); + writer.Indent--; + writer.WriteLine("}"); + + writer.WriteLineNoTabs(); + + // Public partial property + var accessPrefix = member.Accessibility is { } a ? a.GetAccessibilityKeyword() + " " : ""; + writer.WriteLine($"{accessPrefix}partial {member.Type} {member.Name}"); + writer.WriteLine("{"); + writer.Indent++; + + writer.WriteLine($"get => global::System.Runtime.InteropServices.GCHandle<{member.Type}>.FromIntPtr({member.Name}__BackingField).Target;"); + + writer.WriteLine("set"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine($"global::System.Runtime.InteropServices.GCHandle<{member.Type}>.FromIntPtr({member.Name}__BackingField).Dispose();"); + writer.WriteLine($"{member.Name}__BackingField = value is not null"); + writer.Indent++; + writer.WriteLine($"? global::System.Runtime.InteropServices.GCHandle<{member.Type}>.ToIntPtr(new global::System.Runtime.InteropServices.GCHandle<{member.Type}>(value))"); + writer.WriteLine(": default;"); + writer.Indent--; + writer.Indent--; + writer.WriteLine("}"); + + writer.Indent--; + writer.WriteLine("}"); + } + + private static void EmitConstructor(IndentedTextWriter writer, TypeModel model) + { + writer.WriteLine($"public {model.TypeName}(global::Il2CppInterop.Common.ObjectPointer pointer) : base(pointer)"); + writer.WriteLine("{"); + writer.WriteLine("}"); + } + + private static void EmitFinalizer(IndentedTextWriter writer, TypeModel model) + { + writer.WriteLine("[global::Il2CppInterop.Common.Attributes.Il2CppMethod(Name = \"Finalize\")]"); + writer.WriteLine("public override void Il2CppFinalize()"); + writer.WriteLine("{"); + writer.Indent++; + + writer.WriteLine("// This disposal happens when the object is collected by the Il2Cpp GC instead of the managed GC."); + writer.WriteLine("// That ensures that the referenced value is kept alive as long as the Il2Cpp object is alive, even if the managed wrapper gets collected."); + writer.WriteLine("// In theory, the managed wrapper could be collected and recreated multiple times during the lifetime of the Il2Cpp object,"); + writer.WriteLine("// so this ensures that the managed fields are not disposed prematurely."); + + writer.WriteLine("try"); + writer.WriteLine("{"); + writer.Indent++; + + foreach (var name in model.FinalizerMethodNames) + writer.WriteLine($"this.{name}();"); + + foreach (var f in model.Members.Where(f => f.Kind == MemberKind.Managed)) + writer.WriteLine($"{f.Name} = null!;"); + + writer.Indent--; + writer.WriteLine("}"); + + writer.WriteLine("catch (global::System.Exception ex)"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("LogErrorIl2CppFinalize(ex);"); + writer.Indent--; + writer.WriteLine("}"); + + writer.WriteLine("finally"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("base.Il2CppFinalize(); // Must call base method"); + writer.Indent--; + writer.WriteLine("}"); + + writer.Indent--; + writer.WriteLine("}"); + } + + private static void EmitLogErrorFinalizer(IndentedTextWriter writer) + { + writer.WriteLine("partial void LogErrorIl2CppFinalize(global::System.Exception exception);"); + } + + #endregion + + #region Shared emit + + private static void EmitInterfaceMembers(IndentedTextWriter writer, TypeModel model) + { + var typeName = model.TypeName; + + // ObjectClass + writer.WriteLine($"nint global::Il2CppInterop.Common.IIl2CppType.ObjectClass => global::Il2CppInterop.Common.Il2CppType.GetClassPointer<{typeName}>();"); + writer.WriteLineNoTabs(); + + if (model.TypeKind == TypeKind.Struct) + { + // BoxNative + writer.WriteLine($"readonly global::Il2CppInterop.Common.ObjectPointer global::Il2CppInterop.Common.IIl2CppType.BoxNative() => global::Il2CppInterop.Runtime.NativeBoxing.BoxValueType<{typeName}>(in this);"); + writer.WriteLineNoTabs(); + + // Size + writer.WriteLine($"static int global::Il2CppInterop.Common.IIl2CppType<{typeName}>.Size => Il2CppInternals.Size;"); + writer.WriteLineNoTabs(); + + // ReadFromSpan — constructs via object initializer, one field per offset + writer.WriteLine($"static {typeName} global::Il2CppInterop.Common.IIl2CppType<{typeName}>.ReadFromSpan(global::System.ReadOnlySpan span)"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("#nullable disable"); + writer.WriteLine($"return new {typeName}"); + writer.WriteLine("{"); + writer.Indent++; + foreach (var member in model.Members.Where(x => !x.IsStatic)) + writer.WriteLine($"{member.Name} = global::Il2CppInterop.Common.Il2CppType.ReadFromSpanAtOffset<{member.Type}>(span, Il2CppInternals.FieldOffset_{member.Index}),"); + writer.Indent--; + writer.WriteLine("};"); + writer.WriteLine("#nullable restore"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLineNoTabs(); + + // WriteToSpan — one WriteToSpanAtOffset call per field + writer.WriteLine($"static void global::Il2CppInterop.Common.IIl2CppType<{typeName}>.WriteToSpan({typeName} value, global::System.Span span)"); + writer.WriteLine("{"); + writer.Indent++; + foreach (var member in model.Members.Where(x => !x.IsStatic)) + writer.WriteLine($"global::Il2CppInterop.Common.Il2CppType.WriteToSpanAtOffset(value.{member.Name}, span, Il2CppInternals.FieldOffset_{member.Index});"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLineNoTabs(); + } + else + { + // UnboxNative + writer.WriteLine($"static {typeName} global::Il2CppInterop.Common.IIl2CppType<{typeName}>.UnboxNative(global::Il2CppInterop.Common.ObjectPointer pointer) => new {typeName}(pointer);"); + writer.WriteLineNoTabs(); + + // Size + writer.WriteLine($"static int global::Il2CppInterop.Common.IIl2CppType<{typeName}>.Size => nint.Size;"); + writer.WriteLineNoTabs(); + + // ReadFromSpan + writer.WriteLine($"static {typeName}? global::Il2CppInterop.Common.IIl2CppType<{typeName}>.ReadFromSpan(global::System.ReadOnlySpan span)"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine($"return global::Il2CppInterop.Common.Il2CppType.ReadReference<{typeName}>(span);"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLineNoTabs(); + + // WriteToSpan + writer.WriteLine($"static void global::Il2CppInterop.Common.IIl2CppType<{typeName}>.WriteToSpan({typeName}? value, global::System.Span span)"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("global::Il2CppInterop.Common.Il2CppType.WriteReference(value, span);"); + writer.Indent--; + writer.WriteLine("}"); + writer.WriteLineNoTabs(); + } + + if (!string.IsNullOrEmpty(model.AssemblyName)) + { + writer.WriteLine($"static string global::Il2CppInterop.Common.IIl2CppType<{typeName}>.AssemblyName => \"{model.AssemblyName}\";"); + writer.WriteLineNoTabs(); + } + + writer.WriteLine($"static {typeName}()"); + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine("global::System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Il2CppInternals).TypeHandle);"); + writer.Indent--; + writer.WriteLine("}"); + } + + private static void EmitInternalsClass(IndentedTextWriter writer, TypeModel model) + { + var isValueType = model.TypeKind == TypeKind.Struct; + + writer.WriteLine("file static class Il2CppInternals"); + writer.WriteLine("{"); + writer.Indent++; + + if (isValueType) + { + writer.WriteLine("public static readonly int Size;"); + } + foreach (var member in model.Members) + { + if (member.IsStatic) + writer.WriteLine($"public static readonly nint FieldInfoPtr_{member.Index}; // {member.Name}"); + else + writer.WriteLine($"public static readonly int FieldOffset_{member.Index}; // {member.Name}"); + } + writer.WriteLineNoTabs(); + writer.WriteLine("static Il2CppInternals()"); + writer.WriteLine("{"); + writer.Indent++; + + writer.WriteLineNoTabs("#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code"); + writer.WriteLineNoTabs("#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling."); + writer.WriteLine($"global::Il2CppInterop.Runtime.Injection.TypeInjector.RegisterTypeInIl2Cpp<{model.TypeName}>();"); + writer.WriteLineNoTabs("#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling."); + writer.WriteLineNoTabs("#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code"); + + if (isValueType) + { + writer.WriteLine($"Size = global::Il2CppInterop.Common.IL2CPP.il2cpp_class_value_size(global::Il2CppInterop.Common.Il2CppType.GetClassPointer<{model.TypeName}>(), out _);"); + } + + foreach (var member in model.Members) + { + if (member.IsStatic) + writer.WriteLine($"FieldInfoPtr_{member.Index} = global::Il2CppInterop.Runtime.FieldAccess.GetFieldInfo(global::Il2CppInterop.Common.Il2CppType.GetClassPointer<{model.TypeName}>(), \"{member.Name}\");"); + else + writer.WriteLine($"FieldOffset_{member.Index} = global::Il2CppInterop.Runtime.FieldAccess.GetFieldOffset(global::Il2CppInterop.Runtime.FieldAccess.GetFieldInfo(global::Il2CppInterop.Common.Il2CppType.GetClassPointer<{model.TypeName}>(), \"{member.Name}\"));"); + } + + writer.Indent--; + writer.WriteLine("}"); + + writer.Indent--; + writer.WriteLine("}"); + } + #endregion +} diff --git a/Il2CppInterop.SourceGenerator/MemberKind.cs b/Il2CppInterop.SourceGenerator/MemberKind.cs new file mode 100644 index 00000000..be17cb96 --- /dev/null +++ b/Il2CppInterop.SourceGenerator/MemberKind.cs @@ -0,0 +1,7 @@ +namespace Il2CppInterop.SourceGenerator; + +internal enum MemberKind +{ + Il2Cpp, + Managed +} diff --git a/Il2CppInterop.SourceGenerator/MemberModel.cs b/Il2CppInterop.SourceGenerator/MemberModel.cs new file mode 100644 index 00000000..7d4c3420 --- /dev/null +++ b/Il2CppInterop.SourceGenerator/MemberModel.cs @@ -0,0 +1,11 @@ +using Microsoft.CodeAnalysis; + +namespace Il2CppInterop.SourceGenerator; + +internal readonly record struct MemberModel( + string Name, + string Type, + MemberKind Kind, + int Index, + Accessibility? Accessibility, + bool IsStatic); diff --git a/Il2CppInterop.SourceGenerator/TypeModel.cs b/Il2CppInterop.SourceGenerator/TypeModel.cs new file mode 100644 index 00000000..195d636f --- /dev/null +++ b/Il2CppInterop.SourceGenerator/TypeModel.cs @@ -0,0 +1,133 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Il2CppInterop.SourceGenerator; + +internal sealed record TypeModel( + string? Namespace, + string TypeName, + TypeKind TypeKind, // "class", "struct", or "interface" + string? AssemblyName, + EquatableArray Members, + EquatableArray FinalizerMethodNames, + bool NeedsObjectPointerConstructor, + Accessibility? DeclaredAccessibility, + bool IsAbstract +) +{ + internal static TypeModel? FromSymbol(INamedTypeSymbol node, string? assemblyName, CancellationToken ct) + { + + var typeKind = node.TypeKind; + + switch (typeKind) + { + case TypeKind.Unknown: + case TypeKind.Class when (node.BaseType?.SpecialType == SpecialType.System_Object): + return null; + } + + + var members = new List(); + var index = 0; + + if (typeKind == TypeKind.Struct) + { + foreach (var field in node.GetMembers().OfType()) + { + ct.ThrowIfCancellationRequested(); + + var attrs = field.GetAttributes(); + if (field.IsStatic && !attrs.Any(a => a.AttributeClass.IsType("Il2CppFieldAttribute", ["Il2CppInterop", "Common", "Attributes"]))) + { + // Il2CppFieldAttribute is required for static fields, but not for instance fields. + continue; + } + + members.Add(new MemberModel( + Name: field.Name, + Type: field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + Kind: MemberKind.Il2Cpp, + Index: index++, + Accessibility: null, + IsStatic: field.IsStatic + )); + } + } + foreach (var property in node.GetMembers().OfType()) + { + ct.ThrowIfCancellationRequested(); + + var attrs = property.GetAttributes(); + var il2cpp = attrs.Any(a => a.AttributeClass.IsType("Il2CppFieldAttribute", ["Il2CppInterop", "Common", "Attributes"])); + var managed = attrs.Any(a => a.AttributeClass.IsType("ManagedFieldAttribute", ["Il2CppInterop", "Common", "Attributes"])); + + if (!il2cpp && !managed) + continue; + if (!property.IsPartialDefinition) + continue; + if (managed && property.IsStatic) + continue; + + // DeclaredAccessibility defaults to Private when no modifier is written, + // so we check the syntax directly to distinguish explicit "private" from omitted. + var explicitAccess = property.DeclaringSyntaxReferences + .Select(r => r.GetSyntax(ct)) + .OfType() + .SelectMany(s => s.Modifiers) + .Any(m => m.IsKind(SyntaxKind.PublicKeyword) + || m.IsKind(SyntaxKind.InternalKeyword) + || m.IsKind(SyntaxKind.PrivateKeyword) + || m.IsKind(SyntaxKind.ProtectedKeyword)); + + members.Add(new MemberModel( + Name: property.Name, + Type: property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + Kind: managed ? MemberKind.Managed : MemberKind.Il2Cpp, + Index: index++, + Accessibility: !explicitAccess ? null : property.DeclaredAccessibility, + IsStatic: property.IsStatic)); + } + + IReadOnlyList finalizerMethods = []; + var needsObjectPointerConstructor = false; + + if (typeKind == TypeKind.Class) + { + finalizerMethods = [ + ..node.GetMembers() + .OfType() + .Where(m => + !m.IsStatic && + m.Parameters.IsEmpty && + m.ReturnsVoid && + m.GetAttributes().Any(a => + a.AttributeClass.IsType("Il2CppFinalizerAttribute", ["Il2CppInterop", "Common", "Attributes"]))) + .Select(m => m.Name) + ]; + + needsObjectPointerConstructor = !node.GetMembers() + .OfType() + .Any(m => m.MethodKind == MethodKind.Constructor && + m.Parameters is [{ Type: INamedTypeSymbol s }] && + s.IsType("ObjectPointer", ["Il2CppInterop", "Common"])); + } + + return new TypeModel( + Namespace: node.ContainingNamespace.IsGlobalNamespace + ? null + : node.ContainingNamespace.ToDisplayString(), + TypeName: node.Name, + TypeKind: typeKind, + AssemblyName: assemblyName, + Members: new EquatableArray(members), + FinalizerMethodNames: new EquatableArray(finalizerMethods), + NeedsObjectPointerConstructor: needsObjectPointerConstructor, + DeclaredAccessibility: node.DeclaredAccessibility, + IsAbstract: node.IsAbstract); + } + +} + diff --git a/Il2CppInterop.StructGenerator/BitfieldAccessor.cs b/Il2CppInterop.StructGenerator/BitfieldAccessor.cs new file mode 100644 index 00000000..f6a4ddf8 --- /dev/null +++ b/Il2CppInterop.StructGenerator/BitfieldAccessor.cs @@ -0,0 +1,27 @@ +using System.CodeDom.Compiler; + +namespace Il2CppInterop.StructGenerator; + +internal class BitfieldAccessor +{ + public BitfieldAccessor(string accessorName, string elementName, string accessorType = "bool", + bool generateIfNotPresent = true, string? defaultGetter = "", Action? defaultGetBuilder = null, + Action? defaultSetBuilder = null) + { + AccessorName = accessorName; + ElementName = elementName; + AccessorType = accessorType; + GenerateIfNotPresent = generateIfNotPresent; + DefaultImmediateGetter = defaultGetter; + DefaultGetBuilder = defaultGetBuilder; + DefaultSetBuilder = defaultSetBuilder; + } + + public string AccessorName { get; } + public string ElementName { get; } + public string AccessorType { get; } + public bool GenerateIfNotPresent { get; } + public string? DefaultImmediateGetter { get; } + public Action? DefaultGetBuilder { get; } + public Action? DefaultSetBuilder { get; } +} diff --git a/Il2CppInterop.StructGenerator/ByRefWrapper.cs b/Il2CppInterop.StructGenerator/ByRefWrapper.cs new file mode 100644 index 00000000..02774c04 --- /dev/null +++ b/Il2CppInterop.StructGenerator/ByRefWrapper.cs @@ -0,0 +1,22 @@ +namespace Il2CppInterop.StructGenerator; + +internal class ByRefWrapper +{ + public ByRefWrapper(string wrappedType, string wrappedName, string[] nativeNames, string? forcedNativeType = null, + bool addNotSupportedIfNotExist = false, bool makeDummyIfNotExist = false) + { + WrappedType = wrappedType; + WrappedName = wrappedName; + NativeNames = nativeNames; + ForcedNativeType = forcedNativeType; + AddNotSupported = addNotSupportedIfNotExist; + MakeDummyIfNotSupported = makeDummyIfNotExist; + } + + public string WrappedType { get; } + public string WrappedName { get; } + public string[] NativeNames { get; } + public string? ForcedNativeType { get; } + public bool AddNotSupported { get; } + public bool MakeDummyIfNotSupported { get; } +} diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenClass.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenClass.cs index 18b95bd7..421256c9 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenClass.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenClass.cs @@ -1,95 +1,10 @@ -using System.Text; -using Il2CppInterop.StructGenerator.CodeGen.Enums; +namespace Il2CppInterop.StructGenerator.CodeGen; -namespace Il2CppInterop.StructGenerator.CodeGen; - -internal class CodeGenClass : CodeGenElement +internal sealed class CodeGenClass : CodeGenType { - public CodeGenClass(ElementProtection protection, string name) : base(protection, name) + public CodeGenClass(ElementProtection? protection, string name) : base(protection, name) { } - public override byte IndentAmount { get; set; } = 1; public override string Type => "class"; - public List InterfaceNames { get; } = new(); - public List Attributes { get; } = new(); - public List Methods { get; } = new(); - public List Fields { get; } = new(); - public List Properties { get; } = new(); - public List NestedElements { get; } = new(); - - public override string Build() - { - StringBuilder builder = new(); - if (Attributes.Count > 0) - { - for (var i = 0; i < Attributes.Count; i++) - { - if (i > 0) builder.Append(Indent); - builder.AppendLine($"[{Attributes[i]}]"); - } - - builder.Append(Indent); - } - - builder.Append($"{base.Build()}"); - if (InterfaceNames.Count > 0) - builder.Append($" : {string.Join(", ", InterfaceNames)}"); - builder.AppendLine(); - builder.AppendLine($"{Indent}{{"); - foreach (var method in Methods) - { - method.IndentAmount = (byte)(IndentAmount + 1); - builder.Append($"{IndentInner}{method.Build()}"); - } - - foreach (var field in Fields) - builder.AppendLine($"{IndentInner}{field.Build()}"); - foreach (var property in Properties) - { - property.IndentAmount = (byte)(IndentAmount + 1); - builder.Append($"{IndentInner}{property.Build()}"); - } - - foreach (var nestedElement in NestedElements) - { - nestedElement.IndentAmount = (byte)(IndentAmount + 1); - builder.AppendLine($"{IndentInner}{nestedElement.Build()}"); - } - - builder.AppendLine($"{Indent}}}"); - return builder.ToString(); - } - - public static bool operator !=(CodeGenClass lhs, CodeGenClass rhs) - { - return !(lhs == rhs); - } - - public static bool operator ==(CodeGenClass lhs, CodeGenClass rhs) - { - if (lhs.Fields.Count != rhs.Fields.Count) return false; - if (lhs.NestedElements.Count != rhs.NestedElements.Count) return false; - for (var i = 0; i < lhs.Fields.Count; i++) - if (lhs.Fields[i] != rhs.Fields[i]) - return false; - for (var i = 0; i < lhs.NestedElements.Count; i++) - { - if (lhs.NestedElements[i] is not CodeGenEnum lhsEnum) continue; - if (rhs.NestedElements[i] is not CodeGenEnum rhsEnum) continue; - if (lhsEnum != rhsEnum) return false; - } - - return true; - } - - public override bool Equals(object obj) - { - return obj is CodeGenClass @class && this == @class; - } - - public override int GetHashCode() - { - return HashCode.Combine(Fields.Count, NestedElements.Count); - } } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenConstructor.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenConstructor.cs index 829f0797..933b1bc9 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenConstructor.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenConstructor.cs @@ -1,13 +1,17 @@ -using Il2CppInterop.StructGenerator.CodeGen.Enums; - -namespace Il2CppInterop.StructGenerator.CodeGen; +namespace Il2CppInterop.StructGenerator.CodeGen; internal class CodeGenConstructor : CodeGenMethod { - public CodeGenConstructor(string returnType, ElementProtection protection) : base(returnType, protection, + public CodeGenConstructor(string returnType, ElementProtection? protection) : base(returnType, protection, "constructor") { } - public override string Declaration => $"{Protection.ToString().ToLower()} {Keywords}{Type}"; + public override string Declaration + { + get + { + return Protection is null ? $"{Keywords}{Type}" : $"{Protection.Value.ToCSharpString()} {Keywords}{Type}"; + } + } } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenElement.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenElement.cs index a9b4af71..e677d4e4 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenElement.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenElement.cs @@ -1,41 +1,45 @@ -using Il2CppInterop.StructGenerator.CodeGen.Enums; +using System.CodeDom.Compiler; namespace Il2CppInterop.StructGenerator.CodeGen; internal abstract class CodeGenElement { - public CodeGenElement(ElementProtection protection, string name) + public CodeGenElement(ElementProtection? protection, string name) { + ArgumentException.ThrowIfNullOrEmpty(name); Protection = protection; Name = name; } - public abstract byte IndentAmount { get; set; } public abstract string Type { get; } public bool IsStatic { get; set; } public bool IsUnsafe { get; set; } + public bool IsPartial { get; set; } public string Name { get; } - public ElementProtection Protection { get; } - public string Indent => new(' ', (IndentAmount - 1) * 4); - public string IndentInner => new(' ', IndentAmount * 4); + public ElementProtection? Protection { get; } - private List KeywordList + public string Keywords { get { - var list = new List(); - if (IsStatic) list.Add("static"); - if (IsUnsafe) list.Add("unsafe"); - return list; + var staticKeyword = IsStatic ? "static " : string.Empty; + var unsafeKeyword = IsUnsafe ? "unsafe " : string.Empty; + var partialKeyword = IsPartial ? "partial " : string.Empty; + return string.Concat(staticKeyword, unsafeKeyword, partialKeyword); } } - public string Keywords => KeywordList.Count > 0 ? $"{string.Join(' ', KeywordList)} " : string.Empty; - public virtual string Declaration => $"{Protection.ToString().ToLower()} {Keywords}{Type} {Name}"; + public virtual string Declaration + { + get + { + return Protection is null ? $"{Keywords}{Type} {Name}" : $"{Protection.Value.ToCSharpString()} {Keywords}{Type} {Name}"; + } + } - public virtual string Build() + public virtual void Build(IndentedTextWriter writer) { - return $"{Declaration}"; + writer.Write(Declaration); } } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenEnum.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenEnum.cs index f0bdb56c..3fb90e3a 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenEnum.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenEnum.cs @@ -1,67 +1,15 @@ -using System.Text; -using Il2CppInterop.StructGenerator.CodeGen.Enums; +using System.CodeDom.Compiler; namespace Il2CppInterop.StructGenerator.CodeGen; -internal class CodeGenEnumElement -{ - public CodeGenEnumElement(string name, string? value = null) - { - Name = name; - Value = value; - } - - public string Name { get; } - public string? Value { get; } - - public string BuildFrom(CodeGenEnum origin) - { - StringBuilder builder = new($"{origin.IndentInner}{Name}"); - if (Value != null) builder.Append($" = {Value}"); - builder.Append(','); - return builder.ToString(); - } - - public static bool operator !=(CodeGenEnumElement lhs, CodeGenEnumElement rhs) - { - return !(lhs == rhs); - } - - public static bool operator ==(CodeGenEnumElement lhs, CodeGenEnumElement rhs) - { - if (lhs.Name != rhs.Name) return false; - return lhs.Value == rhs.Value; - } - - public override bool Equals(object obj) - { - return obj is CodeGenEnumElement element && this == element; - } - - public override int GetHashCode() - { - return HashCode.Combine(Name, Value); - } -} - -internal enum EnumUnderlyingType -{ - Byte = 0, - UShort, - Int, - UInt, - ULong -} - internal class CodeGenEnum : CodeGenElement { - public CodeGenEnum(EnumUnderlyingType underlyingType, ElementProtection protection, string name) : base(protection, + public CodeGenEnum(EnumUnderlyingType underlyingType, ElementProtection? protection, string name) : base(protection, name) { UnderlyingType = underlyingType; } - public override byte IndentAmount { get; set; } = 1; public override string Type => "enum"; public EnumUnderlyingType UnderlyingType { get; set; } @@ -75,18 +23,19 @@ public CodeGenEnum(EnumUnderlyingType underlyingType, ElementProtection protecti _ => throw new Exception("exhausted enum") }; - public List Elements { get; } = new(); + public List Elements { get; } = []; - public override string Build() + public override void Build(IndentedTextWriter writer) { - StringBuilder builder = new(base.Build()); - if (UnderlyingType != EnumUnderlyingType.Int) builder.Append($" : {UnderlyingType.ToString().ToLower()}"); - builder.AppendLine(); - builder.AppendLine($"{Indent}{{"); - foreach (var element in Elements) - builder.AppendLine(element.BuildFrom(this)); - builder.AppendLine($"{Indent}}}"); - return builder.ToString(); + base.Build(writer); + if (UnderlyingType != EnumUnderlyingType.Int) + writer.Write($" : {UnderlyingType.ToCSharpString()}"); + writer.WriteLine(); + using (new CurlyBrackets(writer)) + { + foreach (var element in Elements) + element.Build(writer); + } } public static bool operator !=(CodeGenEnum lhs, CodeGenEnum rhs) @@ -105,7 +54,7 @@ public override string Build() return true; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is CodeGenEnum @enum && this == @enum; } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenEnumElement.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenEnumElement.cs new file mode 100644 index 00000000..405d120c --- /dev/null +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenEnumElement.cs @@ -0,0 +1,14 @@ +using System.CodeDom.Compiler; + +namespace Il2CppInterop.StructGenerator.CodeGen; + +internal readonly record struct CodeGenEnumElement(string Name, string? Value = null) +{ + public void Build(IndentedTextWriter writer) + { + writer.Write(Name); + if (Value != null) + writer.Write($" = {Value}"); + writer.WriteLine(','); + } +} diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenField.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenField.cs index f656abbe..3d48ec6f 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenField.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenField.cs @@ -1,27 +1,26 @@ -using System.Text; -using Il2CppInterop.StructGenerator.CodeGen.Enums; +using System.CodeDom.Compiler; namespace Il2CppInterop.StructGenerator.CodeGen; internal class CodeGenField : CodeGenElement { - public CodeGenField(string type, ElementProtection protection, string name) : base(protection, name) + public CodeGenField(string type, ElementProtection? protection, string name) : base(protection, name) { + ArgumentException.ThrowIfNullOrEmpty(type); FieldType = type; } - public override byte IndentAmount { get; set; } = 1; public override string Type => FieldType; public string? DefaultValue { get; set; } = null; public string FieldType { get; set; } - public override string Build() + public override void Build(IndentedTextWriter writer) { - StringBuilder builder = new($"{base.Build()}"); - if (DefaultValue != null) builder.Append($" = {DefaultValue}"); - builder.Append(';'); - return builder.ToString(); + base.Build(writer); + if (DefaultValue != null) + writer.Write($" = {DefaultValue}"); + writer.WriteLine(';'); } public static bool operator !=(CodeGenField lhs, CodeGenField rhs) @@ -36,7 +35,7 @@ public override string Build() return lhs.DefaultValue == rhs.DefaultValue; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is CodeGenField field && this == field; } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenFile.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenFile.cs index 1a757948..28699522 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenFile.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenFile.cs @@ -1,40 +1,40 @@ -using System.Text; +using System.CodeDom.Compiler; namespace Il2CppInterop.StructGenerator.CodeGen; internal class CodeGenFile { - private byte myMIndentAmount; public string? Namespace { get; set; } - public List Elements { get; } = new(); - public List Usings { get; } = new(); + public List Elements { get; } = []; + public List Usings { get; } = []; - private string Indent => new(' ', myMIndentAmount * 4); - - public string Build() + private void Build(IndentedTextWriter writer) { - StringBuilder builder = new(); + writer.WriteLine("// "); foreach (var @using in Usings) - builder.AppendLine($"using {@using};"); + writer.WriteUsing(@using); if (Namespace != null) { - builder.AppendLine($"namespace {Namespace}"); - builder.AppendLine("{"); - myMIndentAmount += 1; + writer.WriteLine($"namespace {Namespace}"); + writer.WriteLine("{"); + writer.Indent++; } - foreach (var element in Elements) + element.Build(writer); + if (Namespace != null) { - element.IndentAmount = (byte)(myMIndentAmount + 1); - builder.AppendLine($"{Indent}{element.Build()}"); + writer.Indent--; + writer.WriteLine("}"); } - - if (Namespace != null) builder.AppendLine("}"); - return builder.ToString(); } public void WriteTo(string path) { - File.WriteAllText(path, Build()); + using StreamWriter writer = new(path) + { + NewLine = "\n" + }; + using IndentedTextWriter indentedWriter = new(writer); + Build(indentedWriter); } } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenInterface.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenInterface.cs new file mode 100644 index 00000000..ed19ef53 --- /dev/null +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenInterface.cs @@ -0,0 +1,10 @@ +namespace Il2CppInterop.StructGenerator.CodeGen; + +internal sealed class CodeGenInterface : CodeGenType +{ + public CodeGenInterface(ElementProtection? protection, string name) : base(protection, name) + { + } + + public override string Type => "interface"; +} diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenMethod.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenMethod.cs index 163e22fd..34ad4085 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenMethod.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenMethod.cs @@ -1,45 +1,55 @@ -using System.Text; -using Il2CppInterop.StructGenerator.CodeGen.Enums; +using System.CodeDom.Compiler; namespace Il2CppInterop.StructGenerator.CodeGen; internal class CodeGenMethod : CodeGenElement { - public CodeGenMethod(string returnType, ElementProtection protection, string name) : base(protection, name) + public CodeGenMethod(string returnType, ElementProtection? protection, string name) : base(protection, name) { Type = returnType; } - public override byte IndentAmount { get; set; } = 1; public override string Type { get; } - public List Parameters { get; } = new(); - public Action? MethodBodyBuilder { get; set; } = null; + public List Parameters { get; } = []; + public Action? MethodBodyBuilder { get; set; } = null; public string? ImmediateReturn { get; set; } = null; + public bool HasBody { get; set; } = true; - public string BuildBody() + public void BuildBody(IndentedTextWriter writer) { - StringBuilder builder = new(); - if (ImmediateReturn != null) + if (!HasBody) { - if (ImmediateReturn == "") builder.AppendLine(" { }"); - else builder.AppendLine($" => {ImmediateReturn};"); - return builder.ToString(); + writer.WriteLine(";"); + } + else if (ImmediateReturn != null) + { + if (ImmediateReturn == "") + writer.WriteLine(" { }"); + else + writer.WriteLine($" => {ImmediateReturn};"); + } + else + { + writer.WriteLine(); + using (new CurlyBrackets(writer)) + { + MethodBodyBuilder?.Invoke(writer); + } } - - builder.AppendLine(); - builder.AppendLine($"{Indent}{{"); - StringBuilder body = new(); - MethodBodyBuilder?.Invoke(body); - foreach (var line in body.ToString().Split(Environment.NewLine)) builder.AppendLine($"{IndentInner}{line}"); - builder.AppendLine($"{Indent}}}"); - return builder.ToString(); } - public override string Build() + public override void Build(IndentedTextWriter writer) { - StringBuilder builder = new($"{base.Build()}({string.Join(", ", Parameters.Select(x => x.Build()))})"); - builder.Append(BuildBody()); - return builder.ToString(); + base.Build(writer); + writer.Write('('); + for (var i = 0; i < Parameters.Count; i++) + { + Parameters[i].Build(writer); + if (i != Parameters.Count - 1) + writer.Write(", "); + } + writer.Write(')'); + BuildBody(writer); } } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenParameter.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenParameter.cs index 08b0c011..1ca03d79 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenParameter.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenParameter.cs @@ -1,21 +1,13 @@ -using Il2CppInterop.StructGenerator.CodeGen.Enums; +using System.CodeDom.Compiler; namespace Il2CppInterop.StructGenerator.CodeGen; -internal class CodeGenParameter : CodeGenElement +internal readonly record struct CodeGenParameter(string Type, string Name) { - private readonly string myMParameterType; - - public CodeGenParameter(string parameterType, string name) : base(ElementProtection.Private, name) - { - myMParameterType = parameterType; - } - - public override byte IndentAmount { get; set; } = 1; - public override string Type => myMParameterType; - - public override string Build() + public void Build(IndentedTextWriter writer) { - return $"{myMParameterType} {Name}"; + writer.Write(Type); + writer.Write(' '); + writer.Write(Name); } } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenProperty.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenProperty.cs index ba121db9..fcd97adc 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenProperty.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenProperty.cs @@ -1,16 +1,14 @@ -using System.Text; -using Il2CppInterop.StructGenerator.CodeGen.Enums; +using System.CodeDom.Compiler; namespace Il2CppInterop.StructGenerator.CodeGen; internal class CodeGenProperty : CodeGenElement { - public CodeGenProperty(string propertyType, ElementProtection protection, string name) : base(protection, name) + public CodeGenProperty(string propertyType, ElementProtection? protection, string name) : base(protection, name) { Type = propertyType; } - public override byte IndentAmount { get; set; } = 1; public override string Type { get; } public string? ImmediateGet { get; set; } @@ -21,42 +19,59 @@ public CodeGenProperty(string propertyType, ElementProtection protection, string public bool EmptySet { get; set; } public CodeGenMethod? SetMethod { get; set; } - public override string Build() + public bool HasGet => GetMethod != null || ImmediateGet != null || EmptyGet; + public bool HasSet => SetMethod != null || EmptySet; + + /// + /// An optional initializer for the property. If provided, this will be used to initialize the property with a default value. + /// + /// + /// This is only used if either or is . + /// + public string? Initializer { get; set; } + + public override void Build(IndentedTextWriter writer) { - StringBuilder builder = new(base.Build()); - if ((SetMethod == null && GetMethod != null && GetMethod.ImmediateReturn != null) || ImmediateGet != null) + base.Build(writer); + if (ImmediateGet != null) { - if (ImmediateGet != null) - builder.AppendLine($" => {ImmediateGet};"); - else - builder.Append(GetMethod.BuildBody()); - return builder.ToString(); + writer.WriteLine($" => {ImmediateGet};"); } - - if (EmptyGet || EmptySet) + else if (SetMethod == null && GetMethod != null && GetMethod.ImmediateReturn != null) { - builder.Append(" {"); - if (EmptyGet) builder.Append(" get;"); - if (EmptySet) builder.Append(" set;"); - builder.AppendLine(" }"); - return builder.ToString(); + GetMethod.BuildBody(writer); } - - builder.AppendLine(); - builder.AppendLine($"{Indent}{{"); - if (GetMethod != null) + else if (EmptyGet || EmptySet) { - GetMethod.IndentAmount = (byte)(IndentAmount + 1); - builder.Append($"{IndentInner}get{GetMethod.BuildBody()}"); - } + writer.Write(" {"); + if (EmptyGet) writer.Write(" get;"); + if (EmptySet) writer.Write(" set;"); + if (!string.IsNullOrEmpty(Initializer)) + { + writer.WriteLine($" }} = {Initializer};"); + } + else + { - if (SetMethod != null) + writer.WriteLine(" }"); + } + } + else { - SetMethod.IndentAmount = (byte)(IndentAmount + 1); - builder.Append($"{IndentInner}set{SetMethod.BuildBody()}"); + writer.WriteLine(); + using (new CurlyBrackets(writer)) + { + if (GetMethod != null) + { + writer.Write("get"); + GetMethod.BuildBody(writer); + } + if (SetMethod != null) + { + writer.Write("set"); + SetMethod.BuildBody(writer); + } + } } - - builder.AppendLine($"{Indent}}}"); - return builder.ToString(); } } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenStruct.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenStruct.cs index 22ec7ebe..35e6cb71 100644 --- a/Il2CppInterop.StructGenerator/CodeGen/CodeGenStruct.cs +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenStruct.cs @@ -1,10 +1,8 @@ -using Il2CppInterop.StructGenerator.CodeGen.Enums; +namespace Il2CppInterop.StructGenerator.CodeGen; -namespace Il2CppInterop.StructGenerator.CodeGen; - -internal class CodeGenStruct : CodeGenClass +internal sealed class CodeGenStruct : CodeGenType { - public CodeGenStruct(ElementProtection protection, string name) : base(protection, name) + public CodeGenStruct(ElementProtection? protection, string name) : base(protection, name) { } diff --git a/Il2CppInterop.StructGenerator/CodeGen/CodeGenType.cs b/Il2CppInterop.StructGenerator/CodeGen/CodeGenType.cs new file mode 100644 index 00000000..ace9526d --- /dev/null +++ b/Il2CppInterop.StructGenerator/CodeGen/CodeGenType.cs @@ -0,0 +1,80 @@ +using System.CodeDom.Compiler; + +namespace Il2CppInterop.StructGenerator.CodeGen; + +internal abstract class CodeGenType : CodeGenElement +{ + public CodeGenType(ElementProtection? protection, string name) : base(protection, name) + { + } + + public List InterfaceNames { get; } = []; + public List Attributes { get; } = []; + public List Methods { get; } = []; + public List Fields { get; } = []; + public List Properties { get; } = []; + public List NestedElements { get; } = []; + + public override void Build(IndentedTextWriter writer) + { + foreach (var attribute in Attributes) + { + writer.WriteLine($"[{attribute}]"); + } + base.Build(writer); + if (InterfaceNames.Count > 0) + { + writer.Write(" : "); + writer.Write(InterfaceNames[0]); + for (var i = 1; i < InterfaceNames.Count; i++) + { + writer.Write(", "); + writer.Write(InterfaceNames[i]); + } + } + writer.WriteLine(); + using (new CurlyBrackets(writer)) + { + foreach (var method in Methods) + method.Build(writer); + foreach (var field in Fields) + field.Build(writer); + foreach (var property in Properties) + property.Build(writer); + foreach (var nestedElement in NestedElements) + nestedElement.Build(writer); + } + } + + public static bool operator !=(CodeGenType lhs, CodeGenType rhs) + { + return !(lhs == rhs); + } + + public static bool operator ==(CodeGenType lhs, CodeGenType rhs) + { + if (lhs.Fields.Count != rhs.Fields.Count) return false; + if (lhs.NestedElements.Count != rhs.NestedElements.Count) return false; + for (var i = 0; i < lhs.Fields.Count; i++) + if (lhs.Fields[i] != rhs.Fields[i]) + return false; + for (var i = 0; i < lhs.NestedElements.Count; i++) + { + if (lhs.NestedElements[i] is not CodeGenEnum lhsEnum) continue; + if (rhs.NestedElements[i] is not CodeGenEnum rhsEnum) continue; + if (lhsEnum != rhsEnum) return false; + } + + return true; + } + + public override bool Equals(object? obj) + { + return obj is CodeGenType type && this == type; + } + + public override int GetHashCode() + { + return HashCode.Combine(Fields.Count, NestedElements.Count); + } +} diff --git a/Il2CppInterop.StructGenerator/CodeGen/ElementProtection.cs b/Il2CppInterop.StructGenerator/CodeGen/ElementProtection.cs new file mode 100644 index 00000000..0c551cba --- /dev/null +++ b/Il2CppInterop.StructGenerator/CodeGen/ElementProtection.cs @@ -0,0 +1,20 @@ +namespace Il2CppInterop.StructGenerator.CodeGen; + +internal enum ElementProtection +{ + Private, + Protected, + Internal, + Public +} +internal static class ElementProtectionExtensions +{ + public static string ToCSharpString(this ElementProtection value) => value switch + { + ElementProtection.Private => "private", + ElementProtection.Protected => "protected", + ElementProtection.Internal => "internal", + ElementProtection.Public => "public", + _ => throw new InvalidOperationException($"Unknown ElementProtection value: {value}") + }; +} diff --git a/Il2CppInterop.StructGenerator/CodeGen/EnumUnderlyingType.cs b/Il2CppInterop.StructGenerator/CodeGen/EnumUnderlyingType.cs new file mode 100644 index 00000000..1af71e90 --- /dev/null +++ b/Il2CppInterop.StructGenerator/CodeGen/EnumUnderlyingType.cs @@ -0,0 +1,22 @@ +namespace Il2CppInterop.StructGenerator.CodeGen; + +internal enum EnumUnderlyingType +{ + Byte = 0, + UShort, + Int, + UInt, + ULong +} +internal static class EnumUnderlyingTypeExtensions +{ + public static string ToCSharpString(this EnumUnderlyingType value) => value switch + { + EnumUnderlyingType.Byte => "byte", + EnumUnderlyingType.UShort => "ushort", + EnumUnderlyingType.Int => "int", + EnumUnderlyingType.UInt => "uint", + EnumUnderlyingType.ULong => "ulong", + _ => throw new InvalidOperationException($"Unknown EnumUnderlyingType value: {value}") + }; +} diff --git a/Il2CppInterop.StructGenerator/CodeGen/Enums/ElementProtection.cs b/Il2CppInterop.StructGenerator/CodeGen/Enums/ElementProtection.cs deleted file mode 100644 index 27a9ae8b..00000000 --- a/Il2CppInterop.StructGenerator/CodeGen/Enums/ElementProtection.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Il2CppInterop.StructGenerator.CodeGen.Enums; - -internal enum ElementProtection -{ - Private, - Protected, - Internal, - Public -} diff --git a/Il2CppInterop.StructGenerator/Config.cs b/Il2CppInterop.StructGenerator/Config.cs new file mode 100644 index 00000000..a981a558 --- /dev/null +++ b/Il2CppInterop.StructGenerator/Config.cs @@ -0,0 +1,65 @@ +using System.Diagnostics.CodeAnalysis; +using CppAst; +using Il2CppInterop.StructGenerator.TypeGenerators; + +namespace Il2CppInterop.StructGenerator; + +internal static class Config +{ + /// + /// NOTE: Ignores are handled BEFORE renames + /// + public static readonly string[] ClassForcedIgnores = + [ + // Ignore the reflection structs + "Il2CppPropertyInfo", + "Il2CppMethodInfo" + ]; + + public static readonly Dictionary ClassRenames = new() + { + ["TypeInfo"] = "Il2CppClass", + ["FieldInfo"] = "Il2CppFieldInfo", + ["EventInfo"] = "Il2CppEventInfo", + ["ParameterInfo"] = "Il2CppParameterInfo", + ["PropertyInfo"] = "Il2CppPropertyInfo", + ["MethodInfo"] = "Il2CppMethodInfo" + }; + + public static readonly HashSet ClassNames = + [ + "Il2CppAssembly", + "Il2CppAssemblyName", + "Il2CppClass", + "Il2CppEventInfo", + "Il2CppException", + "Il2CppFieldInfo", + "Il2CppImage", + "Il2CppMethodInfo", + "Il2CppParameterInfo", + "Il2CppPropertyInfo", + "Il2CppType", + ]; + + public static VersionSpecificGenerator? TryCreateGenerator(CppClass nativeClass, string metadataSuffix) => nativeClass.Name switch + { + "Il2CppAssembly" => new Il2CppAssemblyGenerator(metadataSuffix, nativeClass), + "Il2CppAssemblyName" => new Il2CppAssemblyNameGenerator(metadataSuffix, nativeClass), + "Il2CppClass" => new Il2CppClassGenerator(metadataSuffix, nativeClass), + "Il2CppEventInfo" => new Il2CppEventInfoGenerator(metadataSuffix, nativeClass), + "Il2CppException" => new Il2CppExceptionGenerator(metadataSuffix, nativeClass), + "Il2CppFieldInfo" => new Il2CppFieldInfoGenerator(metadataSuffix, nativeClass), + "Il2CppImage" => new Il2CppImageGenerator(metadataSuffix, nativeClass), + "Il2CppMethodInfo" => new Il2CppMethodInfoGenerator(metadataSuffix, nativeClass), + "Il2CppParameterInfo" => new Il2CppParameterInfoGenerator(metadataSuffix, nativeClass), + "Il2CppPropertyInfo" => new Il2CppPropertyInfoGenerator(metadataSuffix, nativeClass), + "Il2CppType" => new Il2CppTypeGenerator(metadataSuffix, nativeClass), + _ => null + }; + + public static bool TryCreateGenerator(CppClass nativeClass, string metadataSuffix, [NotNullWhen(true)] out VersionSpecificGenerator? generator) + { + generator = TryCreateGenerator(nativeClass, metadataSuffix); + return generator != null; + } +} diff --git a/Il2CppInterop.StructGenerator/Utilities/ConversionUtils.cs b/Il2CppInterop.StructGenerator/ConversionUtils.cs similarity index 75% rename from Il2CppInterop.StructGenerator/Utilities/ConversionUtils.cs rename to Il2CppInterop.StructGenerator/ConversionUtils.cs index 956a524b..b986ac86 100644 --- a/Il2CppInterop.StructGenerator/Utilities/ConversionUtils.cs +++ b/Il2CppInterop.StructGenerator/ConversionUtils.cs @@ -1,7 +1,6 @@ using CppAst; -using Il2CppInterop.StructGenerator.Resources; -namespace Il2CppInterop.StructGenerator.Utilities; +namespace Il2CppInterop.StructGenerator; internal static class ConversionUtils { @@ -30,6 +29,7 @@ internal static class ConversionUtils ["methodPointerType"] = "void*", ["Il2CppMethodPointer"] = "void*", ["InvokerMethod"] = "void*", + ["Il2CppClass_InitDataUnion"] = "void*", ["TypeIndex"] = "int", ["TypeDefinitionIndex"] = "int", @@ -58,53 +58,51 @@ internal static class ConversionUtils ["InteropDataIndex"] = "int", ["char"] = "byte", + ["int8_t"] = "sbyte", ["uint8_t"] = "byte", + ["int16_t"] = "short", ["uint16_t"] = "ushort", ["int32_t"] = "int", ["uint32_t"] = "uint", - ["unsigned int"] = "uint", + ["int64_t"] = "long", ["uint64_t"] = "ulong", - ["size_t"] = "IntPtr" - }; + ["intptr_t"] = "nint", + ["uintptr_t"] = "nuint", + ["size_t"] = "nint", - private static readonly string[] SInvalidNames = - { - "object", - "class", - "struct", - "base" + ["unsigned int"] = "uint", + ["unsigned long"] = "nuint", + ["unsigned long long"] = "ulong", }; - public static string NormalizeName(string name) - { - return SInvalidNames.Contains(name) ? $"_{name}" : name; - } - public static string CppTypeToCSharpName(CppType type, out bool needsImport) { needsImport = false; if (type is CppArrayType arrayType) { - if (arrayType.SizeOf == 0) return ""; - if (arrayType.SizeOf == 4) return "uint"; - if (arrayType.SizeOf == 8) return "ulong"; - return $"{CppTypeToCSharpName(arrayType.ElementType, out needsImport)}*"; + return arrayType.SizeOf switch + { + 0 => "", + 4 => "uint", + 8 => "ulong", + _ => $"{CppTypeToCSharpName(arrayType.ElementType, out needsImport)}*" + }; } if (type is CppClass fieldType && fieldType.ClassKind == CppClassKind.Union) return "void*"; // Forgive me for my sins var oldTypeName = type.GetDisplayName().Replace("const ", string.Empty); var ptrCount = oldTypeName.Count(x => x == '*'); - if (ptrCount == 0 && Config.ClassToGenerator.ContainsKey(oldTypeName)) + if (ptrCount == 0 && Config.ClassNames.Contains(oldTypeName)) needsImport = true; string ptrs = new('*', ptrCount); - oldTypeName = oldTypeName.Replace("*", string.Empty); - if (STypeRenames.ContainsKey(oldTypeName)) - oldTypeName = STypeRenames[oldTypeName]; + oldTypeName = oldTypeName.Replace("*", string.Empty).Trim(); + if (STypeRenames.TryGetValue(oldTypeName, out var renamed)) + oldTypeName = renamed; return STypeConversions.TryGetValue(oldTypeName, out var converted) - ? $"{converted!}{ptrs}" + ? $"{converted}{ptrs}" : $"{oldTypeName}{ptrs}"; } } diff --git a/Il2CppInterop.StructGenerator/CppTypeExtensions.cs b/Il2CppInterop.StructGenerator/CppTypeExtensions.cs new file mode 100644 index 00000000..3978a416 --- /dev/null +++ b/Il2CppInterop.StructGenerator/CppTypeExtensions.cs @@ -0,0 +1,13 @@ +using CppAst; + +namespace Il2CppInterop.StructGenerator; + +internal static class CppTypeExtensions +{ + public static CppClass? AsClass(this CppType? type) => type switch + { + CppClass cppClass => cppClass, + CppTypedef cppTypedef => cppTypedef.ElementType.AsClass(), + _ => null + }; +} diff --git a/Il2CppInterop.StructGenerator/DictionaryExtensions.cs b/Il2CppInterop.StructGenerator/DictionaryExtensions.cs new file mode 100644 index 00000000..d8da8feb --- /dev/null +++ b/Il2CppInterop.StructGenerator/DictionaryExtensions.cs @@ -0,0 +1,16 @@ +namespace Il2CppInterop.StructGenerator; + +internal static class DictionaryExtensions +{ + public static TValue GetOrAdd(this Dictionary dictionary, TKey key) + where TKey : notnull + where TValue : new() + { + if (!dictionary.TryGetValue(key, out var value)) + { + value = new TValue(); + dictionary[key] = value; + } + return value; + } +} diff --git a/Il2CppInterop.StructGenerator/Il2CppInterop.StructGenerator.csproj b/Il2CppInterop.StructGenerator/Il2CppInterop.StructGenerator.csproj index 0fa1a0cc..80583e0f 100644 --- a/Il2CppInterop.StructGenerator/Il2CppInterop.StructGenerator.csproj +++ b/Il2CppInterop.StructGenerator/Il2CppInterop.StructGenerator.csproj @@ -1,20 +1,17 @@ - + - netstandard2.1 - enable - Il2CppInterop.StructGenerator - false + net10.0 + Exe + $(NETCoreSdkRuntimeIdentifier) - - - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Il2CppInterop.StructGenerator/Il2CppStructWrapperGenerator.cs b/Il2CppInterop.StructGenerator/Il2CppStructWrapperGenerator.cs index e98b023e..68e22d8d 100644 --- a/Il2CppInterop.StructGenerator/Il2CppStructWrapperGenerator.cs +++ b/Il2CppInterop.StructGenerator/Il2CppStructWrapperGenerator.cs @@ -1,95 +1,60 @@ using System.Text.RegularExpressions; +using AssetRipper.Primitives; using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.Resources; -using Il2CppInterop.StructGenerator.Utilities; using Microsoft.Extensions.Logging; namespace Il2CppInterop.StructGenerator; -public record Il2CppStructWrapperGeneratorOptions( - string HeadersDirectory, - string OutputDirectory, - ILogger? Logger -); - -// TODO: Instead expose as source generator (might not be viable since clang is platform-dependent) -public static class Il2CppStructWrapperGenerator +public static partial class Il2CppStructWrapperGenerator { - private static readonly Dictionary> SGenerators = new(); + private static readonly Dictionary>> SClassNameToGenerators = []; internal static ILogger? Logger { get; set; } - private static int GetMetadataVersion(string libil2CppPath) + private static VersionSpecificGenerator? VisitClass(CppClass @class, int metadataVersion, UnityVersion unityVersion) { - var metadataVersion = -1; - foreach (var versionContainer in Config.MetadataVersionContainers) - { - var fullPath = Path.Combine(libil2CppPath, versionContainer); - if (File.Exists(fullPath)) - { - var metadataMatch = Regex.Match(File.ReadAllText(fullPath), - @"\(s_GlobalMetadataHeader->version == ([0-9]+)\);"); + if (Config.ClassForcedIgnores.Contains(@class.Name)) + return null; + if (Config.ClassRenames.TryGetValue(@class.Name, out var rename)) + @class.Name = rename; + if (!Config.ClassNames.Contains(@class.Name)) + return null; - if (metadataMatch.Success) - { - metadataVersion = int.Parse(metadataMatch.Groups[1].Value); - break; - } - } - } + var generatorsByMetadataVersion = SClassNameToGenerators.GetOrAdd(@class.Name); + var generatorsForMetadataVersion = generatorsByMetadataVersion.GetOrAdd(metadataVersion); - return metadataVersion; - } + var existingVersionGeneratorCount = generatorsForMetadataVersion.Count; + if (!Config.TryCreateGenerator(@class, $"{metadataVersion}_{existingVersionGeneratorCount}", out var generator)) + return null; - private static VersionSpecificGenerator? VisitClass(CppClass @class, int metadataVersion, - UnityVersion unityVersion, CppClass[] classes) - { - if (Config.ClassForcedIgnores.Contains(@class.Name)) return null; - if (Config.ClassRenames.TryGetValue(@class.Name, out var rename)) @class.Name = rename; - if (!Config.ClassToGenerator.TryGetValue(@class.Name, out var generatorType)) return null; - if (!typeof(VersionSpecificGenerator).IsAssignableFrom(generatorType)) - throw new Exception($"{@class.Name} has an invalid generator"); - - var existingVersionGeneratorCount = - SGenerators[metadataVersion].Count(x => x.GetType() == generatorType); - var existingGenerators = - SGenerators.Values.SelectMany(x => x).Where(x => x.GetType() == generatorType).ToList(); - var generator = (VersionSpecificGenerator)Activator.CreateInstance(generatorType, - $"{metadataVersion}_{existingVersionGeneratorCount}", @class, - new Func(dependencyName => { return classes.Single(x => x.Name == dependencyName); }))!; - - foreach (var field in generator.NativeStructGenerator.FieldsToImport.ToList()) + foreach ((var field, var cppField) in generator.NativeStructGenerator.FieldsToImport) { - var cppField = generator.NativeStructGenerator.CppClass.Fields.Single(x => x.Name == field.Name); + var typeClass = cppField.Type.AsClass(); - CppClass? typeClass = null; - if (cppField.Type is CppClass) - typeClass = (CppClass)cppField.Type; - if (cppField.Type is CppTypedef typeDef && typeDef.ElementType is CppClass) - typeClass = (CppClass)typeDef.ElementType; if (typeClass != null) { - var gen = VisitClass(typeClass, metadataVersion, unityVersion, classes); - if (gen == null) continue; - field.FieldType = - $"{gen.HandlerGenerator.HandlerClass.Name}.{gen.NativeStructGenerator.NativeStruct.Name}"; - generator.NativeStructGenerator.FieldsToImport.Remove(field); - if (Config.ClassToGenerator.ContainsKey(gen.NativeStructGenerator.CppClass.Name)) - generator.AddExtraUsing( - $"Il2CppInterop.Runtime.Runtime.VersionSpecific.{gen.NativeStructGenerator.CppClass.Name.Replace("Il2Cpp", string.Empty)}"); + var gen = VisitClass(typeClass, metadataVersion, unityVersion); + if (gen == null) + continue; + field.FieldType = $"{gen.HandlerGenerator.HandlerClass.Name}.{gen.NativeStructGenerator.NativeStruct.Name}"; + generator.ExtraUsings.Add(gen.Namespace); } } - + generator.NativeStructGenerator.FieldsToImport.Clear(); generator.SetupElements(); + + var existingGenerators = generatorsByMetadataVersion.SelectMany(x => x.Value); foreach (var existingGenerator in existingGenerators) + { if (existingGenerator.NativeStructGenerator.NativeStruct == generator.NativeStructGenerator.NativeStruct) { existingGenerator.ApplicableVersions.Add(unityVersion); return existingGenerator; } + } generator.ApplicableVersions.Add(unityVersion); - SGenerators[metadataVersion].Add(generator); + generatorsForMetadataVersion.Add(generator); return generator; } @@ -99,130 +64,230 @@ public static void Generate(Il2CppStructWrapperGeneratorOptions options) if (Directory.Exists(options.OutputDirectory)) Directory.Delete(options.OutputDirectory, true); Directory.CreateDirectory(options.OutputDirectory); - foreach (var (libil2CppDir, version) in Directory.GetDirectories(options.HeadersDirectory) - .Select(x => (x, new UnityVersion(Path.GetFileName(x)))).OrderBy(x => x.Item2)) + var previousVersion = UnityVersion.MinVersion; + foreach (var (headerPath, version) in Directory.GetFiles(options.HeadersDirectory, "*.h") + .Select(x => (x, UnityVersion.Parse(Path.GetFileNameWithoutExtension(x)))).OrderBy(x => x.Item2)) { - var classInternalsPath = Path.Combine(libil2CppDir, "il2cpp-class-internals.h"); - if (!File.Exists(classInternalsPath)) - { - Logger?.LogWarning( - "{} doesn't have il2cpp-class-internals.h - falling back to class-internals.h", version); - classInternalsPath = Path.Combine(libil2CppDir, "class-internals.h"); - if (!File.Exists(classInternalsPath)) - { - Logger?.LogWarning("{} doesn't have class-internals.h", version); - continue; - } - } + var headerText = File.ReadAllText(headerPath); - var objectInternalsPath = Path.Combine(libil2CppDir, "il2cpp-object-internals.h"); - if (!File.Exists(objectInternalsPath)) - { - Logger?.LogWarning( - "{} doesn't have il2cpp-object-internals.h - falling back to object-internals.h", version); - objectInternalsPath = Path.Combine(libil2CppDir, "object-internals.h"); - if (!File.Exists(objectInternalsPath)) - { - Logger?.LogWarning("{} doesn't have object-internals.h", version); - continue; - } - } - - var metadataVersion = GetMetadataVersion(libil2CppDir); - if (metadataVersion == -1) + if (!TryGetMetadataVersion(headerText, out var metadataVersion)) { Logger?.LogWarning("{} has an invalid metadata version", version); continue; } - var classInternalsIsTmp = true; - // Graduated top of my class by the way - { - if (!File.Exists($"{classInternalsPath}_backup")) - { - var classInternalsData = File.ReadAllText(classInternalsPath); - // I have to do this because the lib I use doesn't recognize these unions, so I have to name them in the most disgusting way imaginable - classInternalsData = Regex.Replace(classInternalsData, - @"(union.{0,60}?rgctx_data;.*?method(?:Definition|MetadataHandle);.*?});", "$1 runtime_data;", - RegexOptions.Singleline); - classInternalsData = Regex.Replace(classInternalsData, - @"(union.{0,60}?genericMethod;.*?genericContainer(?:Handle)?;.*?});", "$1 generic_data;", - RegexOptions.Singleline); - - File.Move(classInternalsPath, $"{classInternalsPath}_backup"); - File.WriteAllText(classInternalsPath, classInternalsData); - } - } - if (!SGenerators.ContainsKey(metadataVersion)) - SGenerators[metadataVersion] = new List(); - var compilation = CppParser.ParseFiles(new List { objectInternalsPath, classInternalsPath }, + var compilation = CppParser.Parse(ProcessHeaderText(headerText), new CppParserOptions { - ParseAsCpp = true, + ParserKind = CppParserKind.Cpp, AutoSquashTypedef = false, - ParseMacros = true + ParseMacros = false }); Logger?.LogInformation("Parsing {}", version); - var classes = compilation.Classes.ToArray(); - foreach (var @class in classes) VisitClass(@class, metadataVersion, version, classes); - if (classInternalsIsTmp) + if (compilation.HasErrors) + { + Logger?.LogError("Failed to parse {}", version); + continue; + } + // If this is the first version with this major.minor.build, strip the rest of the information. + var actualVersion = version.Equals(previousVersion.Major, previousVersion.Minor, previousVersion.Build) + ? version + : new UnityVersion(version.Major, version.Minor, version.Build); + foreach (var @class in compilation.Classes) { - File.Delete(classInternalsPath); - File.Move($"{classInternalsPath}_backup", classInternalsPath); + VisitClass(@class, metadataVersion, actualVersion); } + previousVersion = version; } Logger?.LogInformation("Building version specific classes"); - // In the eyes of god - I am a disappointment - Dictionary> versionToGeneratorLookup = new(); - foreach (var generator in SGenerators.Values.SelectMany(x => x)) - { - if (!versionToGeneratorLookup.ContainsKey(generator.GetType())) - versionToGeneratorLookup[generator.GetType()] = - new Dictionary(); - - foreach (var version in generator.ApplicableVersions) - versionToGeneratorLookup[generator.GetType()][version] = generator; - } - foreach (var kvp in versionToGeneratorLookup) + foreach ((var className, var generatorsByMetadataVersion) in SClassNameToGenerators) { + Dictionary versionToGeneratorLookup = []; + foreach (var generator in generatorsByMetadataVersion.SelectMany(x => x.Value)) + { + foreach (var version in generator.ApplicableVersions) + { + versionToGeneratorLookup.Add(version, generator); + } + } + VersionSpecificGenerator? last = null; - foreach (var kvp2 in kvp.Value.Where(kvp2 => last is null || last != kvp2.Value)) + List<(UnityVersion Version, VersionSpecificGenerator Generator)> list = []; + foreach ((var version, var generator) in versionToGeneratorLookup.OrderBy(kvp => kvp.Key)) { - kvp2.Value.HandlerGenerator.HandlerClass.Attributes.Add( - $"ApplicableToUnityVersionsSince(\"{kvp2.Key.ToStringShort()}\")"); - last = kvp2.Value; + if (last is not null && last == generator) + continue; + + var versionString = version is { Type: UnityVersionType.Alpha, TypeNumber: 0 } + ? version.ToStringWithoutType() + : version.ToString(); + generator.HandlerGenerator.HandlerClass.Attributes.Add($"ApplicableToUnityVersionsSince(\"{versionString}\")"); + last = generator; + list.Add((version, generator)); } - } - foreach (var generator in SGenerators.Values.SelectMany(x => x)) - { - var generatorOutputDir = - Path.Combine(options.OutputDirectory, - generator.NativeStructGenerator.CppClass.Name.Replace("Il2Cpp", string.Empty)); - if (!Directory.Exists(generatorOutputDir)) - Directory.CreateDirectory(generatorOutputDir); - CodeGenFile file = new() + var generatorOutputDirectory = Path.Join(options.OutputDirectory, className.Replace("Il2Cpp", null)); + foreach (var generator in generatorsByMetadataVersion.SelectMany(x => x.Value)) + { + Directory.CreateDirectory(generatorOutputDirectory); + CodeGenFile file = new() + { + Namespace = generator.Namespace, + Usings = + { + "System.Runtime.InteropServices" + }, + Elements = + { + generator.HandlerGenerator.HandlerClass + } + }; + file.Usings.AddRange(generator.ExtraUsings); + file.WriteTo(Path.Join(generatorOutputDirectory, $"{generator.GeneratorName}_{generator.MetadataSuffix}.cs")); + } + + // Use the first generator for this class to generate the interface file, since all generators for a given class will have the same interface. + var firstGenerator = list[0].Generator; { - Namespace = - $"Il2CppInterop.Runtime.Runtime.VersionSpecific.{generator.NativeStructGenerator.CppClass.Name.Replace("Il2Cpp", string.Empty)}", - Usings = + var interfacesFile = firstGenerator.GenerateInterfacesFile(); + interfacesFile.WriteTo(Path.Join(generatorOutputDirectory, "Interfaces.cs")); + } + + // Generate the UnityVersionHandler partial class for this class + { + var unityVersionHandlerClass = new CodeGenClass(ElementProtection.Public, "UnityVersionHandler") { - "System", - "System.Runtime.InteropServices" - }, - Elements = + IsPartial = true, + IsStatic = true, + Properties = + { + new CodeGenProperty(firstGenerator.HandlerInterface, ElementProtection.Private, $"{firstGenerator.GeneratorName}StructHandler") + { + EmptyGet = true, + EmptySet = true, + IsStatic = true, + Initializer = $"{firstGenerator.HandlerName}.Instance" + } + }, + Methods = + { + new CodeGenMethod("void", ElementProtection.Private, $"Set{firstGenerator.GeneratorName}StructHandler") + { + IsStatic = true, + Parameters = + { + new CodeGenParameter("UnityVersion", "version") + }, + MethodBodyBuilder = writer => + { + if (list.Count == 1) + { + writer.WriteLine($"{firstGenerator.GeneratorName}StructHandler = {firstGenerator.HandlerName}.Instance;"); + return; + } + + for (var i = list.Count - 1; i >= 0; i--) + { + var (version, generator) = list[i]; + if (i == 0) + { + writer.WriteLine("else"); + } + else + { + writer.Write(i == list.Count - 1 ? "if" : "else if"); + writer.WriteLine($" (version >= new UnityVersion({version.Major}, {version.Minor}, {version.Build}, UnityVersionType.{version.Type}, {version.TypeNumber}))"); + } + writer.WriteLine("{"); + writer.Indent++; + writer.WriteLine($"{generator.GeneratorName}StructHandler = {generator.HandlerName}.Instance;"); + writer.Indent--; + writer.WriteLine("}"); + } + } + } + } + }; + var unityVersionHandlerFile = new CodeGenFile() { - generator.HandlerGenerator.HandlerClass - } - }; - foreach (var extraUsing in generator.ExtraUsings) - file.Usings.Add(extraUsing); - file.WriteTo(Path.Combine(generatorOutputDir, - $"{generator.NativeStructGenerator.NativeStruct.Name.Replace("Il2Cpp", string.Empty)}.cs")); + Namespace = "Il2CppInterop.Runtime.Structs", + Usings = + { + "AssetRipper.Primitives", + firstGenerator.Namespace, + }, + Elements = + { + unityVersionHandlerClass + } + }; + unityVersionHandlerFile.WriteTo(Path.Join(generatorOutputDirectory, "UnityVersionHandler.cs")); + } } Logger = null; } + + private static string ProcessHeaderText(string headerText) + { + const string HeaderPrefix = """ + #line 1 "Prefix.h" + typedef int int32_t; + typedef unsigned int uint32_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef char int8_t; + typedef unsigned char uint8_t; + typedef long long int64_t; + typedef unsigned long long uint64_t; + typedef long intptr_t; + typedef unsigned long uintptr_t; + """; + + string processedHeaderText; + if (headerText.Contains("struct ParameterInfo", StringComparison.Ordinal)) + { + processedHeaderText = $""" + {HeaderPrefix} + #line 1 "Header.h" + {headerText} + """; + } + else + { + // ParameterInfo was removed in v27, but we add it back in manually. + processedHeaderText = $$""" + {{HeaderPrefix}} + #line 1 "ParameterInfo.h" + typedef struct Il2CppType Il2CppType; + typedef struct ParameterInfo + { + const Il2CppType* parameter_type; + } ParameterInfo; + #line 1 "Header.h" + {{headerText.Replace("const Il2CppType** parameters;", "const ParameterInfo* parameters;")}} + """; + } + + return processedHeaderText; + } + + private static bool TryGetMetadataVersion(string headerText, out int metadataVersion) + { + var match = MetadataVersionRegex.Match(headerText); + if (match.Success) + { + return int.TryParse(match.Groups[1].Value, out metadataVersion); + } + else + { + metadataVersion = default; + return false; + } + } + + [GeneratedRegex(@"const int METADATA_VERSION = ([0-9]+);")] + private static partial Regex MetadataVersionRegex { get; } } diff --git a/Il2CppInterop.StructGenerator/Il2CppStructWrapperGeneratorOptions.cs b/Il2CppInterop.StructGenerator/Il2CppStructWrapperGeneratorOptions.cs new file mode 100644 index 00000000..ce15f49d --- /dev/null +++ b/Il2CppInterop.StructGenerator/Il2CppStructWrapperGeneratorOptions.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.StructGenerator; + +public record Il2CppStructWrapperGeneratorOptions( + string HeadersDirectory, + string OutputDirectory, + ILogger? Logger +); diff --git a/Il2CppInterop.StructGenerator/NativeStructGenerator.cs b/Il2CppInterop.StructGenerator/NativeStructGenerator.cs index 17d61e98..82271617 100644 --- a/Il2CppInterop.StructGenerator/NativeStructGenerator.cs +++ b/Il2CppInterop.StructGenerator/NativeStructGenerator.cs @@ -1,7 +1,6 @@ -using CppAst; +using System.Diagnostics; +using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; -using Il2CppInterop.StructGenerator.Utilities; namespace Il2CppInterop.StructGenerator; @@ -17,13 +16,16 @@ public NativeStructGenerator(string metadataSuffix, CppClass cppClass) FillStruct(); } - public List FieldsToImport { get; } = new(); + /// + /// Fields whose types might need to be imported from other namespaces + /// + public List<(CodeGenField, CppField)> FieldsToImport { get; } = []; public CodeGenStruct NativeStruct { get; } public CppClass CppClass { get; } private void FillStruct() { - List bitfields = new(); + List bitfields = []; CodeGenEnum? lastBitfield = null; var bitfieldNextBit = 0; @@ -69,15 +71,7 @@ void FinalizeBitfield() bitfieldNextBit = 0; } - foreach (var baseType in CppClass.BaseTypes) - { - var normalizedType = ConversionUtils.CppTypeToCSharpName(baseType.Type, out var needsImport); - CodeGenField field = new(normalizedType, ElementProtection.Public, - ConversionUtils.NormalizeName(baseType.Type.GetDisplayName().Replace("Il2Cpp", string.Empty) - .ToLower())); - if (needsImport) FieldsToImport.Add(field); - NativeStruct.Fields.Add(field); - } + Debug.Assert(CppClass.BaseTypes.Count == 0); foreach (var field in CppClass.Fields) { @@ -92,9 +86,9 @@ void FinalizeBitfield() else { FinalizeBitfield(); - CodeGenField codeGenField = new(normalizedType, ElementProtection.Public, - ConversionUtils.NormalizeName(field.Name)); - if (needsImport) FieldsToImport.Add(codeGenField); + CodeGenField codeGenField = new(normalizedType, ElementProtection.Public, GetFieldName(field)); + if (needsImport) + FieldsToImport.Add((codeGenField, field)); NativeStruct.Fields.Add(codeGenField); } } @@ -103,8 +97,32 @@ void FinalizeBitfield() NativeStruct.NestedElements.AddRange(bitfields); } - public string Build() + private static string GetFieldName(CppField field) { - return NativeStruct.Build(); + var name = field.Name; + if (name is "object" or "class" or "struct" or "base") + { + return $"_{name}"; + } + else if (name.Length == 0) + { + if (field.Parent is CppClass { Name: "Il2CppMethodInfo" } && field.Type is CppClass { ClassKind: CppClassKind.Union } unionType) + { + // IlCppMethodInfo has two unnamed union fields + if (unionType.Fields.Any(f => f.Name is "rgctx_data")) + { + return "runtime_data"; + } + if (unionType.Fields.Any(f => f.Name is "genericMethod")) + { + return "generic_data"; + } + } + throw new ArgumentException("Field has no name and is not part of a known union", nameof(field)); + } + else + { + return name; + } } } diff --git a/Il2CppInterop.StructGenerator/Program.cs b/Il2CppInterop.StructGenerator/Program.cs new file mode 100644 index 00000000..7b759a75 --- /dev/null +++ b/Il2CppInterop.StructGenerator/Program.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.StructGenerator; + +internal static class Program +{ + internal static void Main(string[] args) + { + // Directory that contains il2cpp headers. Directory must contain .h files named with their unity version. + // https://github.com/nneonneo/Il2CppVersions/tree/master/headers + var headersDirectory = args[0]; + + // Directory to write managed struct wrapper sources to + var outputDirectory = args[1]; + + Il2CppStructWrapperGeneratorOptions options = new(headersDirectory, outputDirectory, new ConsoleLogger()); + Il2CppStructWrapperGenerator.Generate(options); + } + + private sealed class ConsoleLogger : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + Console.WriteLine($"[{logLevel}] {formatter.Invoke(state, exception)}"); + } + } +} diff --git a/Il2CppInterop.StructGenerator/Resources/Config.cs b/Il2CppInterop.StructGenerator/Resources/Config.cs deleted file mode 100644 index f89a2ec5..00000000 --- a/Il2CppInterop.StructGenerator/Resources/Config.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Il2CppInterop.StructGenerator.TypeGenerators; - -namespace Il2CppInterop.StructGenerator.Resources; - -internal static class Config -{ - // NOTE: Ignores are handled BEFORE renames - public static readonly string[] ClassForcedIgnores = - { - // Ignore the reflection structs in object-internals.h - "Il2CppPropertyInfo", - "Il2CppMethodInfo" - }; - - public static readonly Dictionary ClassRenames = new() - { - ["TypeInfo"] = "Il2CppClass", - ["FieldInfo"] = "Il2CppFieldInfo", - ["EventInfo"] = "Il2CppEventInfo", - ["PropertyInfo"] = "Il2CppPropertyInfo", - ["MethodInfo"] = "Il2CppMethodInfo" - }; - - public static readonly Dictionary ClassToGenerator = new() - { - ["Il2CppClass"] = typeof(Il2CppClassGenerator), - ["Il2CppType"] = typeof(Il2CppTypeGenerator), - ["Il2CppAssembly"] = typeof(Il2CppAssemblyGenerator), - ["Il2CppAssemblyName"] = typeof(Il2CppAssemblyNameGenerator), - ["Il2CppFieldInfo"] = typeof(Il2CppFieldInfoGenerator), - ["Il2CppImage"] = typeof(Il2CppImageGenerator), - ["Il2CppEventInfo"] = typeof(Il2CppEventInfoGenerator), - ["Il2CppException"] = typeof(Il2CppExceptionGenerator), - ["Il2CppPropertyInfo"] = typeof(Il2CppPropertyInfoGenerator), - ["Il2CppMethodInfo"] = typeof(Il2CppMethodInfoGenerator) - }; - - public static readonly string[] MetadataVersionContainers = - { - Path.Combine("vm", "MetadataCache.cpp"), - Path.Combine("vm", "GlobalMetadata.cpp") - }; -} diff --git a/Il2CppInterop.StructGenerator/StructHandlerGenerator.cs b/Il2CppInterop.StructGenerator/StructHandlerGenerator.cs index aa32cc5b..906f1e06 100644 --- a/Il2CppInterop.StructGenerator/StructHandlerGenerator.cs +++ b/Il2CppInterop.StructGenerator/StructHandlerGenerator.cs @@ -1,14 +1,13 @@ -using System.Text; +using System.CodeDom.Compiler; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator; internal class StructHandlerGenerator { public StructHandlerGenerator(string name, string handlerInterface, string nativeInterface, string nativeStub, - NativeStructGenerator nativeStructGen, List? parameterOverride = null, - Action? extraBodyProvider = null) + NativeStructGenerator nativeStructGen, IEnumerable? parameterOverride = null, + Action? extraBodyProvider = null) { NativeGenerator = nativeStructGen; HandlerClass = new CodeGenClass(ElementProtection.Public, name) @@ -16,23 +15,31 @@ public StructHandlerGenerator(string name, string handlerInterface, string nativ IsUnsafe = true, InterfaceNames = { handlerInterface } }; - HandlerClass.Methods.Add(new CodeGenMethod("int", ElementProtection.Public, "Size") + HandlerClass.Properties.Add(new CodeGenProperty(name, ElementProtection.Public, "Instance") { - ImmediateReturn = $"sizeof({nativeStructGen.NativeStruct.Name})" + EmptyGet = true, + IsStatic = true, + Initializer = "new()" }); + HandlerClass.Properties.Add(new CodeGenProperty("int", ElementProtection.Public, "Size") + { + ImmediateGet = $"sizeof({nativeStructGen.NativeStruct.Name})" + }); + HandlerClass.Methods.Add(new CodeGenConstructor(name, ElementProtection.Private)); CodeGenMethod createNewMethod = new(nativeInterface, ElementProtection.Public, "CreateNewStruct") { - MethodBodyBuilder = builder => + MethodBodyBuilder = writer => { - builder.Append("IntPtr ptr = Marshal.AllocHGlobal("); - if (SizeProviderOverride != null) builder.AppendLine($"{SizeProviderOverride});"); - else builder.AppendLine("Size());"); - builder.AppendLine( + writer.Write("nint ptr = Marshal.AllocHGlobal("); + if (SizeProviderOverride != null) + writer.WriteLine($"{SizeProviderOverride});"); + else + writer.WriteLine("Size);"); + writer.WriteLine( $"{nativeStructGen.NativeStruct.Name}* _ = ({nativeStructGen.NativeStruct.Name}*)ptr;"); - builder.AppendLine("*_ = default;"); - if (extraBodyProvider != null) - extraBodyProvider(builder); - builder.Append("return new NativeStructWrapper(ptr);"); + writer.WriteLine("*_ = default;"); + extraBodyProvider?.Invoke(writer); + writer.WriteLine("return new NativeStructWrapper(ptr);"); } }; if (parameterOverride != null) @@ -41,10 +48,10 @@ public StructHandlerGenerator(string name, string handlerInterface, string nativ HandlerClass.Methods.Add(new CodeGenMethod(nativeInterface, ElementProtection.Public, "Wrap") { Parameters = { new CodeGenParameter($"{nativeStub}*", "ptr") }, - MethodBodyBuilder = builder => + MethodBodyBuilder = writer => { - builder.AppendLine("if (ptr == null) return null;"); - builder.Append("return new NativeStructWrapper((IntPtr)ptr);"); + writer.WriteLine("if (ptr == null) return null;"); + writer.WriteLine("return new NativeStructWrapper((nint)ptr);"); } }); } diff --git a/Il2CppInterop.StructGenerator/StructWrapperGenerator.cs b/Il2CppInterop.StructGenerator/StructWrapperGenerator.cs index dadabb2c..9203d57d 100644 --- a/Il2CppInterop.StructGenerator/StructWrapperGenerator.cs +++ b/Il2CppInterop.StructGenerator/StructWrapperGenerator.cs @@ -1,29 +1,7 @@ using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator; -internal class ByRefWrapper -{ - public ByRefWrapper(string wrappedType, string wrappedName, string[] nativeNames, string? forcedNativeType = null, - bool addNotSupportedIfNotExist = false, bool makeDummyIfNotExist = false) - { - WrappedType = wrappedType; - WrappedName = wrappedName; - NativeNames = nativeNames; - ForcedNativeType = forcedNativeType; - AddNotSupported = addNotSupportedIfNotExist; - MakeDummyIfNotSupported = makeDummyIfNotExist; - } - - public string WrappedType { get; } - public string WrappedName { get; } - public string[] NativeNames { get; } - public string? ForcedNativeType { get; } - public bool AddNotSupported { get; } - public bool MakeDummyIfNotSupported { get; } -} - internal class StructWrapperGenerator { public StructWrapperGenerator(string nativeInterface) @@ -34,11 +12,11 @@ public StructWrapperGenerator(string nativeInterface) }; WrapperClass.Methods.Add(new CodeGenConstructor("NativeStructWrapper", ElementProtection.Public) { - Parameters = { new CodeGenParameter("IntPtr", "ptr") }, + Parameters = { new CodeGenParameter("nint", "ptr") }, ImmediateReturn = "Pointer = ptr" }); - WrapperClass.Properties.Add(new CodeGenProperty("IntPtr", ElementProtection.Public, "Pointer") + WrapperClass.Properties.Add(new CodeGenProperty("nint", ElementProtection.Public, "Pointer") { EmptyGet = true }); diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyGenerator.cs index 0cadaa54..e000b462 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyGenerator.cs @@ -1,27 +1,22 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppAssemblyGenerator : VersionSpecificGenerator { - public Il2CppAssemblyGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppAssemblyGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeAssemblyStructHandler"; - protected override string HandlerInterface => "INativeAssemblyStructHandler"; - protected override string NativeInterface => "INativeAssemblyStruct"; - protected override string NativeStub => "Il2CppAssembly"; + public override string GeneratorName => "Assembly"; - protected override List? WrapperFields => null; - - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "AssemblyPointer") - { ImmediateGet = $"({NativeStub}*)Pointer" }, + { + ImmediateGet = $"({NativeStub}*)Pointer" + }, new CodeGenProperty("INativeAssemblyNameStruct", ElementProtection.Public, "Name") { GetMethod = new CodeGenMethod("INativeAssemblyNameStruct", ElementProtection.Private, "get") @@ -33,12 +28,10 @@ public Il2CppAssemblyGenerator(string metadataSuffix, CppClass nativeClass, ImmediateReturn = $"_->aname = *({GetNativeField("aname")!.Type}*)Name.AssemblyNamePointer" } } - }; - - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("Il2CppImage*", "Image", new[] { "image" }, addNotSupportedIfNotExist: true) - }; + ]; - protected override List? BitfieldAccessors => null; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("Il2CppImage*", "Image", ["image"], addNotSupportedIfNotExist: true) + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyNameGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyNameGenerator.cs index 9c7bba13..10c76d5f 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyNameGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppAssemblyNameGenerator.cs @@ -1,40 +1,33 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppAssemblyNameGenerator : VersionSpecificGenerator { - public Il2CppAssemblyNameGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppAssemblyNameGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeAssemblyNameStructHandler"; - protected override string HandlerInterface => "INativeAssemblyNameStructHandler"; - protected override string NativeInterface => "INativeAssemblyNameStruct"; - protected override string NativeStub => "Il2CppAssemblyName"; + public override string GeneratorName => "AssemblyName"; - protected override List? WrapperFields => null; - - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "AssemblyNamePointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } - }; - - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("IntPtr", "Name", new[] { "name", "nameIndex" }), - new ByRefWrapper("IntPtr", "Culture", new[] { "culture", "cultureIndex" }), - new ByRefWrapper("IntPtr", "PublicKey", new[] { "public_key", "publicKeyIndex" }), - new ByRefWrapper("int", "Major", new[] { "major" }), - new ByRefWrapper("int", "Minor", new[] { "minor" }), - new ByRefWrapper("int", "Build", new[] { "build" }), - new ByRefWrapper("int", "Revision", new[] { "revision" }), - new ByRefWrapper("ulong", "PublicKeyToken", new[] { "public_key_token", "publicKeyToken" }) - }; + { + ImmediateGet = $"({NativeStub}*)Pointer" + } + ]; - protected override List? BitfieldAccessors => null; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("nint", "Name", ["name", "nameIndex"]), + new ByRefWrapper("nint", "Culture", ["culture", "cultureIndex"]), + new ByRefWrapper("nint", "PublicKey", ["public_key", "publicKeyIndex"]), + new ByRefWrapper("int", "Major", ["major"]), + new ByRefWrapper("int", "Minor", ["minor"]), + new ByRefWrapper("int", "Build", ["build"]), + new ByRefWrapper("int", "Revision", ["revision"]), + new ByRefWrapper("ulong", "PublicKeyToken", ["public_key_token", "publicKeyToken"]) + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppClassGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppClassGenerator.cs index f84fe987..8a84932a 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppClassGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppClassGenerator.cs @@ -1,81 +1,84 @@ -using System.Text; +using System.CodeDom.Compiler; using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppClassGenerator : VersionSpecificGenerator { - public Il2CppClassGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppClassGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { - var lastField = NativeStructGenerator.NativeStruct.Fields.Last(); - if (lastField.Name == "vtable") NativeStructGenerator.NativeStruct.Fields.Remove(lastField); + var lastField = NativeStructGenerator.NativeStruct.Fields[^1]; + if (lastField.Name == "vtable") + NativeStructGenerator.NativeStruct.Fields.RemoveAt(NativeStructGenerator.NativeStruct.Fields.Count - 1); + ExtraUsings.Add("Il2CppInterop.Runtime.Structs.VersionSpecific.Type"); } - protected override string HandlerName => "NativeClassStructHandler"; - protected override string HandlerInterface => "INativeClassStructHandler"; - protected override string NativeInterface => "INativeClassStruct"; - protected override string NativeStub => "Il2CppClass"; + public override string GeneratorName => "Class"; - protected override List? CreateNewParameters => - new() { new CodeGenParameter("int", "vTableSlots") }; + protected override IEnumerable? CreateNewParameters => [new CodeGenParameter("int", "vTableSlots")]; - protected override string? SizeOverride => "Size() + sizeof(VirtualInvokeData) * vTableSlots"; - protected override List? WrapperFields => null; - private bool ByValArgIsPointer => GetNativeField("byval_arg")?.FieldType.EndsWith("*") ?? false; - private bool ThisArgIsPointer => GetNativeField("this_arg")?.FieldType.EndsWith("*") ?? false; + protected override string? SizeOverride => "Size + sizeof(VirtualInvokeData) * vTableSlots"; + private bool ByValArgIsPointer => GetNativeField("byval_arg")?.FieldType.EndsWith('*') ?? false; + private bool ThisArgIsPointer => GetNativeField("this_arg")?.FieldType.EndsWith('*') ?? false; - protected override Action? CreateNewExtraBody => builder => + protected override Action? CreateNewExtraBody => writer => { if (GetNativeField("vtable") is not null) { - builder.AppendLine("Marshal.FreeHGlobal(ptr);"); - builder.AppendLine( - $"throw new NotSupportedException(\"The native struct '{NativeStructGenerator.NativeStruct.Name}' has a vtable field which is not currently supported!\");"); + writer.WriteLine("Marshal.FreeHGlobal(ptr);"); + writer.WriteLine( + $"throw new System.NotSupportedException(\"The native struct '{NativeStructGenerator.NativeStruct.Name}' has a vtable field which is not currently supported!\");"); return; } - if (ByValArgIsPointer) builder.AppendLine("_->byval_arg = UnityVersionHandler.NewType().TypePointer;"); - if (ThisArgIsPointer) builder.AppendLine("_->this_arg = UnityVersionHandler.NewType().TypePointer;"); + if (ByValArgIsPointer) + writer.WriteLine("_->byval_arg = UnityVersionHandler.NewType().TypePointer;"); + if (ThisArgIsPointer) + writer.WriteLine("_->this_arg = UnityVersionHandler.NewType().TypePointer;"); }; - protected override List? WrapperProperties + protected override IReadOnlyList WrapperProperties { get { - List properties = new() + CodeGenProperty byvalArg = new("INativeTypeStruct", ElementProtection.Public, "ByValArg") { - new CodeGenProperty("IntPtr", ElementProtection.Public, "VTable") - { - ImmediateGet = $"IntPtr.Add(Pointer, sizeof({NativeStructGenerator.NativeStruct.Name}))" - }, - new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "ClassPointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } + ImmediateGet = "UnityVersionHandler.Wrap(" }; - CodeGenProperty byvalArg = new("INativeTypeStruct", ElementProtection.Public, "ByValArg") - { ImmediateGet = "UnityVersionHandler.Wrap(" }; CodeGenProperty thisArg = new("INativeTypeStruct", ElementProtection.Public, "ThisArg") - { ImmediateGet = "UnityVersionHandler.Wrap(" }; - AddExtraUsing("Il2CppInterop.Runtime.Runtime.VersionSpecific.Type"); - if (!ByValArgIsPointer) byvalArg.ImmediateGet += "(Il2CppTypeStruct*)&"; - if (!ThisArgIsPointer) thisArg.ImmediateGet += "(Il2CppTypeStruct*)&"; + { + ImmediateGet = "UnityVersionHandler.Wrap(" + }; + if (!ByValArgIsPointer) + byvalArg.ImmediateGet += "(Il2CppTypeStruct*)&"; + if (!ThisArgIsPointer) + thisArg.ImmediateGet += "(Il2CppTypeStruct*)&"; byvalArg.ImmediateGet += "_->byval_arg)"; thisArg.ImmediateGet += "_->this_arg)"; - properties.Add(byvalArg); - properties.Add(thisArg); - return properties; + return [ + new CodeGenProperty("VirtualInvokeData*", ElementProtection.Public, "VTable") + { + ImmediateGet = $"(VirtualInvokeData*)nint.Add(Pointer, sizeof({NativeStructGenerator.NativeStruct.Name}))", + IsUnsafe = true + }, + new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "ClassPointer") + { + ImmediateGet = $"({NativeStub}*)Pointer" + }, + byvalArg, + thisArg, + ]; } } - protected override List? BitfieldAccessors => new() - { + protected override IReadOnlyList BitfieldAccessors => + [ new BitfieldAccessor("ValueType", "valuetype", defaultGetter: "ByValArg.ValueType && ThisArg.ValueType", - defaultSetBuilder: builder => + defaultSetBuilder: writer => { - builder.AppendLine("ByValArg.ValueType = value;"); - builder.Append("ThisArg.ValueType = value;"); + writer.WriteLine("ByValArg.ValueType = value;"); + writer.WriteLine("ThisArg.ValueType = value;"); }), new BitfieldAccessor("Initialized", "initialized"), new BitfieldAccessor("EnumType", "enumtype"), @@ -85,32 +88,32 @@ protected override List? WrapperProperties new BitfieldAccessor("HasFinalize", "has_finalize"), new BitfieldAccessor("IsVtableInitialized", "is_vtable_initialized", defaultGetter: "false"), new BitfieldAccessor("InitializedAndNoError", "initialized_and_no_error", defaultGetter: "true") - }; + ]; - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("uint", "InstanceSize", new[] { "instance_size" }), - new ByRefWrapper("ushort", "VtableCount", new[] { "vtable_count" }), - new ByRefWrapper("ushort", "InterfaceCount", new[] { "interfaces_count" }), - new ByRefWrapper("ushort", "InterfaceOffsetsCount", new[] { "interface_offsets_count" }), - new ByRefWrapper("byte", "TypeHierarchyDepth", new[] { "typeHierarchyDepth" }), - new ByRefWrapper("int", "NativeSize", new[] { "native_size" }), - new ByRefWrapper("uint", "ActualSize", new[] { "actualSize" }), - new ByRefWrapper("ushort", "MethodCount", new[] { "method_count" }), - new ByRefWrapper("ushort", "FieldCount", new[] { "field_count" }), - new ByRefWrapper("Il2CppClassAttributes", "Flags", new[] { "flags" }), - new ByRefWrapper("IntPtr", "Name", new[] { "name" }), - new ByRefWrapper("IntPtr", "Namespace", new[] { "namespaze" }), - new ByRefWrapper("Il2CppImage*", "Image", new[] { "image" }), - new ByRefWrapper("Il2CppClass*", "Parent", new[] { "parent" }), - new ByRefWrapper("Il2CppClass*", "ElementClass", new[] { "element_class" }), - new ByRefWrapper("Il2CppClass*", "CastClass", new[] { "castClass" }), - new ByRefWrapper("Il2CppClass*", "DeclaringType", new[] { "declaringType" }), - new ByRefWrapper("Il2CppClass*", "Class", new[] { "klass" }, makeDummyIfNotExist: true), - new ByRefWrapper("Il2CppFieldInfo*", "Fields", new[] { "fields" }), - new ByRefWrapper("Il2CppMethodInfo**", "Methods", new[] { "methods" }), - new ByRefWrapper("Il2CppClass**", "ImplementedInterfaces", new[] { "implementedInterfaces" }), - new ByRefWrapper("Il2CppRuntimeInterfaceOffsetPair*", "InterfaceOffsets", new[] { "interfaceOffsets" }), - new ByRefWrapper("Il2CppClass**", "TypeHierarchy", new[] { "typeHierarchy" }) - }; + protected override IReadOnlyList ByRefWrappers => + [ + new ByRefWrapper("uint", "InstanceSize", ["instance_size"]), + new ByRefWrapper("ushort", "VTableCount", ["vtable_count"]), + new ByRefWrapper("ushort", "InterfaceCount", ["interfaces_count"]), + new ByRefWrapper("ushort", "InterfaceOffsetsCount", ["interface_offsets_count"]), + new ByRefWrapper("byte", "TypeHierarchyDepth", ["typeHierarchyDepth"]), + new ByRefWrapper("int", "NativeSize", ["native_size"]), + new ByRefWrapper("uint", "ActualSize", ["actualSize"]), + new ByRefWrapper("ushort", "MethodCount", ["method_count"]), + new ByRefWrapper("ushort", "FieldCount", ["field_count"]), + new ByRefWrapper("Il2CppClassAttributes", "Flags", ["flags"]), + new ByRefWrapper("nint", "Name", ["name"]), + new ByRefWrapper("nint", "Namespace", ["namespaze"]), + new ByRefWrapper("Il2CppImage*", "Image", ["image"]), + new ByRefWrapper("Il2CppClass*", "Parent", ["parent"]), + new ByRefWrapper("Il2CppClass*", "ElementClass", ["element_class"]), + new ByRefWrapper("Il2CppClass*", "CastClass", ["castClass"]), + new ByRefWrapper("Il2CppClass*", "DeclaringType", ["declaringType"]), + new ByRefWrapper("Il2CppClass*", "Class", ["klass"], makeDummyIfNotExist: true), + new ByRefWrapper("Il2CppFieldInfo*", "Fields", ["fields"]), + new ByRefWrapper("Il2CppMethodInfo**", "Methods", ["methods"]), + new ByRefWrapper("Il2CppClass**", "ImplementedInterfaces", ["implementedInterfaces"]), + new ByRefWrapper("Il2CppRuntimeInterfaceOffsetPair*", "InterfaceOffsets", ["interfaceOffsets"]), + new ByRefWrapper("Il2CppClass**", "TypeHierarchy", ["typeHierarchy"]) + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppEventInfoGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppEventInfoGenerator.cs index ad1c7d97..08d94379 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppEventInfoGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppEventInfoGenerator.cs @@ -1,37 +1,31 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppEventInfoGenerator : VersionSpecificGenerator { - public Il2CppEventInfoGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppEventInfoGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeEventInfoStructHandler"; - protected override string HandlerInterface => "INativeEventInfoStructHandler"; - protected override string NativeInterface => "INativeEventInfoStruct"; - protected override string NativeStub => "Il2CppEventInfo"; - protected override List? WrapperFields => null; + public override string GeneratorName => "EventInfo"; - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "EventInfoPointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } - }; - - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("IntPtr", "Name", new[] { "name" }), - new ByRefWrapper("Il2CppTypeStruct*", "EventType", new[] { "eventType" }), - new ByRefWrapper("Il2CppClass*", "Parent", new[] { "parent" }), - new ByRefWrapper("Il2CppMethodInfo*", "Add", new[] { "add" }), - new ByRefWrapper("Il2CppMethodInfo*", "Remove", new[] { "remove" }), - new ByRefWrapper("Il2CppMethodInfo*", "Raise", new[] { "raise" }) - }; + { + ImmediateGet = $"({NativeStub}*)Pointer" + } + ]; - protected override List? BitfieldAccessors => null; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("nint", "Name", ["name"]), + new ByRefWrapper("Il2CppTypeStruct*", "EventType", ["eventType"]), + new ByRefWrapper("Il2CppClass*", "Parent", ["parent"]), + new ByRefWrapper("Il2CppMethodInfo*", "Add", ["add"]), + new ByRefWrapper("Il2CppMethodInfo*", "Remove", ["remove"]), + new ByRefWrapper("Il2CppMethodInfo*", "Raise", ["raise"]) + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppExceptionGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppExceptionGenerator.cs index 321fd24d..da562551 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppExceptionGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppExceptionGenerator.cs @@ -1,38 +1,31 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppExceptionGenerator : VersionSpecificGenerator { - public Il2CppExceptionGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppExceptionGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeExceptionStructHandler"; - protected override string HandlerInterface => "INativeExceptionStructHandler"; - protected override string NativeInterface => "INativeExceptionStruct"; - protected override string NativeStub => "Il2CppException"; + public override string GeneratorName => "Exception"; - protected override List? WrapperFields => null; - - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "ExceptionPointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } - }; - - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("Il2CppException*", "InnerException", new[] { "inner_ex" }), - new ByRefWrapper("Il2CppString*", "Message", new[] { "message" }), - new ByRefWrapper("Il2CppString*", "HelpLink", new[] { "_helpURL", "help_link" }), - new ByRefWrapper("Il2CppString*", "ClassName", new[] { "className", "class_name" }), - new ByRefWrapper("Il2CppString*", "StackTrace", new[] { "stack_trace" }), - new ByRefWrapper("Il2CppString*", "RemoteStackTrace", new[] { "remote_stack_trace" }) - }; + { + ImmediateGet = $"({NativeStub}*)Pointer" + } + ]; - protected override List? BitfieldAccessors => null; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("Il2CppException*", "InnerException", ["inner_ex"]), + new ByRefWrapper("Il2CppString*", "Message", ["message"]), + new ByRefWrapper("Il2CppString*", "HelpLink", ["_helpURL", "help_link"]), + new ByRefWrapper("Il2CppString*", "ClassName", ["className", "class_name"]), + new ByRefWrapper("Il2CppString*", "StackTrace", ["stack_trace"]), + new ByRefWrapper("Il2CppString*", "RemoteStackTrace", ["remote_stack_trace"]) + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppFieldInfoGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppFieldInfoGenerator.cs index 3805e46e..94e029d0 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppFieldInfoGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppFieldInfoGenerator.cs @@ -1,36 +1,29 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppFieldInfoGenerator : VersionSpecificGenerator { - public Il2CppFieldInfoGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppFieldInfoGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeFieldInfoStructHandler"; - protected override string HandlerInterface => "INativeFieldInfoStructHandler"; - protected override string NativeInterface => "INativeFieldInfoStruct"; - protected override string NativeStub => "Il2CppFieldInfo"; + public override string GeneratorName => "FieldInfo"; - protected override List? WrapperFields => null; - - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "FieldInfoPointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } - }; - - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("IntPtr", "Name", new[] { "name" }), - new ByRefWrapper("Il2CppTypeStruct*", "Type", new[] { "type" }), - new ByRefWrapper("Il2CppClass*", "Parent", new[] { "parent" }), - new ByRefWrapper("int", "Offset", new[] { "offset" }) - }; + { + ImmediateGet = $"({NativeStub}*)Pointer" + } + ]; - protected override List? BitfieldAccessors => null; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("nint", "Name", ["name"]), + new ByRefWrapper("Il2CppTypeStruct*", "Type", ["type"]), + new ByRefWrapper("Il2CppClass*", "Parent", ["parent"]), + new ByRefWrapper("int", "Offset", ["offset"]) + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppImageGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppImageGenerator.cs index d5fe4188..fdef4e93 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppImageGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppImageGenerator.cs @@ -1,50 +1,44 @@ -using System.Text; +using System.CodeDom.Compiler; using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppImageGenerator : VersionSpecificGenerator { - public Il2CppImageGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppImageGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeImageStructHandler"; - protected override string HandlerInterface => "INativeImageStructHandler"; - protected override string NativeInterface => "INativeImageStruct"; - protected override string NativeStub => "Il2CppImage"; + public override string GeneratorName => "Image"; - protected override List? WrapperFields => null; - - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "ImagePointer") - { ImmediateGet = $"({NativeStub}*)Pointer" }, + { + ImmediateGet = $"({NativeStub}*)Pointer" + }, new CodeGenProperty("bool", ElementProtection.Public, "HasNameNoExt") - { ImmediateGet = GetNativeField("nameNoExt") is not null ? "true" : "false" } - }; - - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("Il2CppAssembly*", "Assembly", new[] { "assembly" }, addNotSupportedIfNotExist: true), - new ByRefWrapper("byte", "Dynamic", new[] { "dynamic" }, makeDummyIfNotExist: true), - new ByRefWrapper("IntPtr", "Name", new[] { "name" }), - new ByRefWrapper("IntPtr", "NameNoExt", new[] { "nameNoExt" }, addNotSupportedIfNotExist: true) - }; + { + ImmediateGet = GetNativeField("nameNoExt") is not null ? "true" : "false" + } + ]; - protected override List? BitfieldAccessors => null; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("Il2CppAssembly*", "Assembly", ["assembly"], addNotSupportedIfNotExist: true), + new ByRefWrapper("byte", "Dynamic", ["dynamic"], makeDummyIfNotExist: true), + new ByRefWrapper("nint", "Name", ["name"]), + new ByRefWrapper("nint", "NameNoExt", ["nameNoExt"], addNotSupportedIfNotExist: true) + ]; - protected override Action? CreateNewExtraBody => builder => + protected override Action? CreateNewExtraBody => writer => { if (GetNativeField("metadataHandle") is not null) { - builder.AppendLine( - "Il2CppImageGlobalMetadata* metadata = (Il2CppImageGlobalMetadata*)Marshal.AllocHGlobal(sizeof(Il2CppImageGlobalMetadata));"); - builder.AppendLine("metadata->image = (Il2CppImage*)_;"); - builder.AppendLine("*(Il2CppImageGlobalMetadata**)&_->metadataHandle = metadata;"); + writer.WriteLine("Il2CppImageGlobalMetadata* metadata = (Il2CppImageGlobalMetadata*)Marshal.AllocHGlobal(sizeof(Il2CppImageGlobalMetadata));"); + writer.WriteLine("metadata->image = (Il2CppImage*)_;"); + writer.WriteLine("*(Il2CppImageGlobalMetadata**)&_->metadataHandle = metadata;"); } }; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppMethodInfoGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppMethodInfoGenerator.cs index 9452d783..60f2404b 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppMethodInfoGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppMethodInfoGenerator.cs @@ -1,48 +1,43 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppMethodInfoGenerator : VersionSpecificGenerator { - public Il2CppMethodInfoGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppMethodInfoGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeMethodInfoStructHandler"; - protected override string HandlerInterface => "INativeMethodInfoStructHandler"; - protected override string NativeInterface => "INativeMethodInfoStruct"; - protected override string NativeStub => "Il2CppMethodInfo"; + public override string GeneratorName => "MethodInfo"; - protected override List? WrapperFields => null; - - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "MethodInfoPointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } - }; + { + ImmediateGet = $"({NativeStub}*)Pointer" + } + ]; - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("IntPtr", "Name", new[] { "name" }), - new ByRefWrapper("ushort", "Slot", new[] { "slot" }), - new ByRefWrapper("IntPtr", "MethodPointer", new[] { "methodPointer", "method" }), - new ByRefWrapper("IntPtr", "VirtualMethodPointer", new[] { "virtualMethodPointer", "methodPointer", "method" }), - new ByRefWrapper("Il2CppClass*", "Class", new[] { "declaring_type", "klass" }), - new ByRefWrapper("IntPtr", "InvokerMethod", new[] { "invoker_method" }), - new ByRefWrapper("Il2CppTypeStruct*", "ReturnType", new[] { "return_type" }), - new ByRefWrapper("Il2CppMethodFlags", "Flags", new[] { "flags" }), - new ByRefWrapper("byte", "ParametersCount", new[] { "parameters_count" }), - new ByRefWrapper("Il2CppParameterInfo*", "Parameters", new[] { "parameters" }), - new ByRefWrapper("uint", "Token", new[] { "token" }, addNotSupportedIfNotExist: true) - }; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("nint", "Name", ["name"]), + new ByRefWrapper("ushort", "Slot", ["slot"]), + new ByRefWrapper("nint", "MethodPointer", ["methodPointer", "method"]), + new ByRefWrapper("nint", "VirtualMethodPointer", ["virtualMethodPointer", "methodPointer", "method"]), + new ByRefWrapper("Il2CppClass*", "Class", ["declaring_type", "klass"]), + new ByRefWrapper("nint", "InvokerMethod", ["invoker_method"]), + new ByRefWrapper("Il2CppTypeStruct*", "ReturnType", ["return_type"]), + new ByRefWrapper("Il2CppMethodFlags", "Flags", ["flags"]), + new ByRefWrapper("byte", "ParametersCount", ["parameters_count"]), + new ByRefWrapper("Il2CppParameterInfo*", "Parameters", ["parameters"]), + new ByRefWrapper("uint", "Token", ["token"], addNotSupportedIfNotExist: true) + ]; - protected override List? BitfieldAccessors => new() - { + protected override IReadOnlyList? BitfieldAccessors => + [ new BitfieldAccessor("IsGeneric", "is_generic"), new BitfieldAccessor("IsInflated", "is_inflated"), new BitfieldAccessor("IsMarshalledFromNative", "is_marshaled_from_native", defaultGetter: "false") - }; + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppParameterInfoGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppParameterInfoGenerator.cs new file mode 100644 index 00000000..55227e91 --- /dev/null +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppParameterInfoGenerator.cs @@ -0,0 +1,33 @@ +using CppAst; +using Il2CppInterop.StructGenerator.CodeGen; + +namespace Il2CppInterop.StructGenerator.TypeGenerators; + +internal class Il2CppParameterInfoGenerator : VersionSpecificGenerator +{ + public Il2CppParameterInfoGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) + { + } + + public override string GeneratorName => "ParameterInfo"; + + protected override IReadOnlyList? WrapperProperties => + [ + new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "ParameterInfoPointer") + { + ImmediateGet = $"({NativeStub}*)Pointer" + }, + new CodeGenProperty("bool", ElementProtection.Public, "HasNamePosToken") + { + ImmediateGet = GetNativeField("name") is not null ? "true" : "false" + } + ]; + + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("nint", "Name", ["name"], addNotSupportedIfNotExist: true), + new ByRefWrapper("int", "Position", ["position"], addNotSupportedIfNotExist: true), + new ByRefWrapper("uint", "Token", ["token"], addNotSupportedIfNotExist: true), + new ByRefWrapper("Il2CppTypeStruct*", "ParameterType", ["parameter_type"]), + ]; +} diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppPropertyInfoGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppPropertyInfoGenerator.cs index a34e95bb..c660b1e5 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppPropertyInfoGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppPropertyInfoGenerator.cs @@ -1,37 +1,30 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppPropertyInfoGenerator : VersionSpecificGenerator { - public Il2CppPropertyInfoGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppPropertyInfoGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativePropertyInfoStructHandler"; - protected override string HandlerInterface => "INativePropertyInfoStructHandler"; - protected override string NativeInterface => "INativePropertyInfoStruct"; - protected override string NativeStub => "Il2CppPropertyInfo"; + public override string GeneratorName => "PropertyInfo"; - protected override List? WrapperFields => null; - - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "PropertyInfoPointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } - }; - - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("IntPtr", "Name", new[] { "name" }), - new ByRefWrapper("Il2CppClass*", "Parent", new[] { "parent" }), - new ByRefWrapper("Il2CppMethodInfo*", "Get", new[] { "get" }), - new ByRefWrapper("Il2CppMethodInfo*", "Set", new[] { "set" }), - new ByRefWrapper("uint", "Attrs", new[] { "attrs" }) - }; + { + ImmediateGet = $"({NativeStub}*)Pointer" + } + ]; - protected override List? BitfieldAccessors => null; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("nint", "Name", ["name"]), + new ByRefWrapper("Il2CppClass*", "Parent", ["parent"]), + new ByRefWrapper("Il2CppMethodInfo*", "Get", ["get"]), + new ByRefWrapper("Il2CppMethodInfo*", "Set", ["set"]), + new ByRefWrapper("uint", "Attrs", ["attrs"]) + ]; } diff --git a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppTypeGenerator.cs b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppTypeGenerator.cs index 1ba98e55..f36aa38a 100644 --- a/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppTypeGenerator.cs +++ b/Il2CppInterop.StructGenerator/TypeGenerators/Il2CppTypeGenerator.cs @@ -1,40 +1,37 @@ using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; namespace Il2CppInterop.StructGenerator.TypeGenerators; internal class Il2CppTypeGenerator : VersionSpecificGenerator { - public Il2CppTypeGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) : base(metadataSuffix, nativeClass, dependencyResolver) + public Il2CppTypeGenerator(string metadataSuffix, CppClass nativeClass) : base(metadataSuffix, nativeClass) { } - protected override string HandlerName => "NativeTypeStructHandler"; - protected override string HandlerInterface => "INativeTypeStructHandler"; - protected override string NativeInterface => "INativeTypeStruct"; + public override string GeneratorName => "Type"; protected override string NativeStub => "Il2CppTypeStruct"; - protected override List? WrapperFields => null; - protected override List? WrapperProperties => new() - { + protected override IReadOnlyList? WrapperProperties => + [ new CodeGenProperty($"{NativeStub}*", ElementProtection.Public, "TypePointer") - { ImmediateGet = $"({NativeStub}*)Pointer" } - }; + { + ImmediateGet = $"({NativeStub}*)Pointer" + } + ]; - protected override List? ByRefWrappers => new() - { - new ByRefWrapper("IntPtr", "Data", new[] { "data" }), - new ByRefWrapper("ushort", "Attrs", new[] { "attrs" }), - new ByRefWrapper("Il2CppTypeEnum", "Type", new[] { "type" }) - }; + protected override IReadOnlyList? ByRefWrappers => + [ + new ByRefWrapper("nint", "Data", ["data"]), + new ByRefWrapper("ushort", "Attrs", ["attrs"]), + new ByRefWrapper("Il2CppTypeEnum", "Type", ["type"]) + ]; - protected override List? BitfieldAccessors => new() - { + protected override IReadOnlyList? BitfieldAccessors => + [ new BitfieldAccessor("ByRef", "byref"), new BitfieldAccessor("Pinned", "pinned"), // maybe throw if not exist new BitfieldAccessor("ValueType", "valuetype", defaultGetter: "false") - }; + ]; } diff --git a/Il2CppInterop.StructGenerator/Usings.cs b/Il2CppInterop.StructGenerator/Usings.cs new file mode 100644 index 00000000..b00d5055 --- /dev/null +++ b/Il2CppInterop.StructGenerator/Usings.cs @@ -0,0 +1 @@ +global using AssetRipper.Text.SourceGeneration; diff --git a/Il2CppInterop.StructGenerator/Utilities/UnityVersion.cs b/Il2CppInterop.StructGenerator/Utilities/UnityVersion.cs deleted file mode 100644 index 797d6fd1..00000000 --- a/Il2CppInterop.StructGenerator/Utilities/UnityVersion.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Il2CppInterop.StructGenerator.Utilities; - -internal struct UnityVersion : IComparable, IComparable -{ - public int Major => unchecked((int)((myMBlob >> 48) & 0xFFFFUL)); - public int Minor => unchecked((int)((myMBlob >> 40) & 0xFFUL)); - public int Build => unchecked((int)((myMBlob >> 32) & 0xFFUL)); - public char Type => (char)unchecked((int)((myMBlob >> 24) & 0xFFUL)); - public int Revision => unchecked((int)((myMBlob >> 16) & 0xFFUL)); - - public UnityVersion(string versionString) - { - var match = Regex.Match(versionString, @"([0-9]+).([0-9]+).([0-9]+)(?:([abcfpx])([0-9]+))?"); - if (!match.Success) throw new Exception("invalid unity version string"); - var major = int.Parse(match.Groups[1].Value); - var minor = int.Parse(match.Groups[2].Value); - var build = int.Parse(match.Groups[3].Value); - var type = match.Groups[4].Success ? match.Groups[4].Value[0] : 'f'; - var revision = match.Groups[5].Success ? int.Parse(match.Groups[5].Value) : 1; - myMBlob = MakeBlob(major, minor, build, type, revision); - } - - private static ulong MakeBlob(int major, int minor, int build, char type, int revision) - { - var blob = ((ulong)(major & 0xFFFF) << 48) | ((ulong)(minor & 0xFF) << 40) | ((ulong)(build & 0xFF) << 32) - | ((ulong)(type & 0xFF) << 24) | ((ulong)(revision & 0xFF) << 16); - return blob; - } - - public int CompareTo(object? obj) - { - if (obj is UnityVersion version) - return CompareTo(version); - return 1; - } - - public int CompareTo(UnityVersion other) - { - if (this > other) - return 1; - if (this < other) - return -1; - return 0; - } - - public static bool operator !=(UnityVersion lhs, UnityVersion rhs) - { - return lhs.myMBlob != rhs.myMBlob; - } - - public static bool operator ==(UnityVersion lhs, UnityVersion rhs) - { - return lhs.myMBlob == rhs.myMBlob; - } - - public static bool operator <(UnityVersion lhs, UnityVersion rhs) - { - return lhs.myMBlob < rhs.myMBlob; - } - - public static bool operator <=(UnityVersion lhs, UnityVersion rhs) - { - return lhs.myMBlob <= rhs.myMBlob; - } - - public static bool operator >(UnityVersion lhs, UnityVersion rhs) - { - return lhs.myMBlob > rhs.myMBlob; - } - - public static bool operator >=(UnityVersion lhs, UnityVersion rhs) - { - return lhs.myMBlob >= rhs.myMBlob; - } - - public string ToStringShort() - { - return $"{Major}.{Minor}.{Build}"; - } - - public override string ToString() - { - return $"{Major}.{Minor}.{Build}{Type}{Revision}"; - } - - private readonly ulong myMBlob; - - public override readonly bool Equals(object obj) - { - return obj is UnityVersion version && this == version; - } - - public override readonly int GetHashCode() => myMBlob.GetHashCode(); -} diff --git a/Il2CppInterop.StructGenerator/VersionSpecificGenerator.cs b/Il2CppInterop.StructGenerator/VersionSpecificGenerator.cs index 51a6334d..94ed970e 100644 --- a/Il2CppInterop.StructGenerator/VersionSpecificGenerator.cs +++ b/Il2CppInterop.StructGenerator/VersionSpecificGenerator.cs @@ -1,53 +1,23 @@ -using System.Text; +using System.CodeDom.Compiler; +using AssetRipper.Primitives; using CppAst; using Il2CppInterop.StructGenerator.CodeGen; -using Il2CppInterop.StructGenerator.CodeGen.Enums; -using Il2CppInterop.StructGenerator.Utilities; namespace Il2CppInterop.StructGenerator; -internal class BitfieldAccessor -{ - public BitfieldAccessor(string accessorName, string elementName, string accessorType = "bool", - bool generateIfNotPresent = true, string? defaultGetter = "", Action? defaultGetBuilder = null, - Action? defaultSetBuilder = null) - { - AccessorName = accessorName; - ElementName = elementName; - AccessorType = accessorType; - GenerateIfNotPresent = generateIfNotPresent; - DefaultImmediateGetter = defaultGetter; - DefaultGetBuilder = defaultGetBuilder; - DefaultSetBuilder = defaultSetBuilder; - } - - public string AccessorName { get; } - public string ElementName { get; } - public string AccessorType { get; } - public bool GenerateIfNotPresent { get; } - public string? DefaultImmediateGetter { get; } - public Action? DefaultGetBuilder { get; } - public Action? DefaultSetBuilder { get; } -} - internal abstract class VersionSpecificGenerator { - public VersionSpecificGenerator(string metadataSuffix, CppClass nativeClass, - Func? dependencyResolver = null) + public VersionSpecificGenerator(string metadataSuffix, CppClass nativeClass) { MetadataSuffix = metadataSuffix; NativeStructGenerator = new NativeStructGenerator(MetadataSuffix, nativeClass); - HandlerGenerator = new StructHandlerGenerator($"{HandlerName}_{MetadataSuffix}", HandlerInterface, + HandlerGenerator = new StructHandlerGenerator(HandlerName, HandlerInterface, NativeInterface, NativeStub, NativeStructGenerator, CreateNewParameters, CreateNewExtraBody) { SizeProviderOverride = SizeOverride }; HandlerGenerator.HandlerClass.NestedElements.Add(NativeStructGenerator.NativeStruct); - if (DependsOnClasses != null && dependencyResolver != null) - foreach (var dependency in DependsOnClasses) - HandlerGenerator.HandlerClass.NestedElements.Add( - new NativeStructGenerator(MetadataSuffix, dependencyResolver(dependency)).NativeStruct); WrapperGenerator = new StructWrapperGenerator(NativeInterface); foreach (var bitfieldField in NativeStructGenerator.NativeStruct.Fields.Where(x => @@ -62,42 +32,37 @@ public VersionSpecificGenerator(string metadataSuffix, CppClass nativeClass, HandlerGenerator.HandlerClass.NestedElements.Add(WrapperGenerator.WrapperClass); } - protected abstract string HandlerName { get; } - protected abstract string HandlerInterface { get; } - protected abstract string NativeInterface { get; } - protected abstract string NativeStub { get; } + public string Namespace => $"Il2CppInterop.Runtime.Structs.VersionSpecific.{GeneratorName}"; + public abstract string GeneratorName { get; } + public string HandlerName => $"Native{GeneratorName}StructHandler_{MetadataSuffix}"; + public string HandlerInterface => $"INative{GeneratorName}StructHandler"; + public string NativeInterface => $"INative{GeneratorName}Struct"; + protected virtual string NativeStub => $"Il2Cpp{GeneratorName}"; - protected virtual string[]? DependsOnClasses { get; } - protected virtual List? CreateNewParameters => null; + protected virtual IEnumerable? CreateNewParameters => null; protected virtual string? SizeOverride => null; - protected abstract List? WrapperFields { get; } - protected abstract List? WrapperProperties { get; } - protected abstract List? ByRefWrappers { get; } - protected abstract List? BitfieldAccessors { get; } + protected virtual IEnumerable? WrapperFields => null; + protected virtual IEnumerable? WrapperProperties => null; + protected virtual IEnumerable? ByRefWrappers => null; + protected virtual IEnumerable? BitfieldAccessors => null; - protected virtual Action? CreateNewExtraBody => null; + protected virtual Action? CreateNewExtraBody => null; public string MetadataSuffix { get; } public NativeStructGenerator NativeStructGenerator { get; } public StructHandlerGenerator HandlerGenerator { get; } public StructWrapperGenerator WrapperGenerator { get; } - public HashSet ApplicableVersions { get; } = new(); - public HashSet ExtraUsings { get; } = new(); - - public void AddExtraUsing(string @using) - { - if (ExtraUsings.Contains(@using)) return; - ExtraUsings.Add(@using); - } + public HashSet ApplicableVersions { get; } = []; + public HashSet ExtraUsings { get; } = []; public void SetupElements() { - List properties = new() - { + List properties = + [ new CodeGenProperty($"{NativeStructGenerator.NativeStruct.Name}*", ElementProtection.Private, "_") { ImmediateGet = $"({NativeStructGenerator.NativeStruct.Name}*)Pointer" } - }; + ]; var wrapperProperties = WrapperProperties; if (wrapperProperties != null) properties.AddRange(wrapperProperties); WrapperGenerator.ImplementProperties(properties); @@ -126,7 +91,7 @@ public void SetupElements() { if (wrapper.AddNotSupported) { - property.ImmediateGet = "throw new NotSupportedException()"; + property.ImmediateGet = "throw new System.NotSupportedException()"; WrapperGenerator.WrapperClass.Properties.Add(property); } @@ -227,8 +192,72 @@ public void SetupElements() return NativeStructGenerator.NativeStruct.Fields.SingleOrDefault(x => x.Name == name); } - public virtual string Build() + public CodeGenFile GenerateInterfacesFile() { - return HandlerGenerator.HandlerClass.Build(); + var createNewStructMethod = new CodeGenMethod(NativeInterface, null, "CreateNewStruct") + { + HasBody = false + }; + createNewStructMethod.Parameters.AddRange(CreateNewParameters ?? []); + var handlerInterface = new CodeGenInterface(ElementProtection.Public, HandlerInterface) + { + InterfaceNames = { "INativeStructHandler" }, + Methods = + { + createNewStructMethod, + new CodeGenMethod(NativeInterface, null, "Wrap") + { + IsUnsafe = true, + HasBody = false, + Parameters = + { + new CodeGenParameter($"{NativeStub}*", "pointer"), + } + }, + } + }; + var nativeInterface = new CodeGenInterface(ElementProtection.Public, NativeInterface) + { + InterfaceNames = { "INativeStruct" }, + }; + foreach (var property in WrapperProperties ?? []) + { + if (property.Protection != ElementProtection.Public) + continue; + + nativeInterface.Properties.Add(new CodeGenProperty(property.Type, null, property.Name) + { + EmptyGet = property.HasGet, + EmptySet = property.HasSet, + IsUnsafe = property.IsUnsafe || property.Type.Contains('*'), + }); + } + foreach (var wrapper in ByRefWrappers ?? []) + { + nativeInterface.Properties.Add(new CodeGenProperty($"ref {wrapper.WrappedType}", null, wrapper.WrappedName) + { + IsUnsafe = wrapper.WrappedType.Contains('*'), + EmptyGet = true, + }); + } + foreach (var accessor in BitfieldAccessors ?? []) + { + nativeInterface.Properties.Add(new CodeGenProperty(accessor.AccessorType, null, accessor.AccessorName) + { + EmptyGet = true, + EmptySet = true, + }); + } + var file = new CodeGenFile() + { + Namespace = Namespace, + Elements = + { + handlerInterface, + nativeInterface, + } + }; + file.Usings.AddRange(ExtraUsings); + return file; } } diff --git a/Il2CppInterop.sln b/Il2CppInterop.sln index 80d120c6..be78101d 100644 --- a/Il2CppInterop.sln +++ b/Il2CppInterop.sln @@ -1,82 +1,102 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31402.337 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.Generator", "Il2CppInterop.Generator\Il2CppInterop.Generator.csproj", "{7C3FD45B-A563-47AF-90DF-8B051A8C33A0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.Runtime", "Il2CppInterop.Runtime\Il2CppInterop.Runtime.csproj", "{F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{463B7E3B-94E8-4EEB-B54A-EF15AD9C7C7E}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - README.md = README.md - Directory.Build.props = Directory.Build.props - build.cake = build.cake - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{99E71726-78FB-4376-89DA-90E5C08F5CD4}" - ProjectSection(SolutionItems) = preProject - Documentation\Class-Injection.md = Documentation\Class-Injection.md - Documentation\Command-Line-Usage.md = Documentation\Command-Line-Usage.md - Documentation\Common-Problems.md = Documentation\Common-Problems.md - Documentation\Injected-Components-In-Asset-Bundles.md = Documentation\Injected-Components-In-Asset-Bundles.md - Documentation\Implementing-Interfaces.md = Documentation\Implementing-Interfaces.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.CLI", "Il2CppInterop.CLI\Il2CppInterop.CLI.csproj", "{F5AA88D1-5E62-46B8-A11C-3FAC40C25600}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{771D8BE3-C373-4754-94EA-4A68B117BAEF}" - ProjectSection(SolutionItems) = preProject - .github\workflows\dotnet.yml = .github\workflows\dotnet.yml - .github\workflows\format_check.yml = .github\workflows\format_check.yml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.StructGenerator", "Il2CppInterop.StructGenerator\Il2CppInterop.StructGenerator.csproj", "{DE781BD4-650F-4ED8-B615-5CB36E6D476A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.Common", "Il2CppInterop.Common\Il2CppInterop.Common.csproj", "{E7D3A81B-11CD-402C-A447-015B00DCA3FD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.HarmonySupport", "Il2CppInterop.HarmonySupport\Il2CppInterop.HarmonySupport.csproj", "{EBC23884-3417-4F24-8623-E913C5151CC3}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Release|Any CPU.Build.0 = Release|Any CPU - {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Release|Any CPU.Build.0 = Release|Any CPU - {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Release|Any CPU.Build.0 = Release|Any CPU - {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Release|Any CPU.Build.0 = Release|Any CPU - {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Release|Any CPU.Build.0 = Release|Any CPU - {EBC23884-3417-4F24-8623-E913C5151CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EBC23884-3417-4F24-8623-E913C5151CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EBC23884-3417-4F24-8623-E913C5151CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EBC23884-3417-4F24-8623-E913C5151CC3}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {99E71726-78FB-4376-89DA-90E5C08F5CD4} = {463B7E3B-94E8-4EEB-B54A-EF15AD9C7C7E} - {771D8BE3-C373-4754-94EA-4A68B117BAEF} = {463B7E3B-94E8-4EEB-B54A-EF15AD9C7C7E} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CA7C3EAB-EDE4-4DB0-BEEF-9E74940B2978} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11109.219 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.Generator", "Il2CppInterop.Generator\Il2CppInterop.Generator.csproj", "{7C3FD45B-A563-47AF-90DF-8B051A8C33A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppInterop.Runtime", "Il2CppInterop.Runtime\Il2CppInterop.Runtime.csproj", "{F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{463B7E3B-94E8-4EEB-B54A-EF15AD9C7C7E}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + build.cake = build.cake + Directory.Build.props = Directory.Build.props + nuget.config = nuget.config + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{99E71726-78FB-4376-89DA-90E5C08F5CD4}" + ProjectSection(SolutionItems) = preProject + Documentation\Class-Injection.md = Documentation\Class-Injection.md + Documentation\Command-Line-Usage.md = Documentation\Command-Line-Usage.md + Documentation\Common-Problems.md = Documentation\Common-Problems.md + Documentation\Implementing-Interfaces.md = Documentation\Implementing-Interfaces.md + Documentation\Injected-Components-In-Asset-Bundles.md = Documentation\Injected-Components-In-Asset-Bundles.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.CLI", "Il2CppInterop.CLI\Il2CppInterop.CLI.csproj", "{F5AA88D1-5E62-46B8-A11C-3FAC40C25600}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{771D8BE3-C373-4754-94EA-4A68B117BAEF}" + ProjectSection(SolutionItems) = preProject + .github\workflows\dotnet.yml = .github\workflows\dotnet.yml + .github\workflows\format_check.yml = .github\workflows\format_check.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.StructGenerator", "Il2CppInterop.StructGenerator\Il2CppInterop.StructGenerator.csproj", "{DE781BD4-650F-4ED8-B615-5CB36E6D476A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.Common", "Il2CppInterop.Common\Il2CppInterop.Common.csproj", "{E7D3A81B-11CD-402C-A447-015B00DCA3FD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.HarmonySupport", "Il2CppInterop.HarmonySupport\Il2CppInterop.HarmonySupport.csproj", "{EBC23884-3417-4F24-8623-E913C5151CC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2Cppmscorlib", "Il2Cppmscorlib\Il2Cppmscorlib.csproj", "{78406496-DB4C-4012-B5DE-D7CDF27652B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.SourceGenerator", "Il2CppInterop.SourceGenerator\Il2CppInterop.SourceGenerator.csproj", "{3110C3CE-A58E-4B3A-8BE2-455AFD69606E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInterop.SourceGenerator.Tests", "Il2CppInterop.SourceGenerator.Tests\Il2CppInterop.SourceGenerator.Tests.csproj", "{03100003-C74A-4ABD-AB2E-EB0395ADA031}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C3FD45B-A563-47AF-90DF-8B051A8C33A0}.Release|Any CPU.Build.0 = Release|Any CPU + {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6405B5F-9162-49EA-9C8A-CE1AA0916CF3}.Release|Any CPU.Build.0 = Release|Any CPU + {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5AA88D1-5E62-46B8-A11C-3FAC40C25600}.Release|Any CPU.Build.0 = Release|Any CPU + {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE781BD4-650F-4ED8-B615-5CB36E6D476A}.Release|Any CPU.Build.0 = Release|Any CPU + {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7D3A81B-11CD-402C-A447-015B00DCA3FD}.Release|Any CPU.Build.0 = Release|Any CPU + {EBC23884-3417-4F24-8623-E913C5151CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBC23884-3417-4F24-8623-E913C5151CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBC23884-3417-4F24-8623-E913C5151CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBC23884-3417-4F24-8623-E913C5151CC3}.Release|Any CPU.Build.0 = Release|Any CPU + {78406496-DB4C-4012-B5DE-D7CDF27652B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78406496-DB4C-4012-B5DE-D7CDF27652B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78406496-DB4C-4012-B5DE-D7CDF27652B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78406496-DB4C-4012-B5DE-D7CDF27652B8}.Release|Any CPU.Build.0 = Release|Any CPU + {3110C3CE-A58E-4B3A-8BE2-455AFD69606E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3110C3CE-A58E-4B3A-8BE2-455AFD69606E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3110C3CE-A58E-4B3A-8BE2-455AFD69606E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3110C3CE-A58E-4B3A-8BE2-455AFD69606E}.Release|Any CPU.Build.0 = Release|Any CPU + {03100003-C74A-4ABD-AB2E-EB0395ADA031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03100003-C74A-4ABD-AB2E-EB0395ADA031}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03100003-C74A-4ABD-AB2E-EB0395ADA031}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03100003-C74A-4ABD-AB2E-EB0395ADA031}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {99E71726-78FB-4376-89DA-90E5C08F5CD4} = {463B7E3B-94E8-4EEB-B54A-EF15AD9C7C7E} + {771D8BE3-C373-4754-94EA-4A68B117BAEF} = {463B7E3B-94E8-4EEB-B54A-EF15AD9C7C7E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CA7C3EAB-EDE4-4DB0-BEEF-9E74940B2978} + EndGlobalSection +EndGlobal diff --git a/Il2Cppmscorlib/Array.cs b/Il2Cppmscorlib/Array.cs new file mode 100644 index 00000000..373b72ee --- /dev/null +++ b/Il2Cppmscorlib/Array.cs @@ -0,0 +1,32 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public abstract class Array : Object +{ + protected Array(ObjectPointer ptr) : base(ptr) + { + } + + public Int32 Length => default; + + public Int32 GetLowerBound(Int32 dimension) + { + return default; + } + + public Int32 GetUpperBound(Int32 dimension) + { + return default; + } + + public Int32 GetLength(Int32 dimension) + { + return default; + } + + public Int32 GetRank() + { + return default; + } +} diff --git a/Il2Cppmscorlib/Boolean.cs b/Il2Cppmscorlib/Boolean.cs new file mode 100644 index 00000000..8866c95c --- /dev/null +++ b/Il2Cppmscorlib/Boolean.cs @@ -0,0 +1,15 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public struct Boolean : IIl2CppType +{ + static int IIl2CppType.Size => throw null; + readonly nint IIl2CppType.ObjectClass => throw null; + static Boolean IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw null; + static void IIl2CppType.WriteToSpan(Boolean value, System.Span span) => throw null; + ObjectPointer IIl2CppType.BoxNative() => throw new System.NotImplementedException(); + + public static implicit operator bool(Boolean value) => throw null; + public static implicit operator Boolean(bool value) => throw null; +} diff --git a/Il2Cppmscorlib/Delegate.cs b/Il2Cppmscorlib/Delegate.cs new file mode 100644 index 00000000..d08cc0d7 --- /dev/null +++ b/Il2Cppmscorlib/Delegate.cs @@ -0,0 +1,30 @@ +using Il2CppSystem.Reflection; + +namespace Il2CppSystem; + +public abstract class Delegate : Object +{ + public IntPtr method_ptr { get; set; } + + public IntPtr invoke_impl { get; set; } + + public IObject m_target { get; set; } + + public IntPtr method { get; set; } + + public IntPtr delegate_trampoline { get; set; } + + public IntPtr extra_arg { get; set; } + + public IntPtr method_code { get; set; } + + public IntPtr interp_method { get; set; } + + public IntPtr interp_invoke_impl { get; set; } + + public MethodInfo method_info { get; set; } + + public MethodInfo original_method_info { get; set; } + + public Boolean method_is_virtual { get; set; } +} diff --git a/Il2Cppmscorlib/Enum.cs b/Il2Cppmscorlib/Enum.cs new file mode 100644 index 00000000..984a5f2d --- /dev/null +++ b/Il2Cppmscorlib/Enum.cs @@ -0,0 +1,11 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public abstract class Enum : ValueType, IIl2CppType +{ + static int IIl2CppType.Size => throw new System.NotImplementedException(); + + static Enum IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw new System.NotImplementedException(); + static void IIl2CppType.WriteToSpan(Enum value, System.Span span) => throw new System.NotImplementedException(); +} diff --git a/Il2Cppmscorlib/Exception.cs b/Il2Cppmscorlib/Exception.cs new file mode 100644 index 00000000..5810d04f --- /dev/null +++ b/Il2Cppmscorlib/Exception.cs @@ -0,0 +1,9 @@ +namespace Il2CppSystem; + +public class Exception : Object +{ + public String ToString(Boolean needFileLineInfo, Boolean needMessage) + { + throw null; + } +} diff --git a/Il2Cppmscorlib/GC.cs b/Il2Cppmscorlib/GC.cs new file mode 100644 index 00000000..fc511c17 --- /dev/null +++ b/Il2Cppmscorlib/GC.cs @@ -0,0 +1,9 @@ +namespace Il2CppSystem; + +public static class GC +{ + public static void ReRegisterForFinalize(IObject obj) + { + throw null; + } +} diff --git a/Il2Cppmscorlib/IEnum.cs b/Il2Cppmscorlib/IEnum.cs new file mode 100644 index 00000000..cfaa3906 --- /dev/null +++ b/Il2Cppmscorlib/IEnum.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem; + +public interface IEnum : IValueType +{ +} diff --git a/Il2Cppmscorlib/IObject.cs b/Il2Cppmscorlib/IObject.cs new file mode 100644 index 00000000..ce4603e5 --- /dev/null +++ b/Il2Cppmscorlib/IObject.cs @@ -0,0 +1,12 @@ +namespace Il2CppSystem; + +public interface IObject +{ + Boolean Equals(IObject @object) => default; + Int32 GetIl2CppHashCode() => default; + void Il2CppFinalize() + { + } + + String ToIl2CppString() => default; +} diff --git a/Il2Cppmscorlib/IValueType.cs b/Il2Cppmscorlib/IValueType.cs new file mode 100644 index 00000000..f73f321a --- /dev/null +++ b/Il2Cppmscorlib/IValueType.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem; + +public interface IValueType : IObject +{ +} diff --git a/Il2Cppmscorlib/Il2Cppmscorlib.csproj b/Il2Cppmscorlib/Il2Cppmscorlib.csproj new file mode 100644 index 00000000..56b8e1b3 --- /dev/null +++ b/Il2Cppmscorlib/Il2Cppmscorlib.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + Il2CppSystem + disable + disable + false + 0.0.0 + + + + + + + diff --git a/Il2Cppmscorlib/Int32.cs b/Il2Cppmscorlib/Int32.cs new file mode 100644 index 00000000..b442c941 --- /dev/null +++ b/Il2Cppmscorlib/Int32.cs @@ -0,0 +1,15 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public struct Int32 : IIl2CppType +{ + static int IIl2CppType.Size => throw null; + readonly nint IIl2CppType.ObjectClass => throw null; + static Int32 IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw null; + static void IIl2CppType.WriteToSpan(Int32 value, System.Span span) => throw null; + ObjectPointer IIl2CppType.BoxNative() => throw new System.NotImplementedException(); + + public static implicit operator int(Int32 value) => throw null; + public static implicit operator Int32(int value) => throw null; +} diff --git a/Il2Cppmscorlib/IntPtr.cs b/Il2Cppmscorlib/IntPtr.cs new file mode 100644 index 00000000..9d72d890 --- /dev/null +++ b/Il2Cppmscorlib/IntPtr.cs @@ -0,0 +1,15 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public struct IntPtr : IIl2CppType +{ + static int IIl2CppType.Size => throw null; + readonly nint IIl2CppType.ObjectClass => throw null; + static IntPtr IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw null; + static void IIl2CppType.WriteToSpan(IntPtr value, System.Span span) => throw null; + ObjectPointer IIl2CppType.BoxNative() => throw new System.NotImplementedException(); + + public static implicit operator nint(IntPtr value) => throw null; + public static implicit operator IntPtr(nint value) => throw null; +} diff --git a/Il2Cppmscorlib/Nullable.cs b/Il2Cppmscorlib/Nullable.cs new file mode 100644 index 00000000..cb359bc4 --- /dev/null +++ b/Il2Cppmscorlib/Nullable.cs @@ -0,0 +1,17 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public struct Nullable : IObject, IValueType, IIl2CppType, IIl2CppType> + where T : struct, IValueType, IIl2CppType, IObject +{ + public Boolean hasValue; + + public T value; + + static int IIl2CppType>.Size => throw new System.NotImplementedException(); + nint IIl2CppType.ObjectClass => throw new System.NotImplementedException(); + static Nullable IIl2CppType>.ReadFromSpan(System.ReadOnlySpan span) => throw new System.NotImplementedException(); + static void IIl2CppType>.WriteToSpan(Nullable value, System.Span span) => throw new System.NotImplementedException(); + ObjectPointer IIl2CppType.BoxNative() => throw new System.NotImplementedException(); +} diff --git a/Il2Cppmscorlib/Object.cs b/Il2Cppmscorlib/Object.cs new file mode 100644 index 00000000..b9efe3a7 --- /dev/null +++ b/Il2Cppmscorlib/Object.cs @@ -0,0 +1,33 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public class Object : IObject, IIl2CppType +{ + protected Object() + { + } + + protected Object(ObjectPointer ptr) + { + } + + static int IIl2CppType.Size => throw new System.NotImplementedException(); + + public nint Pointer => default; + + public bool WasCollected => default; + + nint IIl2CppType.ObjectClass => throw new System.NotImplementedException(); + + static Object IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw new System.NotImplementedException(); + static void IIl2CppType.WriteToSpan(Object value, System.Span span) => throw new System.NotImplementedException(); + public virtual Boolean Equals(IObject @object) => default; + public virtual Int32 GetIl2CppHashCode() => default; + public virtual void Il2CppFinalize() + { + } + + public virtual String ToIl2CppString() => default; + ObjectPointer IIl2CppType.BoxNative() => throw new System.NotImplementedException(); +} diff --git a/Il2Cppmscorlib/README.md b/Il2Cppmscorlib/README.md new file mode 100644 index 00000000..174804dc --- /dev/null +++ b/Il2Cppmscorlib/README.md @@ -0,0 +1,5 @@ +# Il2Cppmscorlib + +This is a stub assembly to enable a circular dependency with Il2CppInterop.Runtime. + +To enforce project standards, nearly all classes in this assembly are marked as abstract, even if they aren't actually abstract in the real assembly. Similarly, constructors are marked as protected to avoid misuse. Objects should be created using the `Il2CppObjectPool.Get` API instead. diff --git a/Il2Cppmscorlib/Reflection/Assembly.cs b/Il2Cppmscorlib/Reflection/Assembly.cs new file mode 100644 index 00000000..97134dae --- /dev/null +++ b/Il2Cppmscorlib/Reflection/Assembly.cs @@ -0,0 +1,13 @@ +namespace Il2CppSystem.Reflection; + +public abstract class Assembly : Object +{ + public virtual AssemblyName GetName() + { + throw null; + } + public virtual Type GetType(String name) + { + throw null; + } +} diff --git a/Il2Cppmscorlib/Reflection/AssemblyName.cs b/Il2Cppmscorlib/Reflection/AssemblyName.cs new file mode 100644 index 00000000..01a71ca4 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/AssemblyName.cs @@ -0,0 +1,6 @@ +namespace Il2CppSystem.Reflection; + +public abstract class AssemblyName : Object +{ + public String Name { get; } +} diff --git a/Il2Cppmscorlib/Reflection/BindingFlags.cs b/Il2Cppmscorlib/Reflection/BindingFlags.cs new file mode 100644 index 00000000..52f7aa75 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/BindingFlags.cs @@ -0,0 +1,34 @@ +namespace Il2CppSystem.Reflection; + +public struct BindingFlags +{ + private readonly int value__; + public static readonly BindingFlags Default = (BindingFlags)0; + public static readonly BindingFlags IgnoreCase = (BindingFlags)1; + public static readonly BindingFlags DeclaredOnly = (BindingFlags)2; + public static readonly BindingFlags Instance = (BindingFlags)4; + public static readonly BindingFlags Static = (BindingFlags)8; + public static readonly BindingFlags Public = (BindingFlags)0x10; + public static readonly BindingFlags NonPublic = (BindingFlags)0x20; + public static readonly BindingFlags FlattenHierarchy = (BindingFlags)0x40; + public static readonly BindingFlags InvokeMethod = (BindingFlags)0x100; + public static readonly BindingFlags CreateInstance = (BindingFlags)0x200; + public static readonly BindingFlags GetField = (BindingFlags)0x400; + public static readonly BindingFlags SetField = (BindingFlags)0x800; + public static readonly BindingFlags GetProperty = (BindingFlags)0x1000; + public static readonly BindingFlags SetProperty = (BindingFlags)0x2000; + public static readonly BindingFlags PutDispProperty = (BindingFlags)0x4000; + public static readonly BindingFlags PutRefDispProperty = (BindingFlags)0x8000; + public static readonly BindingFlags ExactBinding = (BindingFlags)0x10000; + public static readonly BindingFlags SuppressChangeType = (BindingFlags)0x20000; + public static readonly BindingFlags OptionalParamBinding = (BindingFlags)0x40000; + public static readonly BindingFlags IgnoreReturn = (BindingFlags)0x1000000; + public static readonly BindingFlags DoNotWrapExceptions = (BindingFlags)0x2000000; + + public static explicit operator BindingFlags(int value) => throw null; + + public static BindingFlags operator &(BindingFlags left, BindingFlags right) => throw null; + public static BindingFlags operator |(BindingFlags left, BindingFlags right) => throw null; + public static BindingFlags operator ^(BindingFlags left, BindingFlags right) => throw null; + public static BindingFlags operator ~(BindingFlags value) => throw null; +} diff --git a/Il2Cppmscorlib/Reflection/ConstructorInfo.cs b/Il2Cppmscorlib/Reflection/ConstructorInfo.cs new file mode 100644 index 00000000..fd3fa6f9 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/ConstructorInfo.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem.Reflection; + +public abstract class ConstructorInfo : MethodBase +{ +} diff --git a/Il2Cppmscorlib/Reflection/FieldInfo.cs b/Il2Cppmscorlib/Reflection/FieldInfo.cs new file mode 100644 index 00000000..66d08c65 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/FieldInfo.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem.Reflection; + +public abstract class FieldInfo : MemberInfo +{ +} diff --git a/Il2Cppmscorlib/Reflection/MemberInfo.cs b/Il2Cppmscorlib/Reflection/MemberInfo.cs new file mode 100644 index 00000000..bb342ca6 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/MemberInfo.cs @@ -0,0 +1,8 @@ +namespace Il2CppSystem.Reflection; + +public abstract class MemberInfo : Object +{ + public virtual String Name { get; } + + public virtual Type DeclaringType { get; } +} diff --git a/Il2Cppmscorlib/Reflection/MethodBase.cs b/Il2Cppmscorlib/Reflection/MethodBase.cs new file mode 100644 index 00000000..0f206f5b --- /dev/null +++ b/Il2Cppmscorlib/Reflection/MethodBase.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem.Reflection; + +public abstract class MethodBase : MemberInfo +{ +} diff --git a/Il2Cppmscorlib/Reflection/MethodInfo.cs b/Il2Cppmscorlib/Reflection/MethodInfo.cs new file mode 100644 index 00000000..733c8b43 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/MethodInfo.cs @@ -0,0 +1,17 @@ +namespace Il2CppSystem.Reflection; + +public abstract class MethodInfo : MethodBase +{ + public virtual Type ReturnType + { + get + { + throw null; + } + } + + public MethodInfo MakeGenericMethod(Type[] typeArguments) + { + throw null; + } +} diff --git a/Il2Cppmscorlib/Reflection/MonoField.cs b/Il2Cppmscorlib/Reflection/MonoField.cs new file mode 100644 index 00000000..e96e7e15 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/MonoField.cs @@ -0,0 +1,6 @@ +namespace Il2CppSystem.Reflection; + +public abstract class MonoField : RtFieldInfo +{ + public IObject GetValueInternal(IObject obj) => null; +} diff --git a/Il2Cppmscorlib/Reflection/ParameterInfo.cs b/Il2Cppmscorlib/Reflection/ParameterInfo.cs new file mode 100644 index 00000000..ee0b4900 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/ParameterInfo.cs @@ -0,0 +1,6 @@ +namespace Il2CppSystem.Reflection; + +public abstract class ParameterInfo : Object +{ + public Type ParameterType { get; } +} diff --git a/Il2Cppmscorlib/Reflection/RtFieldInfo.cs b/Il2Cppmscorlib/Reflection/RtFieldInfo.cs new file mode 100644 index 00000000..d6f3caac --- /dev/null +++ b/Il2Cppmscorlib/Reflection/RtFieldInfo.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem.Reflection; + +public abstract class RtFieldInfo : RuntimeFieldInfo +{ +} diff --git a/Il2Cppmscorlib/Reflection/RuntimeFieldInfo.cs b/Il2Cppmscorlib/Reflection/RuntimeFieldInfo.cs new file mode 100644 index 00000000..b95f0f0f --- /dev/null +++ b/Il2Cppmscorlib/Reflection/RuntimeFieldInfo.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem.Reflection; + +public abstract class RuntimeFieldInfo : FieldInfo +{ +} diff --git a/Il2Cppmscorlib/Reflection/TypeInfo.cs b/Il2Cppmscorlib/Reflection/TypeInfo.cs new file mode 100644 index 00000000..f9f59f36 --- /dev/null +++ b/Il2Cppmscorlib/Reflection/TypeInfo.cs @@ -0,0 +1,5 @@ +namespace Il2CppSystem.Reflection; + +public abstract class TypeInfo : Type +{ +} diff --git a/Il2Cppmscorlib/Runtime/CompilerServices/RuntimeHelpers.cs b/Il2Cppmscorlib/Runtime/CompilerServices/RuntimeHelpers.cs new file mode 100644 index 00000000..a1c978be --- /dev/null +++ b/Il2Cppmscorlib/Runtime/CompilerServices/RuntimeHelpers.cs @@ -0,0 +1,9 @@ +namespace Il2CppSystem.Runtime.CompilerServices; + +public static class RuntimeHelpers +{ + public static void InitializeArray(Array array, IntPtr fldHandle) + { + throw null; + } +} diff --git a/Il2Cppmscorlib/RuntimeType.cs b/Il2Cppmscorlib/RuntimeType.cs new file mode 100644 index 00000000..776600a8 --- /dev/null +++ b/Il2Cppmscorlib/RuntimeType.cs @@ -0,0 +1,8 @@ +using Il2CppSystem.Reflection; + +namespace Il2CppSystem; + +public abstract class RuntimeType : TypeInfo +{ + public IObject GenericCache { get; set; } +} diff --git a/Il2Cppmscorlib/RuntimeTypeHandle.cs b/Il2Cppmscorlib/RuntimeTypeHandle.cs new file mode 100644 index 00000000..cfed52d4 --- /dev/null +++ b/Il2Cppmscorlib/RuntimeTypeHandle.cs @@ -0,0 +1,6 @@ +namespace Il2CppSystem; + +public struct RuntimeTypeHandle +{ + public IntPtr value; +} diff --git a/Il2Cppmscorlib/String.cs b/Il2Cppmscorlib/String.cs new file mode 100644 index 00000000..f9ad8588 --- /dev/null +++ b/Il2Cppmscorlib/String.cs @@ -0,0 +1,23 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public sealed class String : Object, IIl2CppType +{ + static int IIl2CppType.Size => throw null; + nint IIl2CppType.ObjectClass => throw null; + static String IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw null; + static void IIl2CppType.WriteToSpan(String value, System.Span span) => throw null; + + public String(ObjectPointer pointer) : base(pointer) + { + } + public static implicit operator String(string str) + { + throw null; + } + public static implicit operator string(String str) + { + throw null; + } +} diff --git a/Il2Cppmscorlib/Type.cs b/Il2Cppmscorlib/Type.cs new file mode 100644 index 00000000..03dd41b1 --- /dev/null +++ b/Il2Cppmscorlib/Type.cs @@ -0,0 +1,70 @@ +using Il2CppInterop.Common; +using Il2CppSystem.Reflection; + +namespace Il2CppSystem; + +public abstract class Type : Object, IIl2CppType +{ + static int IIl2CppType.Size => throw null; + nint IIl2CppType.ObjectClass => throw null; + static Type IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw null; + static void IIl2CppType.WriteToSpan(Type value, System.Span span) => throw null; + + public RuntimeTypeHandle _impl { get; set; } + + public Assembly Assembly => throw null; + + public Boolean ContainsGenericParameters => throw null; + + public Type DeclaringType => throw null; + + public abstract String FullName { get; } + + public Boolean IsArray => throw null; + + public Boolean IsByRef => throw null; + + public Boolean IsGenericType => throw null; + + public Boolean IsGenericTypeDefinition => throw null; + + public Boolean IsNested => throw null; + + public Boolean IsPointer => throw null; + + public Boolean IsPrimitive => throw null; + + public Boolean IsSZArray => throw null; + + public Boolean IsTypeDefinition => throw null; + + public String NameOrDefault => throw null; + + public String Namespace => throw null; + + public virtual RuntimeTypeHandle TypeHandle => throw null; + + public Int32 GetArrayRank() => throw null; + + public Type GetElementType() => throw null; + + public Type GetGenericTypeDefinition() => throw null; + + public MethodInfo GetMethod(String name) => throw null; + + public abstract Type GetNestedType(String name, BindingFlags bindingAttr); + + public static Type internal_from_handle(IntPtr handle) => throw null; + + public Type MakeByRefType() => throw null; + + public Type MakeGenericType(Type[] typeArguments) => throw null; + + public Type MakePointerType() => throw null; + + public static Boolean operator ==(Type left, Type right) => throw null; + public static Boolean operator !=(Type left, Type right) => throw null; + + public override bool Equals(object obj) => throw null; + public override int GetHashCode() => throw null; +} diff --git a/Il2Cppmscorlib/TypedReference.cs b/Il2Cppmscorlib/TypedReference.cs new file mode 100644 index 00000000..2ffa208b --- /dev/null +++ b/Il2Cppmscorlib/TypedReference.cs @@ -0,0 +1,22 @@ +namespace Il2CppSystem; + +public struct TypedReference +{ + /// + /// A field that Unity has, but is not present in .NET Framework nor .NET Core. + /// + /// + /// This field was added in + /// + public RuntimeTypeHandle type; + + /// + /// A pointer to the data + /// + public IntPtr Value; + + /// + /// The type handle + /// + public IntPtr Type; +} diff --git a/Il2Cppmscorlib/ValueType.cs b/Il2Cppmscorlib/ValueType.cs new file mode 100644 index 00000000..80883c45 --- /dev/null +++ b/Il2Cppmscorlib/ValueType.cs @@ -0,0 +1,11 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public abstract class ValueType : Object, IIl2CppType +{ + static int IIl2CppType.Size => throw new System.NotImplementedException(); + + static ValueType IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw new System.NotImplementedException(); + static void IIl2CppType.WriteToSpan(ValueType value, System.Span span) => throw new System.NotImplementedException(); +} diff --git a/Il2Cppmscorlib/Void.cs b/Il2Cppmscorlib/Void.cs new file mode 100644 index 00000000..0c4148a5 --- /dev/null +++ b/Il2Cppmscorlib/Void.cs @@ -0,0 +1,12 @@ +using Il2CppInterop.Common; + +namespace Il2CppSystem; + +public struct Void : IIl2CppType +{ + static int IIl2CppType.Size => throw null; + readonly nint IIl2CppType.ObjectClass => throw null; + static Void IIl2CppType.ReadFromSpan(System.ReadOnlySpan span) => throw null; + static void IIl2CppType.WriteToSpan(Void value, System.Span span) => throw null; + ObjectPointer IIl2CppType.BoxNative() => throw new System.NotImplementedException(); +} diff --git a/global.json b/global.json deleted file mode 100644 index 3fea262b..00000000 --- a/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sdk": { - "version": "8.0.0", - "rollForward": "latestFeature" - } -} diff --git a/nuget.config b/nuget.config new file mode 100644 index 00000000..60c52367 --- /dev/null +++ b/nuget.config @@ -0,0 +1,5 @@ + + + + +