diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index 7acc5653..28588657 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -292,7 +292,276 @@ namespace il2cpp_utils { return std::array(classof(TArgs)...); } + /// @brief Makes a delegate wrapping a context function (such as a context lambda). + /// @tparam T The type to return. + /// @tparam I The instance object to provide to this delegate. + /// @tparam R The return type of the delegate. + /// @tparam TArgs The arguments of the delegate. + /// @param delegateClass The Il2CppClass* of the delegate to create. + /// @param instance The (move constructible) instance reference to provide to the delegate. This instance is moved and will no longer be valid. + /// @param f The function to invoke with the delegate. + /// @return The created delegate. + template + [[deprecated("DO NOT USE! USE custom_types INSTEAD!")]] T MakeDelegate(const Il2CppClass* delegateClass, I& instance, std::function f) { + auto* wrapperInstance = reinterpret_cast*>(__AllocateUnsafe(sizeof(WrapperInstance))); + + wrapperInstance->rawInstance = std::move(instance); + wrapperInstance->wrappedFunc = f; + return MakeDelegate(delegateClass, wrapperInstance, &invoker_func_instance); + } + + /// @brief Makes a delegate wrapping a context function (such as a context lambda). + /// @tparam T The type to return. + /// @tparam I The instance object to provide to this delegate. + /// @tparam R The return type of the delegate. + /// @tparam TArgs The arguments of the delegate. + /// @param instance The (move constructible) instance reference to provide to the delegate. This instance is moved and will no longer be valid. + /// @param f The function to invoke with the delegate. + /// @return The created delegate. + template + [[deprecated("DO NOT USE! USE custom_types INSTEAD!")]] T MakeDelegate(I& instance, std::function f) { + return MakeDelegate(classof(T), instance, f); + } + + /// @brief Makes a delegate wrapping a context function (such as a context lambda). + /// @tparam T The type to return. + /// @tparam I The instance object to provide to this delegate. + /// @tparam R The return type of the delegate. + /// @tparam TArgs The arguments of the delegate. + /// @param delegateClass The Il2CppClass* of the delegate to create. + /// @param instance The (move constructible) instance reference to provide to the delegate. This instance is moved and will no longer be valid. + /// @param f The function to invoke with the delegate. + /// @return The created delegate. + template + [[deprecated("DO NOT USE! USE custom_types INSTEAD!")]] T MakeDelegate(const Il2CppClass* delegateClass, std::function f) { + auto* wrapperInstance = reinterpret_cast*>(__AllocateUnsafe(sizeof(WrapperStatic))); + wrapperInstance->wrappedFunc = f; + return MakeDelegate(delegateClass, wrapperInstance, &invoker_func_static); + } + + /// @brief Makes a delegate wrapping a context function (such as a context lambda). + /// @tparam T The type to return. + /// @tparam I The instance object to provide to this delegate. + /// @tparam R The return type of the delegate. + /// @tparam TArgs The arguments of the delegate. + /// @param instance The (move constructible) instance reference to provide to the delegate. This instance is moved and will no longer be valid. + /// @param f The function to invoke with the delegate. + /// @return The created delegate. + template + [[deprecated("DO NOT USE! USE custom_types INSTEAD!")]] T MakeDelegate(std::function f) { + return MakeDelegate(classof(T), f); + } + + /// @brief Makes a delegate wrapping the provided instance method. + /// @tparam T The type to return. + /// @tparam I The instance object. + /// @tparam R The return type of the delegate. + /// @tparam TArgs The arguments of the delegate. + /// @param delegateClass The Il2CppClass* of the delegate to create. + /// @param instance The (move constructible) instance reference to provide to the delegate. This instance is moved and will no longer be valid. + /// @param memberFunc A pointer to the member function on the provided instance to invoke for this delegate. + /// @return The created delegate. + template + [[deprecated("DO NOT USE! USE custom_types INSTEAD!")]] inline T MakeDelegate(const Il2CppClass* delegateClass, I& instance, R (I::*memberFunc)(TArgs...)) { + return MakeDelegate(delegateClass, instance, std::function(memberFunc)); + } + + /// @brief Makes a delegate wrapping the provided instance method. + /// @tparam T The type to return. + /// @tparam I The instance object. + /// @tparam R The return type of the delegate. + /// @tparam TArgs The arguments of the delegate. + /// @param instance The (move constructible) instance reference to provide to the delegate. This instance is moved and will no longer be valid. + /// @param memberFunc A pointer to the member function on the provided instance to invoke for this delegate. + /// @return The created delegate. + template + [[deprecated("DO NOT USE! USE custom_types INSTEAD!")]] inline T MakeDelegate(I& instance, R (I::*memberFunc)(TArgs...)) { + return MakeDelegate(classof(T), instance, std::function(memberFunc)); + } + + /// @brief Creates and returns a C# System.Func from the provided function_ptr_t. + /// Note that this function assumes AOT code exists for a System.Func with the provided generic arguments. + /// @tparam Ret The return type of the function + /// @tparam TArgs The arguments of the function + /// @returns The created System.Func. Null if it could not be created. + template + T MakeFunc(function_ptr_t lambda) { + static_assert(sizeof...(TArgs) + 1 <= 16, "Cannot create a Func` where N is > 16!"); + static_assert(!std::is_same_v, "Function used in ::il2cpp_utils::MakeFunc must have a non-void return!"); + auto const& logger = il2cpp_utils::Logger; + // Get generic class with matching number of args + static auto* genericClass = il2cpp_utils::GetClassFromName("System", "Func`" + ::std::to_string(sizeof...(TArgs) + 1)); + // Extract all parameter types and return types + static auto genericClasses = ExtractFromFunctionNoArgs(); + // Instantiate the Func` type + auto* instantiatedFunc = RET_DEFAULT_UNLESS(logger, il2cpp_utils::MakeGeneric(genericClass, genericClasses)); + // Create the action from the instantiated Func` type + return il2cpp_utils::MakeDelegate(instantiatedFunc, static_cast(nullptr), lambda); + } + + /// @brief Creates and returns a C# System.Action from the provided function_ptr_t. + /// Note that this function assumes AOT code exists for a System.Action with the provided generic arguments. + /// @tparam TArgs The arguments of the function + /// @returns The created System.Action. Null if it could not be created. + template + T MakeAction(function_ptr_t lambda) { + static_assert(sizeof...(TArgs) <= 16, "Cannot create an Action` where N is > 16!"); + auto const& logger = il2cpp_utils::Logger; + if constexpr (sizeof...(TArgs) != 0) { + // Get generic class with matching number of args + static auto* genericClass = il2cpp_utils::GetClassFromName("System", "Action`" + ::std::to_string(sizeof...(TArgs))); + // Extract all parameter types and return types + static auto genericClasses = ExtractFromFunctionNoArgs(); + // Instantiate the Func` type + auto* instantiatedFunc = RET_DEFAULT_UNLESS(logger, il2cpp_utils::MakeGeneric(genericClass, genericClasses)); + // Create the action from the instantiated Func` type + return il2cpp_utils::MakeDelegate(instantiatedFunc, static_cast(nullptr), lambda); + } else { + static auto* klass = il2cpp_utils::GetClassFromName("System", "Action"); + return il2cpp_utils::MakeDelegate(klass, static_cast(nullptr), lambda); + } + } + + /// @brief Creates a delegate of return type T, with target TObj, using the provided Il2CppClass* + /// @tparam T The type to return + /// @tparam TObj The type of the target object + /// @param delegateClass The delegate Il2CppClass* to use + /// @param obj The target instance + /// @param callback The callback function_ptr_t + /// @returns The created delegate + template + T MakeDelegate(const Il2CppClass* delegateClass, TObj obj, function_ptr_t callback) { + static_assert(sizeof(TObj) == sizeof(void*), "TObj must have the same size as a pointer!"); + static_assert(sizeof(T) == sizeof(void*), "T must have the same size as a pointer!"); + auto const& logger = il2cpp_utils::Logger; + auto callbackPtr = reinterpret_cast(callback); + /* + * TODO: call PlatformInvoke::MarshalFunctionPointerToDelegate directly instead of copying code from it, + * or at least use a cache like utils::NativeDelegateMethodCache::GetNativeDelegate(nativeFunctionPointer); + */ + // Lets cache this method. Well formed delegates have only one Invoke method, so ignore param count. + auto itr = delegateMethodInfoMap.find({ callbackPtr, obj == nullptr }); + MethodInfo* method; + if (itr != delegateMethodInfoMap.end()) { + method = itr->second; + } else { + auto* invoke = il2cpp_utils::FindMethodUnsafe(delegateClass, "Invoke", -1); + method = reinterpret_cast(calloc(1, sizeof(MethodInfo))); + // Add the allocated delegate so we can free it later. + method->methodPointer = callbackPtr; + method->invoker_method = invoke->invoker_method; + method->name = "NativeDelegateMethod"; + method->klass = il2cpp_functions::defaults->object_class; + method->parameters = invoke->parameters; + method->return_type = invoke->return_type; + method->parameters_count = invoke->parameters_count; + method->slot = kInvalidIl2CppMethodSlot; + method->has_full_generic_sharing_signature = false; +#if defined(UNITY_2018) || defined(UNITY_2021) || defined(UNITY_2022) + method->indirect_call_via_invokers = true; // "a fake MethodInfo wrapping a native function pointer" +#endif + if (obj == nullptr) method->flags |= METHOD_ATTRIBUTE_STATIC; + AddAllocatedDelegate({ callbackPtr, obj == nullptr }, method); + } + // In the event that a function is static, this will behave as normal + // Yes, we mutate the held one as well. This is okay because we will ALWAYS mutate it. + auto* delegate = RET_DEFAULT_UNLESS(logger, il2cpp_utils::New(delegateClass, obj, &method)); + auto* asDelegate = reinterpret_cast(delegate); + if ((void*)asDelegate->method_ptr != (void*)callback) { + logger.error("Created Delegate's method_ptr ({p) is incorrect (should be {})!", fmt::ptr((void*)asDelegate->method_ptr), callback); + return nullptr; + } + return delegate; + } + + /// @brief Creates a delegate of return type T, with target TObj, using the provided Il2CppClass* + /// @tparam T The type to return + /// @tparam TObj The type of the target object + /// @param obj The target instance + /// @param callback The callback function_ptr_t + /// @returns The created delegate + template + T MakeDelegate(TObj obj, function_ptr_t callback) { + return MakeDelegate(classof(T), obj, callback); + } + + /// @brief Creates a delegate of return type T, with target TObj, using the provided Il2CppClass*. + /// Assumes the callback has no parameters and a void return. + /// @tparam T The type to return + /// @tparam TObj The type of the target object + /// @param delegateClass The delegate Il2CppClass* to use + /// @param obj The target instance + /// @param callback The callback function_ptr_t + /// @returns The created delegate + template + T MakeDelegate(const Il2CppClass* delegateClass, TObj obj, void* callback) { + auto tmp = reinterpret_cast>(callback); + return MakeDelegate(delegateClass, obj, tmp); + } + + /// @brief Creates a delegate of return type T, with target TObj, using the provided Il2CppType* + /// PLEASE!!! use the FieldInfo*, MethodInfo*, or Il2CppClass* versions instead if you can. + /// @tparam T The type to return + /// @tparam TObj The type of the target object + /// @param delegateType The delegate Il2CppType* to use + /// @param obj The target instance + /// @param callback The callback function_ptr_t + /// @returns The created delegate + template + T MakeDelegate(const Il2CppType* actionType, TObj obj, function_ptr_t callback) { + il2cpp_functions::Init(); + Il2CppClass* delegateClass = il2cpp_functions::class_from_il2cpp_type(actionType); + return MakeDelegate(delegateClass, obj, callback); + } + + /// @brief Creates a delegate of return type T, with target TObj, using the provided Il2CppType* and void* callback. + /// Assumes the callback has no parameters and a void return. + /// @tparam T The type to return + /// @tparam TObj The type of the target object + /// @param delegateType The delegate Il2CppType* to use + /// @param obj The target instance + /// @param callback The callback function + /// @returns The created delegate + template + T MakeDelegate(const Il2CppType* delegateType, TObj obj, void* callback) { + auto tmp = reinterpret_cast>(callback); + return MakeDelegate(delegateType, obj, tmp); + } + + /// @brief Creates a delegate fit to be passed in the given parameter position to the given method. + /// @tparam T The type to return + /// @tparam T1 The type to forward to another call of MakeDelegate + /// @tparam T2 The type to forward to another call of MakeDelegate + /// @param method The MethodInfo* to grab the parameter from + /// @param paramIdx The parameter to grab the type from + /// @param arg1 Forwarded to another MakeDelegate + /// @param arg2 Forwarded to another MakeDelegate + /// @returns The created delegate + template + T MakeDelegate(const MethodInfo* method, int paramIdx, T1&& arg1, T2&& arg2) { + il2cpp_functions::Init(); + auto const& logger = il2cpp_utils::Logger; + auto* delegateType = RET_0_UNLESS(logger, il2cpp_functions::method_get_param(method, paramIdx)); + return MakeDelegate(delegateType, arg1, arg2); + } + + /// @brief Creates a delegate fit to be assigned to the given field. + /// @tparam T The type to return + /// @tparam T1 The type to forward to another call of MakeDelegate + /// @tparam T2 The type to forward to another call of MakeDelegate + /// @param field The FieldInfo* to grab the parameter from + /// @param arg1 Forwarded to another MakeDelegate + /// @param arg2 Forwarded to another MakeDelegate + /// @returns The created delegate + template + T MakeDelegate(FieldInfo* field, T1&& arg1, T2&& arg2) { + il2cpp_functions::Init(); + auto const& logger = il2cpp_utils::Logger; + auto* delegateType = RET_0_UNLESS(logger, il2cpp_functions::field_get_type(field)); + return MakeDelegate(delegateType, arg1, arg2); + } + void RemoveDelegate(Il2CppDelegate* delegateInstance, Il2CppDelegate* comparePointer) noexcept; // MethodInfo* + hook variadic function --> type check template @@ -647,4 +916,4 @@ namespace il2cpp_utils { #pragma pack(pop) -#endif /* IL2CPP_UTILS_H */ +#endif /* IL2CPP_UTILS_H */ \ No newline at end of file diff --git a/src/tests/il2cpp-tests.cpp b/src/tests/il2cpp-tests.cpp index 8344a22c..970fb489 100644 --- a/src/tests/il2cpp-tests.cpp +++ b/src/tests/il2cpp-tests.cpp @@ -262,120 +262,101 @@ static void test_runmethodrethrow_on_throwing_method() { } } +// Delegate test callbacks and state (file-scope to avoid lambdas) +static bool __il2cpp_test_action_called = false; +static bool __il2cpp_test_makedelegate_called = false; +static int __il2cpp_test_func_value = 0; + +static void __il2cpp_test_action_callback() { + __il2cpp_test_action_called = true; + LOG_OK("[il2cpp-tests] MakeAction callback executed"); +} + +static void __il2cpp_test_makedelegate_callback() { + __il2cpp_test_makedelegate_called = true; + LOG_OK("[il2cpp-tests] MakeDelegate callback executed"); +} + +static int __il2cpp_test_func_callback() { + __il2cpp_test_func_value = 2025; + LOG_OK("[il2cpp-tests] MakeFunc callback executed"); + return __il2cpp_test_func_value; +} + static void test_delegates() { using namespace il2cpp_utils; auto const& logger = il2cpp_utils::Logger; logger.info("[il2cpp-tests] Starting delegate/action tests"); - // Get System.Action type as a reflection Type - auto* actionClass = GetClassFromName("System", "Action"); - if (!actionClass) { - LOG_FAIL("[il2cpp-tests] System.Action class not found, skipping delegate tests"); - return; - } - auto* actionType = GetSystemType(actionClass); - if (!actionType) { - LOG_FAIL("[il2cpp-tests] Could not get reflection type for System.Action"); - return; - } - // Attempt to create a delegate that wraps System.GC.Collect (static void Collect()) - auto* gcClass = GetClassFromName("System", "GC"); - if (!gcClass) { - LOG_FAIL("[il2cpp-tests] System.GC class not found, skipping delegate CreateDelegate test"); - } else { - auto* gcType = GetSystemType(gcClass); - if (!gcType) { - LOG_FAIL("[il2cpp-tests] Could not get reflection type for System.GC"); - } else { - // Try CreateDelegate(Type, Type, String) -> delegate for static method - auto* delegateKlass = GetClassFromName("System", "Delegate"); - if (!delegateKlass) { - LOG_FAIL("[il2cpp-tests] System.Delegate class not found, cannot Find CreateDelegate"); - } else { - auto createStatic = FindMethod( - delegateKlass, "CreateDelegate", - std::array{ ExtractIndependentType(), ExtractIndependentType(), ExtractIndependentType() }); - if (!createStatic) { - LOG_FAIL("[il2cpp-tests] Could not find Delegate.CreateDelegate(Type,Type,String) overload"); + + // Using file-scope callbacks defined above: __il2cpp_test_action_callback, + // __il2cpp_test_makedelegate_callback, __il2cpp_test_func_callback + + // MakeAction (no-arg void) + { + auto act = il2cpp_utils::MakeAction<>(reinterpret_cast>(__il2cpp_test_action_callback)); + if (act) { + LOG_OK("[il2cpp-tests] MakeAction created -> {}", fmt::ptr(act)); + try { + RunMethodRethrow((Il2CppObject*)act, "Invoke"); + if (__il2cpp_test_action_called) { + LOG_OK("[il2cpp-tests] MakeAction Invoke executed callback"); } else { - if (auto del = RunMethodOpt(nullptr, createStatic, actionType, gcType, std::string("Collect"))) { - LOG_OK("[il2cpp-tests] Successfully created delegate for GC.Collect -> {}", fmt::ptr(*del)); - // Invoke the delegate's Invoke() method (Action.Invoke) - try { - RunMethodRethrow(*del, "Invoke"); - LOG_OK("[il2cpp-tests] Invoked delegate Invoke() successfully (should have called GC.Collect)"); - } catch (const il2cpp_utils::RunMethodException& e) { - LOG_FAIL("[il2cpp-tests] Running delegate Invoke threw RunMethodException: {}", e.what()); - } - } else { - LOG_FAIL("[il2cpp-tests] Delegate.CreateDelegate returned nullopt or failed"); - } + LOG_FAIL("[il2cpp-tests] MakeAction Invoke did not run callback"); } + } catch (const il2cpp_utils::RunMethodException& e) { + LOG_FAIL("[il2cpp-tests] MakeAction Invoke threw: {}", e.what()); } + } else { + LOG_FAIL("[il2cpp-tests] MakeAction failed to create"); } } - // Test creating a delegate instance from a managed method on an object (instance delegate) - // We'll create a System.Object and use its ToString method as a Func - if (auto obj = New("System", "Object")) { - // auto* objClass = GetClassFromName("System", "Object"); - auto* funcClass = GetClassFromName("System", "Func`1"); - if (!funcClass) { - LOG_FAIL("[il2cpp-tests] System.Func`1 not found, skipping instance delegate test"); - return; - } - // Make Func generic - auto* stringClass = GetClassFromName("System", "String"); - if (!stringClass) { - LOG_FAIL("[il2cpp-tests] System.String not found, skipping instance delegate test"); - return; - } - const Il2CppClass* const genArgs[] = { stringClass }; - auto* funcGeneric = il2cpp_utils::MakeGeneric(funcClass, genArgs); - if (!funcGeneric) { - LOG_FAIL("[il2cpp-tests] Could not make generic List for Func"); - return; - } - - // Get reflection type for the delegate type - auto* funcType = GetSystemType(funcGeneric); - if (!funcType) { - LOG_FAIL("[il2cpp-tests] Could not get reflection type for Func"); - return; + // MakeFunc (no-arg non-void) + { + auto f = il2cpp_utils::MakeFunc<>(reinterpret_cast>(__il2cpp_test_func_callback)); + if (f) { + LOG_OK("[il2cpp-tests] MakeFunc created -> {}", fmt::ptr(f)); + try { + auto res = RunMethodRethrow((Il2CppObject*)f, "Invoke"); + LOG_OK("[il2cpp-tests] MakeFunc Invoke returned -> {}", res); + if (res == __il2cpp_test_func_value) { + LOG_OK("[il2cpp-tests] MakeFunc result matches callback value"); + } else { + LOG_FAIL("[il2cpp-tests] MakeFunc result {} != expected {}", res, __il2cpp_test_func_value); + } + } catch (const il2cpp_utils::RunMethodException& e) { + LOG_FAIL("[il2cpp-tests] MakeFunc Invoke threw: {}", e.what()); + } + } else { + LOG_FAIL("[il2cpp-tests] MakeFunc failed to create"); } + } - // Create delegate bound to the object's ToString method: Delegate.CreateDelegate(Type, Object, String) - { - auto* delegateKlass = GetClassFromName("System", "Delegate"); - if (!delegateKlass) { - LOG_FAIL("[il2cpp-tests] System.Delegate class not found, cannot Find CreateDelegate for instance overload"); - } else { - auto createInstance = - FindMethod(delegateKlass, "CreateDelegate", - std::array{ ExtractIndependentType(), ExtractIndependentType(), ExtractIndependentType() }); - if (!createInstance) { - LOG_FAIL("[il2cpp-tests] Could not find Delegate.CreateDelegate(Type,Object,String) overload"); - } else { - if (auto delObj = RunMethodOpt(nullptr, createInstance, funcType, (Il2CppObject*)*obj, std::string("ToString"))) { - LOG_OK("[il2cpp-tests] Created instance delegate for Object.ToString -> {}", fmt::ptr(*delObj)); - // Invoke the delegate's Invoke() method and get string result - try { - auto res = RunMethodRethrow(*delObj, "Invoke"); - if (res) { - LOG_OK("[il2cpp-tests] Delegate.Invoke() returned -> {}", fmt::ptr(res)); - } - } catch (const il2cpp_utils::RunMethodException& e) { - LOG_FAIL("[il2cpp-tests] Running instance delegate Invoke threw: {}", e.what()); - } + // MakeDelegate using explicit Action class + { + auto* actionKlass = GetClassFromName("System", "Action"); + if (!actionKlass) { + LOG_FAIL("[il2cpp-tests] System.Action class not found, skipping MakeDelegate test"); + } else { + auto del = il2cpp_utils::MakeDelegate(actionKlass, static_cast(nullptr), reinterpret_cast>(__il2cpp_test_makedelegate_callback)); + if (del) { + LOG_OK("[il2cpp-tests] MakeDelegate(Action) created -> {}", fmt::ptr(del)); + try { + RunMethodRethrow((Il2CppObject*)del, "Invoke"); + if (__il2cpp_test_makedelegate_called) { + LOG_OK("[il2cpp-tests] MakeDelegate Invoke executed callback"); } else { - LOG_FAIL("[il2cpp-tests] Could not create instance delegate for Object.ToString"); + LOG_FAIL("[il2cpp-tests] MakeDelegate Invoke did not run callback"); } + } catch (const il2cpp_utils::RunMethodException& e) { + LOG_FAIL("[il2cpp-tests] MakeDelegate Invoke threw: {}", e.what()); } + } else { + LOG_FAIL("[il2cpp-tests] MakeDelegate(Action) failed to create"); } } - } else { - LOG_FAIL("[il2cpp-tests] Could not create System.Object instance for delegate test"); } }