BepInEx / Il2CppInterop

A tool interoperate between CoreCLR and Il2Cpp at runtime
GNU Lesser General Public License v3.0
210 stars 68 forks source link

RegisterTypeInIl2Cpp: Support injecting generic types #22

Open Albeoris opened 2 years ago

Albeoris commented 2 years ago

Describe your proposal

Generic types such as Dictionary, List and HashSet are very helpful in mod development.

Now we have to use Dictionary<Object, Object>, as it is most often found in every game. But it also does not support all the necessary methods, for example TryGetValue is often not available.

I'd like to get support for Generic types. It is not yet clear how they differ conceptually from any other types, since at the IL2CPP level we will get a specific type, like Dictionary_String_String.

Error:

ClassInjector.RegisterTypeInIl2Cpp<Dictionary<String, String>>();
System.Exception: Failed to register required types. ---> System.ArgumentException: Type Il2CppSystem.Collections.Generic.Dictionary`2[System.String,System.String] is generic and can't be used in il2cpp
  at UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp (System.Type type, UnhollowerRuntimeLib.RegisterTypeOptions options) [0x00071] in <38fa7eb6c12441c8a8621750ed4c48b6>:0 
  at UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp (System.Type type) [0x00000] in <38fa7eb6c12441c8a8621750ed4c48b6>:0 
  at UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp[T] () [0x00000] in <38fa7eb6c12441c8a8621750ed4c48b6>:0 

Proposed solution

Add the ability to register a specific Generic type and all its methods.

If such a type is already registered, register the methods that are missing because they were not used at the time of compilation.

Alternatives

In the case of Dictionary, List and HashSet, we can continue to use Dictionary<Object, Object> under hood, but provide a clever wrapper like

IDictionary<String, String> dic = GenericFactory.GetDictionary<String,String>();

for Reference types.

Related: BepInEx/BepInEx#334

ghorsington commented 2 years ago

Greetings!

Thank you for the proposal. This, however, is related more to Il2CppUnhollower which is the library for Il2Cpp<->Managed interop. I'll move the issue to our Il2CppUnhollower repo instead.

HookedBehemoth commented 3 months ago

If this is still a requested feature, I could look into it, as I've worked on the Class-Injector before for generics and interfaces. I'm a little bit confused by this issue thread though. How would you work with the injected type? Do you want to pass it to il2cpp code or use it yourself somewhere? The example you provided is a standard library type and is not inheriting from Il2CppObjectBase.

Albeoris commented 1 month ago

Hi, @HookedBehemoth !

How would you work with the injected type? Do you want to pass it to il2cpp code or use it yourself somewhere?

Yes, this is still a requested feature:

  1. IL2CPP does not contain all the methods that the original type has. For example, if the game authors did not use Dictionary<String,String>.TryGetValue, then the compiled code will not have this method.

Wanted change: the ability to register missing methods using the full type description as a reference. This will allow us to handle IL2CPP collections in the usual way, without having to convert the IL2CPP instance to a regular one by copying all the elements.

// Register type Dictionary<String, String> and all its methods
ClassInjector.EnsureTypeRegisteredWithAllMethods<Dictionary<String, String>>();

// Registers all methods for all types inherited from Dictionary<,>
ClassInjector.EnsureAllMethodsRegistered(typeof(Dictionary<,>));
  1. Passing a Generic argument to the IL2CPP layer, it will be converted to a concrete type. If such a type does not exist in the assembly, an error will be thrown.


class Graph<T>
{
  // Public method allow us to pass any Node<T>
  void Add(Node<T> value);

  // But there is only two implementations on IL2CPP layer:
  // Graph_Event      // Graph<Event>
  // Graph_Condition  // Graph<Condition>

  // Node_Event      // Node<Event>
  // Node_Condition  // Node<Condition>
}

// So this call will throw an error:
Graph<Scene>.Add(SpecialNodes.CurrentScene);

Wanted change: the ability to register Generic<Scene> and its dependencies included Node<Scene> on IL2CPP layer and use them without errors.

ClassInjector.EnsureTypeRegisteredWithAllMethods<Graph<Scene>>();
ds5678 commented 1 month ago

Ideally, we should support injecting any aspect of the type system. That would enable us to automatically inject unstripped classes as part of our code generation.