From 6b0b9f523f90f9bc0d9cfc461da93d242ac8d1b0 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Mon, 11 Dec 2023 20:29:30 +0100 Subject: [PATCH 01/89] Things compile, needs xref fixes though --- CMakeLists.txt | 128 +++++++++++++++-------- build.ps1 | 24 ++--- qpm.json | 2 +- qpm.shared.json | 6 +- shared/utils/il2cpp-functions.hpp | 47 ++++++--- shared/utils/il2cpp-type-check.hpp | 16 ++- shared/utils/il2cpp-utils-classes.hpp | 5 +- shared/utils/il2cpp-utils-exceptions.hpp | 6 +- shared/utils/il2cpp-utils-methods.hpp | 30 ++++-- shared/utils/il2cpp-utils.hpp | 15 +-- shared/utils/typedefs-list.hpp | 8 +- shared/utils/typedefs.h | 23 +++- src/tests/array-wrapper-tests.cpp | 9 +- src/tests/safeptr-tests.cpp | 3 +- src/tests/string-tests.cpp | 6 +- src/tests/test-check.cpp | 5 + src/utils/il2cpp-functions.cpp | 107 +++++++++++++------ src/utils/il2cpp-utils-classes.cpp | 51 ++++++--- src/utils/il2cpp-utils-exceptions.cpp | 4 +- src/utils/il2cpp-utils.cpp | 15 +-- 20 files changed, 335 insertions(+), 175 deletions(-) create mode 100644 src/tests/test-check.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b64ca0d5..b5fc5cfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED 20) # LTO set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) # define that stores the actual source directory set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) @@ -22,57 +23,92 @@ add_compile_definitions(VERSION=\"${MOD_VERSION}\") add_compile_definitions(MOD_ID=\"${MOD_ID}\") add_compile_definitions(VERSION_NUMBER=300150000) add_compile_definitions(UNITY_2019) +add_compile_definitions(UNITY_2021) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -if (DEFINED TEST_BUILD) - MESSAGE(STATUS "Compiling with test defines") - add_compile_definitions(TEST_CALLBACKS) - add_compile_definitions(TEST_SAFEPTR) - add_compile_definitions(TEST_BYREF) - add_compile_definitions(TEST_ARRAY) - add_compile_definitions(TEST_LIST) - add_compile_definitions(TEST_STRING) - add_compile_definitions(TEST_HOOK) -endif() - -add_library( - ${COMPILE_ID} - SHARED +function(setup_target target add_test) + message(STATUS "Setting up target ${target}") + + add_library( + ${target} + SHARED + ) + + if (add_test) + target_compile_definitions( + ${target} + PRIVATE + + TEST_CALLBACKS + TEST_SAFEPTR + TEST_BYREF + TEST_ARRAY + TEST_LIST + TEST_STRING + TEST_HOOK + ) + endif() + + # recursively get all src files + RECURSE_FILES(cpp_file_list_utils ${SOURCE_DIR}/utils/*.cpp) + RECURSE_FILES(c_file_list_utils ${SOURCE_DIR}/utils/*.c) + target_sources(${target} PRIVATE ${cpp_file_list_utils}) + target_sources(${target} PRIVATE ${c_file_list_utils}) + + RECURSE_FILES(cpp_file_list_config ${SOURCE_DIR}/config/*.cpp) + RECURSE_FILES(c_file_list_config ${SOURCE_DIR}/config/*.c) + target_sources(${target} PRIVATE ${cpp_file_list_config}) + target_sources(${target} PRIVATE ${c_file_list_config}) + + if (add_test) + message(STATUS "Adding tests source files to target ${target}") + RECURSE_FILES(cpp_file_list_tests ${SOURCE_DIR}/tests/*.cpp) + RECURSE_FILES(c_file_list_tests ${SOURCE_DIR}/tests/*.c) + target_sources(${target} PRIVATE ${cpp_file_list_tests}) + target_sources(${target} PRIVATE ${c_file_list_tests}) + endif() + + # add root dir as include dir + target_include_directories(${target} PRIVATE ${CMAKE_SOURCE_DIR}) + # add src dir as include dir + target_include_directories(${target} PRIVATE ${SOURCE_DIR}) + # add include dir as include dir + target_include_directories(${target} PRIVATE ${INCLUDE_DIR}) + # add shared dir as include dir + target_include_directories(${target} PUBLIC ${SHARED_DIR}) + + target_link_directories(${target} PRIVATE ${EXTERN_DIR}/libs) + target_link_libraries(${target} PRIVATE -llog) + target_link_options(${target} PRIVATE "-fuse-ld=ld") + + file(GLOB_RECURSE so_libs ${EXTERN_DIR}/libs/*.so) + file(GLOB_RECURSE a_libs ${EXTERN_DIR}/libs/*.a) + + target_link_libraries( + ${target} + PRIVATE + ${so_libs} + ${a_libs} + ) + + target_include_directories(${target} PRIVATE ${EXTERN_DIR}/includes) + target_include_directories(${target} SYSTEM PRIVATE ${EXTERN_DIR}/includes/libil2cpp/il2cpp/libil2cpp) +endfunction() + +# compile test as one project +setup_target(${COMPILE_ID}-test YES) +# regular bs hook +setup_target(${COMPILE_ID} NO) + +target_compile_definitions( + ${COMPILE_ID} + PRIVATE + NO_TEST ) -# recursively get all src files -RECURSE_FILES(cpp_file_list_utils ${SOURCE_DIR}/utils/*.cpp) -RECURSE_FILES(c_file_list_utils ${SOURCE_DIR}/utils/*.c) -target_sources(${COMPILE_ID} PRIVATE ${cpp_file_list_utils}) -target_sources(${COMPILE_ID} PRIVATE ${c_file_list_utils}) - -RECURSE_FILES(cpp_file_list_config ${SOURCE_DIR}/config/*.cpp) -RECURSE_FILES(c_file_list_config ${SOURCE_DIR}/config/*.c) -target_sources(${COMPILE_ID} PRIVATE ${cpp_file_list_config}) -target_sources(${COMPILE_ID} PRIVATE ${c_file_list_config}) - -if (DEFINED TEST_BUILD) -RECURSE_FILES(cpp_file_list_tests ${SOURCE_DIR}/tests/*.cpp) -RECURSE_FILES(c_file_list_tests ${SOURCE_DIR}/tests/*.c) -target_sources(${COMPILE_ID} PRIVATE ${cpp_file_list_tests}) -target_sources(${COMPILE_ID} PRIVATE ${c_file_list_tests}) -endif() - -# add root dir as include dir -target_include_directories(${COMPILE_ID} PRIVATE ${CMAKE_SOURCE_DIR}) -# add src dir as include dir -target_include_directories(${COMPILE_ID} PRIVATE ${SOURCE_DIR}) -# add include dir as include dir -target_include_directories(${COMPILE_ID} PRIVATE ${INCLUDE_DIR}) -# add shared dir as include dir -target_include_directories(${COMPILE_ID} PUBLIC ${SHARED_DIR}) - -target_link_libraries(${COMPILE_ID} PRIVATE -llog) -target_link_options(${COMPILE_ID} PRIVATE "-fuse-ld=ld") - -# add extern stuff like libs and other includes -include(extern.cmake) +# make bs hook build depend on the build of bs hook tests +# add_dependencies(${COMPILE_ID} ${COMPILE_ID}-test) add_custom_command(TARGET ${COMPILE_ID} POST_BUILD COMMAND ${CMAKE_STRIP} -g -S -d --strip-all diff --git a/build.ps1 b/build.ps1 index 9eaf8af2..21fa661e 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,3 +1,7 @@ +param ( + [Parameter(Mandatory=$false)] + [Switch] $clean +) function Clean-Build-Folder { if (Test-Path -Path "build") @@ -9,28 +13,14 @@ function Clean-Build-Folder { } } -$NDKPath = Get-Content $PSScriptRoot/ndkpath.txt - -Clean-Build-Folder - -& cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DTEST_BUILD=1 . -B build -& cmake --build ./build - -$ExitCode = $LastExitCode - -if (-not ($ExitCode -eq 0)) { - $msg = "ExitCode: " + $ExitCode - Write-Output $msg - exit $ExitCode +if ($clean.IsPresent) { + Clean-Build-Folder } -# clean folder -Clean-Build-Folder # build mod - & cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" . -B build & cmake --build ./build $ExitCode = $LastExitCode -exit $ExitCode \ No newline at end of file +exit $ExitCode diff --git a/qpm.json b/qpm.json index f9f93de7..46013991 100644 --- a/qpm.json +++ b/qpm.json @@ -20,7 +20,7 @@ }, { "id": "libil2cpp", - "versionRange": ">=0.1.2, <0.3.0", + "versionRange": "^0.3.0", "additionalData": {} }, { diff --git a/qpm.shared.json b/qpm.shared.json index 2e5ac127..188cf2a8 100644 --- a/qpm.shared.json +++ b/qpm.shared.json @@ -21,7 +21,7 @@ }, { "id": "libil2cpp", - "versionRange": ">=0.1.2, <0.3.0", + "versionRange": "^0.3.0", "additionalData": {} }, { @@ -44,12 +44,12 @@ { "dependency": { "id": "libil2cpp", - "versionRange": "=0.2.3", + "versionRange": "=0.3.1", "additionalData": { "headersOnly": true } }, - "version": "0.2.3" + "version": "0.3.1" }, { "dependency": { diff --git a/shared/utils/il2cpp-functions.hpp b/shared/utils/il2cpp-functions.hpp index fe8c0c80..2e6e1b3d 100644 --- a/shared/utils/il2cpp-functions.hpp +++ b/shared/utils/il2cpp-functions.hpp @@ -8,11 +8,16 @@ #include #include "logging.hpp" #include "utils.h" +#include "vm/GlobalMetadataFileInternals.h" #if !defined(UNITY_2019) && __has_include("il2cpp-runtime-stats.h") #define UNITY_2019 #endif +#if !defined(UNITY_2021) && __has_include("il2cpp-mono-api.h") +#define UNITY_2021 +#endif + #if defined(__GLIBCXX__) || defined(__GLIBCPP__) // We are currently compiling with GNU GCC libstdc++, so we are already using its STL implementation typedef std::string gnu_string; @@ -123,7 +128,7 @@ static rt name(TArgs&&... args) { \ class il2cpp_functions { public: // These methods autogenerated by Sc2ad: - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(int, init, (const char* domain_name)); API_FUNC(int, init_utf16, (const Il2CppChar * domain_name)); #else @@ -153,7 +158,7 @@ class il2cpp_functions { API_FUNC(Il2CppClass*, bounded_array_class_get, (Il2CppClass * element_class, uint32_t rank, bool bounded)); API_FUNC(int, array_element_size, (const Il2CppClass * array_class)); API_FUNC(const Il2CppImage*, assembly_get_image, (const Il2CppAssembly * assembly)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(void, class_for_each, (void(*klassReportFunc)(Il2CppClass* klass, void* userData), void* userData)); #endif API_FUNC(const Il2CppType*, class_enum_basetype, (Il2CppClass * klass)); @@ -176,7 +181,7 @@ class il2cpp_functions { API_FUNC(const MethodInfo*, class_get_methods, (Il2CppClass * klass, void* *iter)); API_FUNC(const MethodInfo*, class_get_method_from_name, (const Il2CppClass * klass, const char* name, int argsCount)); API_FUNC(const char*, class_get_name, (const Il2CppClass * klass)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(void, type_get_name_chunked, (const Il2CppType * type, void(*chunkReportFunc)(void* data, void* userData), void* userData)); #endif API_FUNC(const char*, class_get_namespace, (const Il2CppClass * klass)); @@ -200,7 +205,7 @@ class il2cpp_functions { API_FUNC(const Il2CppImage*, class_get_image, (Il2CppClass * klass)); API_FUNC(const char*, class_get_assemblyname, (const Il2CppClass * klass)); API_FUNC(int, class_get_rank, (const Il2CppClass * klass)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(uint32_t, class_get_data_size, (const Il2CppClass * klass)); API_FUNC(void*, class_get_static_field_data, (const Il2CppClass * klass)); #endif @@ -211,7 +216,7 @@ class il2cpp_functions { API_FUNC(Il2CppDomain*, domain_get, ()); API_FUNC(const Il2CppAssembly*, domain_assembly_open, (Il2CppDomain * domain, const char* name)); API_FUNC(const Il2CppAssembly**, domain_get_assemblies, (const Il2CppDomain * domain, size_t * size)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(void, raise_exception, (Il2CppException*)); #endif API_FUNC(Il2CppException*, exception_from_name_msg, (const Il2CppImage * image, const char *name_space, const char *name, const char *msg)); @@ -231,7 +236,7 @@ class il2cpp_functions { API_FUNC(void, field_static_get_value, (FieldInfo * field, void *value)); API_FUNC(void, field_static_set_value, (FieldInfo * field, void *value)); API_FUNC(void, field_set_value_object, (Il2CppObject * instance, FieldInfo * field, Il2CppObject * value)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(bool, field_is_literal, (FieldInfo * field)); #endif API_FUNC(void, gc_collect, (int maxGenerations)); @@ -239,7 +244,7 @@ class il2cpp_functions { API_FUNC(void, gc_disable, ()); API_FUNC(void, gc_enable, ()); API_FUNC(bool, gc_is_disabled, ()); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(int64_t, gc_get_max_time_slice_ns, ()); API_FUNC(void, gc_set_max_time_slice_ns, (int64_t maxTimeSlice)); API_FUNC(bool, gc_is_incremental, ()); @@ -247,7 +252,7 @@ class il2cpp_functions { API_FUNC(int64_t, gc_get_used_size, ()); API_FUNC(int64_t, gc_get_heap_size, ()); API_FUNC(void, gc_wbarrier_set_field, (Il2CppObject * obj, void **targetAddress, void *object)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(bool, gc_has_strict_wbarriers, ()); API_FUNC(void, gc_set_external_allocation_tracker, (void(*func)(void*, size_t, int))); API_FUNC(void, gc_set_external_wbarrier_tracker, (void(*func)(void**))); @@ -259,7 +264,7 @@ class il2cpp_functions { API_FUNC(uint32_t, gchandle_new_weakref, (Il2CppObject * obj, bool track_resurrection)); API_FUNC(Il2CppObject*, gchandle_get_target, (uint32_t gchandle)); API_FUNC(void, gchandle_free, (uint32_t gchandle)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(void, gchandle_foreach_get_target, (void(*func)(void* data, void* userData), void* userData)); API_FUNC(uint32_t, object_header_size, ()); API_FUNC(uint32_t, array_object_header_size, ()); @@ -267,8 +272,15 @@ class il2cpp_functions { API_FUNC(uint32_t, offset_of_array_bounds_in_array_object_header, ()); API_FUNC(uint32_t, allocation_granularity, ()); #endif + #ifndef UNITY_2021 API_FUNC(void*, unity_liveness_calculation_begin, (Il2CppClass * filter, int max_object_count, il2cpp_register_object_callback callback, void* userdata, il2cpp_WorldChangedCallback onWorldStarted, il2cpp_WorldChangedCallback onWorldStopped)); API_FUNC(void, unity_liveness_calculation_end, (void* state)); + #endif + #ifdef UNITY_2021 + API_FUNC(void*, il2cpp_unity_liveness_allocate_struct, (Il2CppClass * filter, int max_object_count, il2cpp_register_object_callback callback, void* userdata, il2cpp_liveness_reallocate_callback reallocate)); + API_FUNC(void, il2cpp_unity_liveness_finalize, (void* state)); + API_FUNC(void, il2cpp_unity_liveness_free_struct, (void* state)); + #endif API_FUNC(void, unity_liveness_calculation_from_root, (Il2CppObject * root, void* state)); API_FUNC(void, unity_liveness_calculation_from_statics, (void* state)); API_FUNC(const Il2CppType*, method_get_return_type, (const MethodInfo * method)); @@ -343,7 +355,7 @@ class il2cpp_functions { API_FUNC(bool, thread_get_frame_at, (Il2CppThread * thread, int32_t offset, Il2CppStackFrameInfo * frame)); API_FUNC(int32_t, current_thread_get_stack_depth, ()); API_FUNC(int32_t, thread_get_stack_depth, (Il2CppThread * thread)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(void, override_stack_backtrace, (Il2CppBacktraceFunc stackBacktraceFunc)); #endif API_FUNC(Il2CppObject*, type_get_object, (const Il2CppType * type)); @@ -354,7 +366,7 @@ class il2cpp_functions { API_FUNC(uint32_t, type_get_attrs, (const Il2CppType * type)); API_FUNC(bool, type_equals, (const Il2CppType * type, const Il2CppType * otherType)); API_FUNC(char*, type_get_assembly_qualified_name, (const Il2CppType * type)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(bool, type_is_static, (const Il2CppType * type)); API_FUNC(bool, type_is_pointer_type, (const Il2CppType * type)); #endif @@ -370,7 +382,7 @@ class il2cpp_functions { API_FUNC(void, register_log_callback, (Il2CppLogCallback method)); API_FUNC(void, debugger_set_agent_options, (const char* options)); API_FUNC(bool, is_debugger_attached, ()); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(void, register_debugger_agent_transport, (Il2CppDebuggerTransport * debuggerTransport)); API_FUNC(bool, debug_get_method_info, (const MethodInfo*, Il2CppMethodDebugInfo * methodDebugInfo)); #endif @@ -381,7 +393,7 @@ class il2cpp_functions { API_FUNC(bool, custom_attrs_has_attr, (Il2CppCustomAttrInfo * ainfo, Il2CppClass * attr_klass)); API_FUNC(Il2CppArray*, custom_attrs_construct, (Il2CppCustomAttrInfo * cinfo)); API_FUNC(void, custom_attrs_free, (Il2CppCustomAttrInfo * ainfo)); - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC(void, class_set_userdata, (Il2CppClass * klass, void* userdata)); API_FUNC(int, class_get_userdata_offset, ()); #endif @@ -396,7 +408,7 @@ class il2cpp_functions { API_FUNC_VISIBLE(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeDefinitionIndex, (TypeDefinitionIndex index)); API_FUNC_VISIBLE(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeIndex, (TypeIndex index)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC_VISIBLE(std::string, _Type_GetName_, (const Il2CppType *type, Il2CppTypeNameFormat format)); #else API_FUNC_VISIBLE(gnu_string, _Type_GetName_, (const Il2CppType *type, Il2CppTypeNameFormat format)); @@ -456,6 +468,11 @@ class il2cpp_functions { static const Il2CppGenericParameter* MetadataCache_GetGenericParameterFromIndex(GenericParameterIndex index); static Il2CppClass* MetadataCache_GetNestedTypeFromIndex(NestedTypeIndex index); static TypeDefinitionIndex MetadataCache_GetIndexForTypeDefinition(const Il2CppClass* typeDefinition); + static TypeDefinitionIndex MetadataCache_GetIndexForTypeDefinition(const Il2CppTypeDefinition* typeDefinition); + static GenericParameterIndex MetadataCache_GetGenericParameterIndexFromParameter(Il2CppMetadataGenericParameterHandle handle); + static const Il2CppTypeDefinition* MetadataCache_GetTypeDefinition(Il2CppClass* klass); + static GenericParameterIndex MetadataCache_GetGenericContainerIndex(Il2CppClass* klass); + static Il2CppClass* MetadataCache_GetTypeInfoFromHandle(Il2CppMetadataTypeHandle handle); // Whether all of the il2cpp functions have been initialized or not static bool initialized; @@ -469,4 +486,4 @@ class il2cpp_functions { #pragma pack(pop) -#endif /* IL2CPP_FUNCTIONS_H */ \ No newline at end of file +#endif /* IL2CPP_FUNCTIONS_H */ diff --git a/shared/utils/il2cpp-type-check.hpp b/shared/utils/il2cpp-type-check.hpp index 60b26d6c..034a6956 100644 --- a/shared/utils/il2cpp-type-check.hpp +++ b/shared/utils/il2cpp-type-check.hpp @@ -107,6 +107,14 @@ namespace il2cpp_utils { } }; + static inline Il2CppClass* GetGenericTemplateClass(Il2CppGenericClass* generic_class) { + #ifdef UNITY_2021 + return CRASH_UNLESS(il2cpp_functions::type_get_class_or_element_class(generic_class->type)); + #else + return CRASH_UNLESS(il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(generic_class->typeDefinitionIndex)); + #endif + } + template #ifndef BS_HOOK_USE_CONCEPTS struct il2cpp_no_arg_class && T::IS_VALUE_TYPE>> { @@ -121,7 +129,7 @@ namespace il2cpp_utils { Il2CppClass* classWithNested = declaring; if (declaring->generic_class) { // Class::GetNestedTypes refuses to work on generic instances, so get the generic template instead - classWithNested = CRASH_UNLESS(il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(declaring->generic_class->typeDefinitionIndex)); + classWithNested = GetGenericTemplateClass(declaring->generic_class); } #if __has_feature(cxx_rtti) std::string typeName = type_name(); @@ -166,7 +174,7 @@ namespace il2cpp_utils { Il2CppClass* classWithNested = declaring; if (declaring->generic_class) { // Class::GetNestedTypes refuses to work on generic instances, so get the generic template instead - classWithNested = CRASH_UNLESS(il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(declaring->generic_class->typeDefinitionIndex)); + classWithNested = GetGenericTemplateClass(declaring->generic_class); } #if __has_feature(cxx_rtti) std::string typeName = type_name(); @@ -334,7 +342,7 @@ namespace il2cpp_utils { Il2CppClass* classWithNested = declaring; if (declaring->generic_class) { // Class::GetNestedTypes refuses to work on generic instances, so get the generic template instead - classWithNested = CRASH_UNLESS(il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(declaring->generic_class->typeDefinitionIndex)); + classWithNested = GetGenericTemplateClass(declaring->generic_class); } std::string typeName(S::NESTED_NAME); @@ -364,7 +372,7 @@ namespace il2cpp_utils { Il2CppClass* classWithNested = declaring; if (declaring->generic_class) { // Class::GetNestedTypes refuses to work on generic instances, so get the generic template instead - classWithNested = CRASH_UNLESS(il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(declaring->generic_class->typeDefinitionIndex)); + classWithNested = GetGenericTemplateClass(declaring->generic_class); } std::string typeName(S::NESTED_NAME); diff --git a/shared/utils/il2cpp-utils-classes.hpp b/shared/utils/il2cpp-utils-classes.hpp index c3cd3e30..f478cabe 100644 --- a/shared/utils/il2cpp-utils-classes.hpp +++ b/shared/utils/il2cpp-utils-classes.hpp @@ -1,5 +1,6 @@ #pragma once +#include "utils/typedefs.h" #pragma pack(push) #include "logging.hpp" @@ -155,12 +156,12 @@ namespace il2cpp_utils { // Adds the given nested types of the namespaze, parentName, and klass to the hastable // Mainly used in AddTypeToNametoClassHashTable - void AddNestedTypesToNametoClassHashTable(Il2CppNameToTypeDefinitionIndexHashTable* hashTable, const char *namespaze, const ::std::string& parentName, Il2CppClass *klass); + void AddNestedTypesToNametoClassHashTable(Il2CppNameToTypeHandleHashTable* hashTable, const char *namespaze, const ::std::string& parentName, Il2CppClass *klass); // Adds the given nested types of typeDefinition to the class hash table of a given image // Mainly used in AddTypeToNametoClassHashTable void AddNestedTypesToNametoClassHashTable(const Il2CppImage* img, const Il2CppTypeDefinition* typeDefinition); - + /// @brief This method allows you to check if the parameter is a child or instance of the parent class. E.g (B extends A) /// @tparam ParentT The parent class (left hand assignment) /// @param subOrInstanceKlass the instance class (right hand assignment) diff --git a/shared/utils/il2cpp-utils-exceptions.hpp b/shared/utils/il2cpp-utils-exceptions.hpp index 45bef8d0..e2a2a449 100644 --- a/shared/utils/il2cpp-utils-exceptions.hpp +++ b/shared/utils/il2cpp-utils-exceptions.hpp @@ -35,7 +35,7 @@ namespace il2cpp_utils { // Returns a legible string from an Il2CppException* ::std::string ExceptionToString(Il2CppException* exp) noexcept; - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) /// @brief Raises the provided Il2CppException to be used within il2cpp. /// @param exp The exception instance to throw [[noreturn]] void raise(const Il2CppException* exp); @@ -81,7 +81,7 @@ namespace il2cpp_utils { // Logs the backtrace with the Logging::ERROR level, using the global logger instance. void log_backtrace() const; [[noreturn]] void rethrow() const { - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) il2cpp_utils::raise(ex); #else #warning "The exception being rethrown like this is unlikely to behave correctly!" @@ -139,4 +139,4 @@ namespace il2cpp_utils { SAFE_ABORT(); \ } -#endif \ No newline at end of file +#endif diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index 6207ce2f..ca21dae7 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -173,7 +173,11 @@ namespace il2cpp_utils { if constexpr (::std::is_base_of_v>) { if (arg) { auto* klass = il2cpp_functions::object_get_class(reinterpret_cast(arg)); + #ifdef UNITY_2021 + if (klass && il2cpp_functions::class_is_valuetype(klass)) { + #else if (klass && klass->valuetype) { + #endif // Arg is an Il2CppObject* of a value type. It needs to be unboxed. return il2cpp_functions::object_unbox(reinterpret_cast(arg)); } @@ -201,7 +205,7 @@ namespace il2cpp_utils { // Pointer type, grab class and perform deduction for unbox. // Must be classof deducible! auto* k = classof(Dt); - if (k && k->valuetype) { + if (k && il2cpp_functions::class_is_valuetype(k)) { // Arg is an Il2CppObject* of a value type. It needs to be unboxed. return il2cpp_functions::object_unbox(reinterpret_cast(arg)); } @@ -278,16 +282,30 @@ namespace il2cpp_utils { if (method->parameters_count != argSz) { return false; } - auto genCount = (method->is_generic && !method->is_inflated) ? method->genericContainer->type_argc : 0; + #ifdef UNITY_2021 + auto genericContainer = reinterpret_cast(method->genericContainerHandle); + #else + auto genericContainer = method->genericContainer; + #endif + auto genCount = (method->is_generic && !method->is_inflated) ? genericContainer->type_argc : 0; if (genCount != genSz) { // logger.warning("Potential method match had wrong number of generics %i (expected %lu)", genCount, genSz); return false; } // TODO: supply boolStrictMatch and use type_equals instead of IsConvertibleFrom if supplied? for (decltype(method->parameters_count) i = 0; i < argSz; i++) { + #ifdef UNITY_2021 + auto* paramType = method->parameters[i]; + #else auto* paramType = method->parameters[i].parameter_type; + #endif + if (paramType->type == IL2CPP_TYPE_MVAR) { + #ifdef UNITY_2021 + auto genIdx = il2cpp_functions::MetadataCache_GetGenericParameterIndexFromParameter(paramType->data.genericParameterHandle); + #else auto genIdx = paramType->data.genericParameterIndex - method->genericContainer->genericParameterStart; + #endif if (genIdx < 0) { logger.warning("Extracted invalid genIdx %i from parameter %i", genIdx, i); } else if (genIdx >= genCount) { @@ -356,7 +374,7 @@ namespace il2cpp_utils { // Need to potentially call Class::Init here as well // This snippet is almost identical to what libil2cpp does - if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0 && method->klass && method->klass->has_cctor && !method->klass->cctor_finished) { + if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0 && method->klass && !method->klass->cctor_finished_or_no_cctor) { il2cpp_functions::Class_Init(method->klass); } try { @@ -371,7 +389,7 @@ namespace il2cpp_utils { // Static method reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); } else { - if (method->klass->valuetype) { + if (il2cpp_functions::class_is_valuetype(method->klass)) { // Value type instance method. Instance parameter is always boxed in some way. auto boxedRepr = instance; if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { @@ -411,7 +429,7 @@ namespace il2cpp_utils { // Static method return reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); } else { - if (method->klass->valuetype) { + if (il2cpp_functions::class_is_valuetype(method->klass)) { auto boxedRepr = instance; if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { // Boxing is only required if we invoke to adjustor thunks instead of actual impls @@ -654,7 +672,7 @@ namespace il2cpp_utils { auto* createdMethod = RET_NULLOPT_UNLESS(logger, MakeGenericMethod(info, genTypes)); return RunMethod(instance, createdMethod, params...); } - + template ::std::optional RunGenericMethod(T&& classOrInstance, ::std::string_view methodName, ::std::vector genTypes, TArgs&& ...params) noexcept { static auto& logger = getLogger(); diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index 9be1e73a..80e3e662 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -170,7 +170,7 @@ namespace il2cpp_utils { if constexpr (what_able) { allocEx->message = newcsstr(arg.what()); } - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) raise(allocEx); #else #warning "Raising C++ exceptions without il2cpp_functions::raise is undefined behavior!" @@ -212,10 +212,11 @@ namespace il2cpp_utils { method->return_type = invoke->return_type; method->parameters_count = invoke->parameters_count; method->slot = kInvalidIl2CppMethodSlot; - method->is_marshaled_from_native = true; // "a fake MethodInfo wrapping a native function pointer" + method->has_full_generic_sharing_signature = false; + method->indirect_call_via_invokers = true; // "a fake MethodInfo wrapping a native function pointer" 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::NewUnsafe(delegateClass, obj, &method)); @@ -444,7 +445,7 @@ namespace il2cpp_utils { /// @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. + /// @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)); @@ -457,7 +458,7 @@ namespace il2cpp_utils { /// @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. + /// @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)); @@ -610,7 +611,7 @@ namespace il2cpp_utils { auto* params = info->parameters; // Because we check arguments left to right, we can take advantage of params++ to iterate through the elements. // We know they must be valid since we check the parameter count above. - if (!(AssignableFrom(il2cpp_functions::class_from_type((params++)->parameter_type)) && ...)) { + if (!(AssignableFrom(il2cpp_functions::class_from_type(params++)) && ...)) { return false; } return true; @@ -662,7 +663,7 @@ namespace il2cpp_utils { auto* params = info->parameters; // Because we check arguments left to right, we can take advantage of params++ to iterate through the elements. // We know they must be valid since we check the parameter count above. - if (!(AssignableFrom(il2cpp_functions::class_from_type((params++)->parameter_type)) && ...)) { + if (!(AssignableFrom(il2cpp_functions::class_from_type(params++)) && ...)) { return false; } return true; diff --git a/shared/utils/typedefs-list.hpp b/shared/utils/typedefs-list.hpp index 69bb41b8..25abf506 100644 --- a/shared/utils/typedefs-list.hpp +++ b/shared/utils/typedefs-list.hpp @@ -8,11 +8,13 @@ template*> struct ListWrapper { static_assert(sizeof(Ptr) == sizeof(void*), "Size of Ptr type must be the same as a void*!"); - + + constexpr ListWrapper() noexcept : ptr(nullptr) {} + // TODO: Consider requirementally constexpr-ifying this call // TODO: Apply these il2cpp conversion changes to ArrayW as well, to permit ArrayW to hold wrapper types and not pure pointers constexpr ListWrapper(Ptr const& p) noexcept : ptr(p) {} - + template requires (std::is_pointer_v && !il2cpp_utils::has_il2cpp_conversion) constexpr ListWrapper(void* alterInit) noexcept : ptr(reinterpret_cast(alterInit)) {} @@ -125,4 +127,4 @@ static_assert(il2cpp_utils::has_il2cpp_conversion*>>) template struct ::il2cpp_utils::il2cpp_type_check::need_box> { constexpr static bool value = false; -}; \ No newline at end of file +}; diff --git a/shared/utils/typedefs.h b/shared/utils/typedefs.h index dc6ac704..0a1fd451 100644 --- a/shared/utils/typedefs.h +++ b/shared/utils/typedefs.h @@ -322,6 +322,7 @@ NEED_NO_BOX(Il2CppStackFrame); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionAssemblyName*, assembly_name); NEED_NO_BOX(Il2CppReflectionAssemblyName); // DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionAssembly*, assembly); +#ifndef UNITY_2021 DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionAssembly*, mono_assembly); NEED_NO_BOX(Il2CppReflectionAssembly); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionField*, mono_field); @@ -329,10 +330,13 @@ NEED_NO_BOX(Il2CppReflectionField); // DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionParameter*, parameter_info); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionParameter*, mono_parameter_info); NEED_NO_BOX(Il2CppReflectionParameter); +#endif DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionModule*, module); NEED_NO_BOX(Il2CppReflectionModule); +#ifndef UNITY_2021 DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionPointer*, pointer); NEED_NO_BOX(Il2CppReflectionPointer); +#endif DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppSystemException*, system_exception); NEED_NO_BOX(Il2CppSystemException); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppArgumentException*, argument_exception); @@ -357,16 +361,20 @@ DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppThread*, thread); NEED_NO_BOX(Il2CppThread); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionRuntimeType*, runtimetype); NEED_NO_BOX(Il2CppReflectionRuntimeType); +#ifndef UNITY_2021 DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionMonoEventInfo*, mono_event_info); NEED_NO_BOX(Il2CppReflectionMonoEventInfo); +#endif DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppTypedRef*, typed_reference); NEED_NO_BOX(Il2CppTypedRef); +#ifndef UNITY_2021 DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionMethod*, mono_method); NEED_NO_BOX(Il2CppReflectionMethod); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppMethodInfo*, mono_method_info); NEED_NO_BOX(Il2CppMethodInfo); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppPropertyInfo*, mono_property_info); NEED_NO_BOX(Il2CppPropertyInfo); +#endif DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppException*, exception); NEED_NO_BOX(Il2CppException); @@ -400,14 +408,23 @@ struct NamespaceAndNamePairEquals } }; -struct Il2CppNameToTypeDefinitionIndexHashTable : public Il2CppHashMap, TypeDefinitionIndex, NamespaceAndNamePairHash, NamespaceAndNamePairEquals> +struct Il2CppNameToTypeHandleHashTable : public Il2CppHashMap, Il2CppMetadataTypeHandle, NamespaceAndNamePairHash, NamespaceAndNamePairEquals> { - typedef Il2CppHashMap, TypeDefinitionIndex, NamespaceAndNamePairHash, NamespaceAndNamePairEquals> Base; - Il2CppNameToTypeDefinitionIndexHashTable() : Base() + typedef Il2CppHashMap, Il2CppMetadataTypeHandle, NamespaceAndNamePairHash, NamespaceAndNamePairEquals> Base; + Il2CppNameToTypeHandleHashTable() : Base() { } }; +typedef struct Il2CppImageGlobalMetadata +{ + TypeDefinitionIndex typeStart; + TypeDefinitionIndex exportedTypeStart; + CustomAttributeIndex customAttributeStart; + MethodIndex entryPointIndex; + const Il2CppImage* image; +} Il2CppImageGlobalMetadata; + #pragma pack(pop) #endif /* TYPEDEFS_H */ diff --git a/src/tests/array-wrapper-tests.cpp b/src/tests/array-wrapper-tests.cpp index 1d33a918..d864b42b 100644 --- a/src/tests/array-wrapper-tests.cpp +++ b/src/tests/array-wrapper-tests.cpp @@ -1,7 +1,8 @@ #ifdef TEST_ARRAY -#include "../../shared/utils/typedefs-array.hpp" +#include "../../shared/utils/typedefs.h" #include +#include static void constDoThing(const ArrayW& wrap) { auto i = wrap[0]; @@ -41,13 +42,13 @@ static void doThing() { std::cout << i << std::endl; // Should be simply nullptr std::cout << static_cast*>(initThing) << std::endl; - + /// get first element that fulfills the predicate arr.FirstOrDefault(); arr3.First(); arr.FirstOrDefault([](auto x){ return x == 0; }); arr3.First([](auto x){ return x == 0; }); - + /// get first reverse iter element that fulfills the predicate arr.FirstOrDefault(); arr3.First(); @@ -77,4 +78,4 @@ static void doThing2() { il2cpp_utils::RunMethodRethrow>((Il2CppClass*)nullptr, &info); } -#endif \ No newline at end of file +#endif diff --git a/src/tests/safeptr-tests.cpp b/src/tests/safeptr-tests.cpp index f90526b1..6bd984a6 100644 --- a/src/tests/safeptr-tests.cpp +++ b/src/tests/safeptr-tests.cpp @@ -1,5 +1,6 @@ #ifdef TEST_SAFEPTR -#include "../../shared/utils/typedefs-wrappers.hpp" +#include "../../shared/utils/typedefs.h" +#include static void testRef(SafePtr& ref) { *ref = 55; diff --git a/src/tests/string-tests.cpp b/src/tests/string-tests.cpp index 3a0164cf..9dd3f6b7 100644 --- a/src/tests/string-tests.cpp +++ b/src/tests/string-tests.cpp @@ -57,7 +57,7 @@ static void test2() { std::wstring w3s(w3); std::u16string_view w6s(w6); Il2CppString* v2(w1); - + if (one == w1) ; if (w1 == one) @@ -94,7 +94,7 @@ static void test2() { w1 + "testappend"; w2 += "testappendinplace"; - + w1.starts_with("test"); w1[0] = 'A'; @@ -118,4 +118,4 @@ static void test2() { RunMethod((Il2CppString*)w1, "Equals", one); } #pragma clang diagnostic pop -#endif \ No newline at end of file +#endif diff --git a/src/tests/test-check.cpp b/src/tests/test-check.cpp new file mode 100644 index 00000000..8810677d --- /dev/null +++ b/src/tests/test-check.cpp @@ -0,0 +1,5 @@ +#ifdef NO_TEST +#if defined(TEST_CALLBACKS) || defined(TEST_SAFEPTR) || defined(TEST_BYREF) || defined(TEST_ARRAY) || defined(TEST_LIST) || defined(TEST_STRING) || defined(TEST_HOOK) +#error "tests are being built into the release for bs hook!" +#endif +#endif diff --git a/src/utils/il2cpp-functions.cpp b/src/utils/il2cpp-functions.cpp index dcce2bc1..c9f87a74 100644 --- a/src/utils/il2cpp-functions.cpp +++ b/src/utils/il2cpp-functions.cpp @@ -8,7 +8,7 @@ #define API_INIT(rt, name, ...) rt(*il2cpp_functions::il2cpp_##name) __VA_ARGS__ // All the fields... -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(int, init, (const char* domain_name)); API_INIT(int, init_utf16, (const Il2CppChar* domain_name)); #else @@ -38,7 +38,7 @@ API_INIT(Il2CppArray*, array_new_full, (Il2CppClass * array_class, il2cpp_array_ API_INIT(Il2CppClass*, bounded_array_class_get, (Il2CppClass * element_class, uint32_t rank, bool bounded)); API_INIT(int, array_element_size, (const Il2CppClass* array_class)); API_INIT(const Il2CppImage*, assembly_get_image, (const Il2CppAssembly* assembly)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(void, class_for_each, (void (*klassReportFunc)(Il2CppClass* klass, void* userData), void* userData)); #endif API_INIT(const Il2CppType*, class_enum_basetype, (Il2CppClass * klass)); @@ -61,7 +61,7 @@ API_INIT(FieldInfo*, class_get_field_from_name, (Il2CppClass * klass, const char API_INIT(const MethodInfo*, class_get_methods, (Il2CppClass * klass, void** iter)); API_INIT(const MethodInfo*, class_get_method_from_name, (const Il2CppClass* klass, const char* name, int argsCount)); API_INIT(const char*, class_get_name, (const Il2CppClass* klass)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(void, type_get_name_chunked, (const Il2CppType* type, void (*chunkReportFunc)(void* data, void* userData), void* userData)); #endif API_INIT(const char*, class_get_namespace, (const Il2CppClass* klass)); @@ -85,7 +85,7 @@ API_INIT(bool, class_is_enum, (const Il2CppClass* klass)); API_INIT(const Il2CppImage*, class_get_image, (Il2CppClass * klass)); API_INIT(const char*, class_get_assemblyname, (const Il2CppClass* klass)); API_INIT(int, class_get_rank, (const Il2CppClass* klass)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(uint32_t, class_get_data_size, (const Il2CppClass* klass)); API_INIT(void*, class_get_static_field_data, (const Il2CppClass* klass)); #endif @@ -96,7 +96,7 @@ API_INIT(uint64_t, stats_get_value, (Il2CppStat stat)); API_INIT(Il2CppDomain*, domain_get, ()); API_INIT(const Il2CppAssembly*, domain_assembly_open, (Il2CppDomain * domain, const char* name)); API_INIT(const Il2CppAssembly**, domain_get_assemblies, (const Il2CppDomain* domain, size_t* size)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(void, raise_exception, (Il2CppException*)); #endif API_INIT(Il2CppException*, exception_from_name_msg, (const Il2CppImage* image, const char* name_space, const char* name, const char* msg)); @@ -116,7 +116,7 @@ API_INIT(void, field_set_value, (Il2CppObject * obj, FieldInfo* field, void* val API_INIT(void, field_static_get_value, (FieldInfo * field, void* value)); API_INIT(void, field_static_set_value, (FieldInfo * field, void* value)); API_INIT(void, field_set_value_object, (Il2CppObject * instance, FieldInfo* field, Il2CppObject* value)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(bool, field_is_literal, (FieldInfo * field)); #endif API_INIT(void, gc_collect, (int maxGenerations)); @@ -124,7 +124,7 @@ API_INIT(int32_t, gc_collect_a_little, ()); API_INIT(void, gc_disable, ()); API_INIT(void, gc_enable, ()); API_INIT(bool, gc_is_disabled, ()); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(int64_t, gc_get_max_time_slice_ns, ()); API_INIT(void, gc_set_max_time_slice_ns, (int64_t maxTimeSlice)); API_INIT(bool, gc_is_incremental, ()); @@ -132,7 +132,7 @@ API_INIT(bool, gc_is_incremental, ()); API_INIT(int64_t, gc_get_used_size, ()); API_INIT(int64_t, gc_get_heap_size, ()); API_INIT(void, gc_wbarrier_set_field, (Il2CppObject * obj, void** targetAddress, void* object)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(bool, gc_has_strict_wbarriers, ()); API_INIT(void, gc_set_external_allocation_tracker, (void (*func)(void*, size_t, int))); API_INIT(void, gc_set_external_wbarrier_tracker, (void (*func)(void**))); @@ -144,7 +144,7 @@ API_INIT(uint32_t, gchandle_new, (Il2CppObject * obj, bool pinned)); API_INIT(uint32_t, gchandle_new_weakref, (Il2CppObject * obj, bool track_resurrection)); API_INIT(Il2CppObject*, gchandle_get_target, (uint32_t gchandle)); API_INIT(void, gchandle_free, (uint32_t gchandle)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(void, gchandle_foreach_get_target, (void (*func)(void* data, void* userData), void* userData)); API_INIT(uint32_t, object_header_size, ()); API_INIT(uint32_t, array_object_header_size, ()); @@ -152,10 +152,17 @@ API_INIT(uint32_t, offset_of_array_length_in_array_object_header, ()); API_INIT(uint32_t, offset_of_array_bounds_in_array_object_header, ()); API_INIT(uint32_t, allocation_granularity, ()); #endif +#ifndef UNITY_2021 API_INIT(void*, unity_liveness_calculation_begin, (Il2CppClass * filter, int max_object_count, il2cpp_register_object_callback callback, void* userdata, il2cpp_WorldChangedCallback onWorldStarted, il2cpp_WorldChangedCallback onWorldStopped)); API_INIT(void, unity_liveness_calculation_end, (void* state)); +#endif +#ifdef UNITY_2021 +API_INIT(void*, il2cpp_unity_liveness_allocate_struct, (Il2CppClass * filter, int max_object_count, il2cpp_register_object_callback callback, void* userdata, il2cpp_liveness_reallocate_callback reallocate)); +API_INIT(void, il2cpp_unity_liveness_finalize, (void* state)); +API_INIT(void, il2cpp_unity_liveness_free_struct, (void* state)); +#endif API_INIT(void, unity_liveness_calculation_from_root, (Il2CppObject * root, void* state)); API_INIT(void, unity_liveness_calculation_from_statics, (void* state)); API_INIT(const Il2CppType*, method_get_return_type, (const MethodInfo* method)); @@ -230,7 +237,7 @@ API_INIT(bool, current_thread_get_frame_at, (int32_t offset, Il2CppStackFrameInf API_INIT(bool, thread_get_frame_at, (Il2CppThread * thread, int32_t offset, Il2CppStackFrameInfo* frame)); API_INIT(int32_t, current_thread_get_stack_depth, ()); API_INIT(int32_t, thread_get_stack_depth, (Il2CppThread * thread)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(void, override_stack_backtrace, (Il2CppBacktraceFunc stackBacktraceFunc)); #endif API_INIT(Il2CppObject*, type_get_object, (const Il2CppType* type)); @@ -241,7 +248,7 @@ API_INIT(bool, type_is_byref, (const Il2CppType* type)); API_INIT(uint32_t, type_get_attrs, (const Il2CppType* type)); API_INIT(bool, type_equals, (const Il2CppType* type, const Il2CppType* otherType)); API_INIT(char*, type_get_assembly_qualified_name, (const Il2CppType* type)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(bool, type_is_static, (const Il2CppType* type)); API_INIT(bool, type_is_pointer_type, (const Il2CppType* type)); #endif @@ -257,7 +264,7 @@ API_INIT(void, set_find_plugin_callback, (Il2CppSetFindPlugInCallback method)); API_INIT(void, register_log_callback, (Il2CppLogCallback method)); API_INIT(void, debugger_set_agent_options, (const char* options)); API_INIT(bool, is_debugger_attached, ()); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(void, register_debugger_agent_transport, (Il2CppDebuggerTransport * debuggerTransport)); API_INIT(bool, debug_get_method_info, (const MethodInfo*, Il2CppMethodDebugInfo* methodDebugInfo)); #endif @@ -268,7 +275,7 @@ API_INIT(Il2CppObject*, custom_attrs_get_attr, (Il2CppCustomAttrInfo * ainfo, Il API_INIT(bool, custom_attrs_has_attr, (Il2CppCustomAttrInfo * ainfo, Il2CppClass* attr_klass)); API_INIT(Il2CppArray*, custom_attrs_construct, (Il2CppCustomAttrInfo * cinfo)); API_INIT(void, custom_attrs_free, (Il2CppCustomAttrInfo * ainfo)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(void, class_set_userdata, (Il2CppClass * klass, void* userdata)); API_INIT(int, class_get_userdata_offset, ()); #endif @@ -283,7 +290,7 @@ API_INIT(bool, Class_Init, (Il2CppClass * klass)); API_INIT(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeDefinitionIndex, (TypeDefinitionIndex index)); API_INIT(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeIndex, (TypeIndex index)); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(std::string, _Type_GetName_, (const Il2CppType* type, Il2CppTypeNameFormat format)); #else API_INIT(gnu_string, _Type_GetName_, (const Il2CppType* type, Il2CppTypeNameFormat format)); @@ -362,18 +369,47 @@ Il2CppClass* il2cpp_functions::MetadataCache_GetNestedTypeFromIndex(NestedTypeIn return il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(nestedTypeIndices[index]); } -TypeDefinitionIndex il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(const Il2CppClass* typeDefinition) { +TypeDefinitionIndex il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(const Il2CppTypeDefinition* typeDefinition) { CheckS_GlobalMetadata(); - IL2CPP_ASSERT(typeDefinition->typeDefinition); + IL2CPP_ASSERT(klass); const Il2CppTypeDefinition* typeDefinitions = (const Il2CppTypeDefinition*)((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->typeDefinitionsOffset); - IL2CPP_ASSERT(typeDefinition->typeDefinition >= typeDefinitions && typeDefinition->typeDefinition < typeDefinitions + s_GlobalMetadataHeader->typeDefinitionsCount); - - ptrdiff_t index = typeDefinition->typeDefinition - typeDefinitions; + IL2CPP_ASSERT(typeDefinition->typeDefinition >= typeDefinitions && typeDefinition->typeDefinition < typeDefinitions + s_GlobalMetadataHeader->typeDefinitionsSize / sizeof(Il2CppTypeDefinition)); + ptrdiff_t index = typeDefinition - typeDefinitions; IL2CPP_ASSERT(index <= std::numeric_limits::max()); return static_cast(index); } +TypeDefinitionIndex il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(const Il2CppClass* klass) { + return MetadataCache_GetIndexForTypeDefinition(reinterpret_cast(klass->typeMetadataHandle)); +} + +GenericParameterIndex il2cpp_functions::MetadataCache_GetGenericParameterIndexFromParameter(Il2CppMetadataGenericParameterHandle handle) { + const Il2CppGenericParameter* genericParameter = reinterpret_cast(handle); + const Il2CppGenericParameter* genericParameters = (const Il2CppGenericParameter*)((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->genericParametersOffset); + + IL2CPP_ASSERT(genericParameter >= genericParameters && genericParameter < genericParameters + s_GlobalMetadataHeader->genericParametersSize / sizeof(Il2CppGenericParameter)); + + ptrdiff_t index = reinterpret_cast(genericParameter) - reinterpret_cast(genericParameters); + IL2CPP_ASSERT(index <= std::numeric_limits::max()); + return static_cast(index); +} + +const Il2CppTypeDefinition* il2cpp_functions::MetadataCache_GetTypeDefinition(Il2CppClass* klass) { + return reinterpret_cast(klass->typeMetadataHandle); +} + +GenericParameterIndex il2cpp_functions::MetadataCache_GetGenericContainerIndex(Il2CppClass* klass) { + return MetadataCache_GetTypeDefinition(klass)->genericContainerIndex; +} + +Il2CppClass* il2cpp_functions::MetadataCache_GetTypeInfoFromHandle(Il2CppMetadataTypeHandle handle) { + const Il2CppTypeDefinition* typeDefinition = reinterpret_cast(handle); + auto typeIndex = MetadataCache_GetIndexForTypeDefinition(typeDefinition); + return MetadataCache_GetTypeInfoFromTypeDefinitionIndex(typeIndex); +} + + char* il2cpp_functions::Type_GetName(const Il2CppType* type, Il2CppTypeNameFormat format) { if (!il2cpp__Type_GetName_) return nullptr; // TODO debug the ref/lifetime weirdness with _Type_GetName_ to avoid the need for explicit allocation @@ -489,7 +525,7 @@ void il2cpp_functions::Init() { logger.error("Failed to grab modloader libil2cpp.so handle: %p", imagehandle); return; } -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(init); API_SYM(init_utf16); #else @@ -519,7 +555,7 @@ void il2cpp_functions::Init() { API_SYM(bounded_array_class_get); API_SYM(array_element_size); API_SYM(assembly_get_image); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(class_for_each); #endif API_SYM(class_enum_basetype); @@ -542,7 +578,7 @@ void il2cpp_functions::Init() { API_SYM(class_get_methods); API_SYM(class_get_method_from_name); API_SYM(class_get_name); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(type_get_name_chunked); #endif API_SYM(class_get_namespace); @@ -566,7 +602,7 @@ void il2cpp_functions::Init() { API_SYM(class_get_image); API_SYM(class_get_assemblyname); API_SYM(class_get_rank); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(class_get_data_size); API_SYM(class_get_static_field_data); #endif @@ -577,7 +613,7 @@ void il2cpp_functions::Init() { API_SYM(domain_get); API_SYM(domain_assembly_open); API_SYM(domain_get_assemblies); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(raise_exception); #endif API_SYM(exception_from_name_msg); @@ -597,7 +633,7 @@ void il2cpp_functions::Init() { API_SYM(field_static_get_value); API_SYM(field_static_set_value); API_SYM(field_set_value_object); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(field_is_literal); #endif API_SYM(gc_collect); @@ -605,7 +641,7 @@ void il2cpp_functions::Init() { API_SYM(gc_disable); API_SYM(gc_enable); API_SYM(gc_is_disabled); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(gc_get_max_time_slice_ns); API_SYM(gc_set_max_time_slice_ns); API_SYM(gc_is_incremental); @@ -613,7 +649,7 @@ void il2cpp_functions::Init() { API_SYM(gc_get_used_size); API_SYM(gc_get_heap_size); API_SYM(gc_wbarrier_set_field); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(gc_has_strict_wbarriers); API_SYM(gc_set_external_allocation_tracker); API_SYM(gc_set_external_wbarrier_tracker); @@ -625,7 +661,7 @@ void il2cpp_functions::Init() { API_SYM(gchandle_new_weakref); API_SYM(gchandle_get_target); API_SYM(gchandle_free); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(gchandle_foreach_get_target); API_SYM(object_header_size); API_SYM(array_object_header_size); @@ -633,8 +669,15 @@ void il2cpp_functions::Init() { API_SYM(offset_of_array_bounds_in_array_object_header); API_SYM(allocation_granularity); #endif +#ifndef UNITY_2021 API_SYM(unity_liveness_calculation_begin); API_SYM(unity_liveness_calculation_end); +#endif +#ifdef UNITY_2021 + API_SYM(il2cpp_unity_liveness_allocate_struct); + API_SYM(il2cpp_unity_liveness_finalize); + API_SYM(il2cpp_unity_liveness_free_struct); +#endif API_SYM(unity_liveness_calculation_from_root); API_SYM(unity_liveness_calculation_from_statics); API_SYM(method_get_return_type); @@ -709,7 +752,7 @@ void il2cpp_functions::Init() { API_SYM(thread_get_frame_at); API_SYM(current_thread_get_stack_depth); API_SYM(thread_get_stack_depth); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(override_stack_backtrace); #endif API_SYM(type_get_object); @@ -720,7 +763,7 @@ void il2cpp_functions::Init() { API_SYM(type_get_attrs); API_SYM(type_equals); API_SYM(type_get_assembly_qualified_name); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(type_is_static); API_SYM(type_is_pointer_type); #endif @@ -736,7 +779,7 @@ void il2cpp_functions::Init() { API_SYM(register_log_callback); API_SYM(debugger_set_agent_options); API_SYM(is_debugger_attached); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(register_debugger_agent_transport); API_SYM(debug_get_method_info); #endif @@ -747,7 +790,7 @@ void il2cpp_functions::Init() { API_SYM(custom_attrs_has_attr); API_SYM(custom_attrs_construct); API_SYM(custom_attrs_free); -#ifdef UNITY_2019 +#if defined(UNITY_2019) || defined(UNITY_2021) API_SYM(class_set_userdata); API_SYM(class_get_userdata_offset); #endif diff --git a/src/utils/il2cpp-utils-classes.cpp b/src/utils/il2cpp-utils-classes.cpp index a7b0df98..33e3b4f5 100644 --- a/src/utils/il2cpp-utils-classes.cpp +++ b/src/utils/il2cpp-utils-classes.cpp @@ -17,7 +17,7 @@ namespace il2cpp_utils { if (genClass->cached_class) { return ClassStandardName(genClass->cached_class); } - if (genClass->typeDefinitionIndex != kTypeDefinitionIndexInvalid) { + if (il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(genClass->cached_class) != kTypeDefinitionIndexInvalid) { il2cpp_functions::Init(); auto* klass = il2cpp_functions::GenericClass_GetClass(genClass); return ClassStandardName(klass); @@ -55,8 +55,13 @@ namespace il2cpp_utils { void* myIter = nullptr; if (!methodInit) { // log results of Class::Init + #ifdef UNITY_2021 + logger.warning("klass->initialized: %i, init_pending: %i, initialized_and_no_error: %i, initializationExceptionGCHandle: %Xll", + klass->initialized, klass->init_pending, klass->initialized_and_no_error, klass->initializationExceptionGCHandle); + #else logger.warning("klass->initialized: %i, init_pending: %i, has_initialization_error: %i, initializationExceptionGCHandle: %Xll", klass->initialized, klass->init_pending, klass->has_initialization_error, klass->initializationExceptionGCHandle); + #endif auto* m1 = il2cpp_functions::class_get_methods(klass, &myIter); // attempt again to initialize the method data if (klass->method_count && !klass->methods) { logger.error("Class::Init and class_get_methods failed to initialize klass->methods! class_get_methods returned: %p", @@ -67,11 +72,13 @@ namespace il2cpp_utils { logger.debug("Pointer: %p", klass); logger.debug("Type Token: %i", il2cpp_functions::class_get_type_token(klass)); - auto typeDefIdx = klass->generic_class ? klass->generic_class->typeDefinitionIndex : il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(klass); + auto typeDefIdx = il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(klass->generic_class ? klass->generic_class->cached_class : klass); logger.debug("TypeDefinitionIndex: %i", typeDefIdx); // Repair the typeDefinition value if it was null but we found one + #ifndef UNITY_2021 if (!klass->typeDefinition && typeDefIdx > 0) klass->typeDefinition = il2cpp_functions::MetadataCache_GetTypeDefinitionFromIndex(typeDefIdx); logger.debug("Type definition: %p", klass->typeDefinition); + #endif logger.debug("Assembly Name: %s", il2cpp_functions::class_get_assemblyname(klass)); @@ -96,9 +103,15 @@ namespace il2cpp_utils { // Therefore, this code makes only the following assumptions: // 1. If is_generic is set, then genericContainerIndex was also intentionally set (even if it's 0) and is not -1 (invalid) // 2. Even if is_generic wasn't set, a positive genericContainerIndex was intentionally set that way and is a valid index. - if (klass->is_generic || klass->genericContainerIndex > 0) { - auto* genContainer = il2cpp_functions::MetadataCache_GetGenericContainerFromIndex(klass->genericContainerIndex); - logger.debug("genContainer: idx %i, ownerIndex: %i, is_method: %i", klass->genericContainerIndex, genContainer->ownerIndex, genContainer->is_method); + #ifdef UNITY_2021 + auto klassGenericContainerIndex = il2cpp_functions::MetadataCache_GetGenericContainerIndex(klass); + #else + auto klassGenericContainerIndex = klass->genericContainerIndex; + #endif + + if (klass->is_generic || klassGenericContainerIndex > 0) { + auto* genContainer = il2cpp_functions::MetadataCache_GetGenericContainerFromIndex(klassGenericContainerIndex); + logger.debug("genContainer: idx %i, ownerIndex: %i, is_method: %i", klassGenericContainerIndex, genContainer->ownerIndex, genContainer->is_method); if (genContainer->ownerIndex != typeDefIdx) { logger.error("genContainer ownerIndex mismatch!"); } @@ -114,7 +127,7 @@ namespace il2cpp_utils { } } } else { - logger.debug("genericContainerIndex: %i", klass->genericContainerIndex); + logger.debug("genericContainerIndex: %i", klassGenericContainerIndex); } logger.debug("%i =========METHODS=========", indent); @@ -158,7 +171,7 @@ namespace il2cpp_utils { } std::string genClassName = GenericClassStandardName(genClass); - auto* typeDefClass = il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(genClass->typeDefinitionIndex); + auto* typeDefClass = il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(genClass->cached_class)); if (!typeDefClass) continue; classToGenericClassMap[typeDefClass][genClassName.c_str()] = genClass; @@ -194,14 +207,16 @@ namespace il2cpp_utils { if (img->nameToClassHashTable == nullptr) { logger.debug("Assembly's nameToClassHashTable is empty. Populating it instead."); - img->nameToClassHashTable = new Il2CppNameToTypeDefinitionIndexHashTable(); + img->nameToClassHashTable = new Il2CppNameToTypeHandleHashTable(); + auto metadata = reinterpret_cast(img->metadataHandle); + for (uint32_t index = 0; index < img->typeCount; index++) { - TypeDefinitionIndex typeIndex = img->typeStart + index; + TypeDefinitionIndex typeIndex = metadata->typeStart + index; AddTypeToNametoClassHashTable(img, typeIndex); } for (uint32_t index = 0; index < img->exportedTypeCount; index++) { - auto typeIndex = il2cpp_functions::MetadataCache_GetExportedTypeFromIndex(img->exportedTypeStart + index); + auto typeIndex = il2cpp_functions::MetadataCache_GetExportedTypeFromIndex(metadata->exportedTypeStart + index); if (typeIndex != kTypeIndexInvalid) AddTypeToNametoClassHashTable(img, typeIndex); } @@ -213,7 +228,7 @@ namespace il2cpp_utils { if (strncmp(classPrefix.data(), itr->first.key.second, classPrefix.length()) == 0) { // Starts with! // Convert TypeDefinitionIndex --> class - auto klazz = il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(itr->second); + auto klazz = il2cpp_functions::MetadataCache_GetTypeInfoFromHandle(itr->second); matches[ClassStandardName(klazz)] = klazz; } } @@ -242,8 +257,9 @@ namespace il2cpp_utils { if (img != il2cpp_functions::get_corlib()) AddNestedTypesToNametoClassHashTable(img, typeDefinition); - - img->nameToClassHashTable->insert(std::make_pair(std::make_pair(il2cpp_functions::MetadataCache_GetStringFromIndex(typeDefinition->namespaceIndex), il2cpp_functions::MetadataCache_GetStringFromIndex(typeDefinition->nameIndex)), index)); + // GlobalMetadata.cpp shows Il2CppMetadataTypeHandle == Il2CppTypeDefinition const* + auto handle = reinterpret_cast(typeDefinition); + img->nameToClassHashTable->insert(std::make_pair(std::make_pair(il2cpp_functions::MetadataCache_GetStringFromIndex(typeDefinition->namespaceIndex), il2cpp_functions::MetadataCache_GetStringFromIndex(typeDefinition->nameIndex)), handle)); } void AddNestedTypesToNametoClassHashTable(const Il2CppImage* img, const Il2CppTypeDefinition* typeDefinition) { @@ -254,16 +270,19 @@ namespace il2cpp_utils { } } - void AddNestedTypesToNametoClassHashTable(Il2CppNameToTypeDefinitionIndexHashTable* hashTable, const char *namespaze, const std::string& parentName, Il2CppClass *klass) { + void AddNestedTypesToNametoClassHashTable(Il2CppNameToTypeHandleHashTable* hashTable, const char *namespaze, const std::string& parentName, Il2CppClass *klass) { il2cpp_functions::Init(); std::string name = parentName + "/" + klass->name; char *pName = (char*)gc_alloc_specific(name.size() + 1 * sizeof(char)); strlcpy(pName, name.c_str(), name.length() + 1); - hashTable->insert(std::make_pair(std::make_pair(namespaze, (const char*)pName), il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(klass))); + auto typeDefinition = il2cpp_functions::MetadataCache_GetTypeDefinition(klass); + // GlobalMetadata.cpp shows Il2CppMetadataTypeHandle == Il2CppTypeDefinition const* + auto handle = reinterpret_cast(typeDefinition); + hashTable->insert(std::make_pair(std::make_pair(namespaze, (const char*)pName), handle)); void *iter = NULL; while (Il2CppClass *nestedClass = il2cpp_functions::class_get_nested_types(klass, &iter)) AddNestedTypesToNametoClassHashTable(hashTable, namespaze, name, nestedClass); } -} \ No newline at end of file +} diff --git a/src/utils/il2cpp-utils-exceptions.cpp b/src/utils/il2cpp-utils-exceptions.cpp index 5088351d..c0494900 100644 --- a/src/utils/il2cpp-utils-exceptions.cpp +++ b/src/utils/il2cpp-utils-exceptions.cpp @@ -18,7 +18,7 @@ namespace il2cpp_utils { return msg; } - #ifdef UNITY_2019 + #if defined(UNITY_2019) || defined(UNITY_2021) [[noreturn]] void raise(const Il2CppException* exp) { CRASH_UNLESS(exp); il2cpp_functions::raise_exception(const_cast(exp)); @@ -65,4 +65,4 @@ namespace il2cpp_utils { void RunMethodException::log_backtrace() const { log_backtrace_full(stacktrace_buffer, stacktrace_size); } -} \ No newline at end of file +} diff --git a/src/utils/il2cpp-utils.cpp b/src/utils/il2cpp-utils.cpp index 38345203..f5018f1c 100644 --- a/src/utils/il2cpp-utils.cpp +++ b/src/utils/il2cpp-utils.cpp @@ -42,7 +42,8 @@ namespace il2cpp_utils { if (method->parameters_count != argTypes.size()) { return false; } - auto genCount = (method->is_generic && !method->is_inflated) ? method->genericContainer->type_argc : 0; + auto genContainer = reinterpret_cast(method->genericContainerHandle); + auto genCount = (method->is_generic && !method->is_inflated) ? genContainer->type_argc : 0; if ((size_t)genCount != genTypes.size()) { logger.warning("Potential method match had wrong number of generics %i (expected %lu)", genCount, genTypes.size()); @@ -50,9 +51,9 @@ namespace il2cpp_utils { } // TODO: supply boolStrictMatch and use type_equals instead of IsConvertibleFrom if supplied? for (decltype(method->parameters_count) i = 0; i < method->parameters_count; i++) { - auto* paramType = method->parameters[i].parameter_type; + auto* paramType = method->parameters[i]; if (paramType->type == IL2CPP_TYPE_MVAR) { - auto genIdx = paramType->data.genericParameterIndex - method->genericContainer->genericParameterStart; + auto genIdx = il2cpp_functions::MetadataCache_GetGenericParameterIndexFromParameter(paramType->data.genericParameterHandle) - genContainer->genericParameterStart; if (genIdx < 0) { logger.warning("Extracted invalid genIdx %i from parameter %i", genIdx, i); } else if (genIdx >= genCount) { @@ -250,11 +251,11 @@ namespace il2cpp_utils { } obj->klass = const_cast(klass); // Call cctor, we don't bother making a new thread for the type initializer. BE WARNED! - if (klass->has_cctor && !klass->cctor_finished && !klass->cctor_started) { + if (klass->has_cctor && !klass->cctor_finished_or_no_cctor && !klass->cctor_started) { obj->klass->cctor_started = true; auto* m = RET_0_UNLESS(logger, FindMethodUnsafe(klass, ".cctor", 0)); RET_0_UNLESS(logger, il2cpp_utils::RunStaticMethodUnsafe(m)); - obj->klass->cctor_finished = true; + obj->klass->cctor_finished_or_no_cctor = true; } return obj; } @@ -269,7 +270,7 @@ namespace il2cpp_utils { } obj->klass = const_cast(klass); // Call cctor, we don't bother making a new thread for the type initializer. BE WARNED! - if (klass->has_cctor && !klass->cctor_finished && !klass->cctor_started) { + if (klass->has_cctor && !klass->cctor_finished_or_no_cctor && !klass->cctor_started) { obj->klass->cctor_started = true; auto* m = FindMethodUnsafe(klass, ".cctor", 0); if (!m) { @@ -278,7 +279,7 @@ namespace il2cpp_utils { if (!il2cpp_utils::RunStaticMethodUnsafe(m)) { throw exceptions::StackTraceException("Failed to run .cctor method!"); } - obj->klass->cctor_finished = true; + obj->klass->cctor_finished_or_no_cctor = true; } return obj; } From f30304b2a501f8147fd0ca34d194d5fa53cc0b06 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Mon, 11 Dec 2023 20:49:32 +0100 Subject: [PATCH 02/89] Fix debug binary path --- .github/workflows/build-ndk.yml | 2 +- .github/workflows/publish.yml | 2 +- CMakeLists.txt | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ndk.yml b/.github/workflows/build-ndk.yml index 5ec09a24..024fca31 100644 --- a/.github/workflows/build-ndk.yml +++ b/.github/workflows/build-ndk.yml @@ -71,7 +71,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: debug_${{ steps.libname.outputs.NAME }} - path: ./build/debug_${{ steps.libname.outputs.NAME }} + path: ./build/debug/${{ steps.libname.outputs.NAME }} if-no-files-found: error # TODO: Add auto-populating releases, auto update versions, auto publish package on release diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2c007ddb..14313d22 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -78,6 +78,6 @@ jobs: tag_name: ${{ github.event.inputs.version }} files: | ./build/${{ steps.libname.outputs.NAME }} - ./build/debug_${{ steps.libname.outputs.NAME }} + ./build/debug/${{ steps.libname.outputs.NAME }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CMakeLists.txt b/CMakeLists.txt index b5fc5cfd..fb893bfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,7 +116,11 @@ add_custom_command(TARGET ${COMPILE_ID} POST_BUILD COMMENT "Strip debug symbols done on final binary.") add_custom_command(TARGET ${COMPILE_ID} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E rename lib${COMPILE_ID}.so debug_lib${COMPILE_ID}.so + COMMAND ${CMAKE_COMMAND} -E make_directory debug + COMMENT "Create the debug dir" +) +add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rename lib${COMPILE_ID}.so debug/lib${COMPILE_ID}.so COMMENT "Rename the lib to debug_ since it has debug symbols" ) From ff320e9662dc58cbcae2640a0ea7b03ae461971d Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Mon, 11 Dec 2023 20:49:53 +0100 Subject: [PATCH 03/89] remove accidental include --- shared/utils/il2cpp-utils-classes.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/utils/il2cpp-utils-classes.hpp b/shared/utils/il2cpp-utils-classes.hpp index f478cabe..461bd78a 100644 --- a/shared/utils/il2cpp-utils-classes.hpp +++ b/shared/utils/il2cpp-utils-classes.hpp @@ -1,6 +1,5 @@ #pragma once -#include "utils/typedefs.h" #pragma pack(push) #include "logging.hpp" From b5a3f8298c3c3582e3034f0d6a687ac46c4fe618 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 12 Dec 2023 00:10:20 +0100 Subject: [PATCH 04/89] Add concept usage to the main places they should be at --- shared/utils/base-wrapper-type.hpp | 12 +- shared/utils/byref.hpp | 4 +- shared/utils/enum-wrapper-type.hpp | 35 +++++ shared/utils/hooking.hpp | 8 +- shared/utils/il2cpp-type-check.hpp | 12 +- shared/utils/il2cpp-utils-methods.hpp | 12 +- shared/utils/size-concepts.hpp | 63 +++++++++ shared/utils/type-concepts.hpp | 196 ++++++++++++++++++++++++++ shared/utils/typedefs-array.hpp | 8 +- shared/utils/typedefs-list.hpp | 3 + shared/utils/typedefs-object.hpp | 6 +- shared/utils/typedefs-string.hpp | 14 +- shared/utils/value-wrapper-type.hpp | 48 +++++++ src/tests/hook-tests.cpp | 4 +- 14 files changed, 384 insertions(+), 41 deletions(-) create mode 100644 shared/utils/enum-wrapper-type.hpp create mode 100644 shared/utils/size-concepts.hpp create mode 100644 shared/utils/type-concepts.hpp create mode 100644 shared/utils/value-wrapper-type.hpp diff --git a/shared/utils/base-wrapper-type.hpp b/shared/utils/base-wrapper-type.hpp index dd37889f..772126ad 100644 --- a/shared/utils/base-wrapper-type.hpp +++ b/shared/utils/base-wrapper-type.hpp @@ -3,16 +3,7 @@ #include #include #include "il2cpp-type-check.hpp" - -namespace il2cpp_utils { - template - /// @brief A concept depicting if a type is a wrapper type. - // TODO: Make this use a static creation method instead of a constructor - concept has_il2cpp_conversion = requires (T t) { - {t.convert()} -> std::same_as; - std::is_constructible_v; - }; -} +#include "type-concepts.hpp" namespace bs_hook { /// @brief Represents the most basic wrapper type. @@ -28,6 +19,7 @@ namespace bs_hook { return const_cast(instance); } + Il2CppObject* operator ->() const noexcept { return const_cast(static_cast(instance)); } operator Il2CppObject*() const noexcept { return const_cast(static_cast(instance)); } protected: diff --git a/shared/utils/byref.hpp b/shared/utils/byref.hpp index 9e5a8701..fe598f61 100644 --- a/shared/utils/byref.hpp +++ b/shared/utils/byref.hpp @@ -1,5 +1,6 @@ #pragma once #include "il2cpp-type-check.hpp" +#include "type-concepts.hpp" /// @brief Represents a byref parameter. /// This is REQUIRED for codegen invokes, as RunMethodThrow can't tell the difference between a reference parameter and a byref on constexpr time. @@ -33,6 +34,7 @@ struct ByRef { } static_assert(sizeof(T*) == sizeof(void*)); }; +MARK_GEN_REF_T(ByRef); // Type specializations for byref specifics // We do not need il2cpp_no_arg_class specialization for ByRef, since it will never get to that point. @@ -56,4 +58,4 @@ struct ::il2cpp_utils::il2cpp_type_check::il2cpp_arg_type> { }; // Creates a ByRef type to wrap a reference -#define byref(...) (ByRef(__VA_ARGS__)) \ No newline at end of file +#define byref(...) (ByRef(__VA_ARGS__)) diff --git a/shared/utils/enum-wrapper-type.hpp b/shared/utils/enum-wrapper-type.hpp new file mode 100644 index 00000000..70ffc8fb --- /dev/null +++ b/shared/utils/enum-wrapper-type.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "value-wrapper-type.hpp" + +namespace bs_hook { + // 0 special case, but otherwise the size should be an actual amount of bytes it could be + template + requires(sz == 0x0 || sz == 0x1 || sz == 0x2 || sz == 0x4 || sz == 0x8) + struct EnumTypeWrapper : public ValueTypeWrapper { + static constexpr auto VALUE_TYPE_SIZE = ValueTypeWrapper::VALUE_TYPE_SIZE; + using ValueTypeWrapper::ValueTypeWrapper; + + constexpr EnumTypeWrapper() = default; + ~EnumTypeWrapper() = default; + + constexpr EnumTypeWrapper(EnumTypeWrapper&&) = default; + constexpr EnumTypeWrapper(EnumTypeWrapper const&) = default; + + constexpr EnumTypeWrapper& operator=(EnumTypeWrapper&&) = default; + constexpr EnumTypeWrapper& operator=(EnumTypeWrapper const&) = default; + }; + + /// @brief struct to pass a pointer to an enum into a method + struct EnumPtr : public VTPtr { + template + EnumPtr(EnumTypeWrapper& e) : VTPtr(e) {}; + + explicit EnumPtr(void* i) : VTPtr(i) {}; + }; +} + +template +struct ::il2cpp_utils::ValueTypeTrait<::bs_hook::EnumTypeWrapper> { + constexpr static bool value = true; +}; diff --git a/shared/utils/hooking.hpp b/shared/utils/hooking.hpp index 01c4c9d4..9530fce8 100644 --- a/shared/utils/hooking.hpp +++ b/shared/utils/hooking.hpp @@ -454,7 +454,7 @@ struct Hook_##name_ { \ using funcType = retval (*)(__VA_ARGS__); \ static_assert(std::is_same_v::funcType>, "Hook method signature does not match!"); \ constexpr static const char* name() { return #name_; } \ - static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter::get(); } \ + static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo(); } \ static funcType* trampoline() { return &name_; } \ static inline retval (*name_)(__VA_ARGS__) = nullptr; \ static funcType hook() { return &::Hooking::HookCatchWrapper<&hook_##name_, funcType>::wrapper; } \ @@ -469,7 +469,7 @@ struct Hook_##name_ { \ using funcType = retval (*)(__VA_ARGS__); \ static_assert(std::is_same_v::funcType>, "Hook method signature does not match!"); \ constexpr static const char* name() { return #name_; } \ - static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter::get(); } \ + static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo(); } \ static funcType* trampoline() { return &name_; } \ static inline retval (*name_)(__VA_ARGS__) = nullptr; \ static funcType hook() { return hook_##name_; } \ @@ -542,7 +542,7 @@ struct Hook_##name_ { \ using funcType = decltype(&::Hooking::HookWrapperCompose<&hook_##name_>::wrapper); \ /* static_assert(std::is_same_v::funcType>, "Hook method signature does not match!"); */ \ constexpr static const char* name() { return #name_; } \ - static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter::get(); } \ + static const MethodInfo* getInfo() { return ::il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo(); } \ static funcType* trampoline() { return &orig_base; } \ static inline funcType orig_base = nullptr; \ template \ @@ -691,4 +691,4 @@ void InstallHookDirect(L& logger, void* dst) { // TODO: Not yet implemented #define UNINSTALL_HOOK_DIRECT(logger, name, addr) void -} \ No newline at end of file +} diff --git a/shared/utils/il2cpp-type-check.hpp b/shared/utils/il2cpp-type-check.hpp index 034a6956..54746566 100644 --- a/shared/utils/il2cpp-type-check.hpp +++ b/shared/utils/il2cpp-type-check.hpp @@ -117,9 +117,9 @@ namespace il2cpp_utils { template #ifndef BS_HOOK_USE_CONCEPTS - struct il2cpp_no_arg_class && T::IS_VALUE_TYPE>> { + struct il2cpp_no_arg_class && T::__IL2CPP_IS_VALUE_TYPE>> { #else - requires (std::is_base_of_v && T::IS_VALUE_TYPE) + requires (std::is_base_of_v && T::__IL2CPP_IS_VALUE_TYPE) struct il2cpp_no_arg_class { #endif // TODO: make this work on any class with a `using declaring_type`, then remove NestedType @@ -162,9 +162,9 @@ namespace il2cpp_utils { // For non-value types, forward accordingly. Should only apply to T*s that have these properties. template #ifndef BS_HOOK_USE_CONCEPTS - struct il2cpp_no_arg_class && !T::IS_VALUE_TYPE>> { + struct il2cpp_no_arg_class && !T::__IL2CPP_IS_VALUE_TYPE>> { #else - requires (std::is_base_of_v && !T::IS_VALUE_TYPE) + requires (std::is_base_of_v && !T::__IL2CPP_IS_VALUE_TYPE) struct il2cpp_no_arg_class { #endif // TODO: make this work on any class with a `using declaring_type`, then remove NestedType @@ -334,7 +334,7 @@ namespace il2cpp_utils { }; template class S> - requires (S::IS_VALUE_TYPE && has_get::declaring_type>>) + requires (S::__IL2CPP_IS_VALUE_TYPE && has_get::declaring_type>>) struct il2cpp_no_arg_class> { static inline Il2CppClass* get() { // Resolve our declaring type @@ -364,7 +364,7 @@ namespace il2cpp_utils { }; template class S> - requires (!S::IS_VALUE_TYPE && has_get::declaring_type>>) + requires (!S::__IL2CPP_IS_VALUE_TYPE && has_get::declaring_type>>) struct il2cpp_no_arg_class*> { static inline Il2CppClass* get() { // Resolve our declaring type diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index ca21dae7..50d5e1a7 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -353,7 +353,7 @@ namespace il2cpp_utils { } if constexpr (checkTypes && sizeof...(TArgs) > 0) { - std::array types{ExtractType(params)...}; + std::array types{::il2cpp_utils::ExtractType(params)...}; if (!ParameterMatch(method, types)) { throw RunMethodException("Parameters do not match!", method); } @@ -491,7 +491,7 @@ namespace il2cpp_utils { CRASH_UNLESS(method); if constexpr (checkTypes && sizeof...(TArgs) > 0) { - std::array types{ExtractType(params)...}; + std::array types{::il2cpp_utils::ExtractType(params)...}; if (!ParameterMatch(method, types)) { throw RunMethodException("Parameters do not match!", method); } @@ -565,7 +565,7 @@ namespace il2cpp_utils { RET_NULLOPT_UNLESS(logger, method); if constexpr (checkTypes && sizeof...(TArgs) > 0) { - std::array types{ExtractType(params)...}; + std::array types{::il2cpp_utils::ExtractType(params)...}; RET_NULLOPT_UNLESS(logger, ParameterMatch(method, types)); } @@ -629,7 +629,7 @@ namespace il2cpp_utils { RunMethod(T&& classOrInstance, ::std::string_view methodName, TArgs&& ...params) { static auto& logger = getLogger(); if constexpr (checkTypes) { - std::array types{ExtractType(params)...}; + std::array types{::il2cpp_utils::ExtractType(params)...}; auto* method = RET_NULLOPT_UNLESS(logger, FindMethod(classOrInstance, NoArgClass(), methodName, types)); return RunMethod(classOrInstance, method, params...); } @@ -676,7 +676,7 @@ namespace il2cpp_utils { template ::std::optional RunGenericMethod(T&& classOrInstance, ::std::string_view methodName, ::std::vector genTypes, TArgs&& ...params) noexcept { static auto& logger = getLogger(); - std::array types{ExtractType(params)...}; + std::array types{::il2cpp_utils::ExtractType(params)...}; auto* info = RET_NULLOPT_UNLESS(logger, FindMethod(classOrInstance, NoArgClass(), methodName, genTypes, types)); return RunGenericMethod(classOrInstance, info, genTypes, params...); @@ -730,7 +730,7 @@ namespace il2cpp_utils { obj = RET_NULLOPT_UNLESS(logger, createManual(klass)); } // runtime_invoke constructor with right type(s) of arguments, return null if constructor errors - std::array types{ExtractType(args)...}; + std::array types{::il2cpp_utils::ExtractType(args)...}; auto* method = RET_NULLOPT_UNLESS(logger, FindMethod(klass, ".ctor", types)); RET_NULLOPT_UNLESS(logger, RunMethod(obj, method, args...)); return FromIl2CppObject(obj); diff --git a/shared/utils/size-concepts.hpp b/shared/utils/size-concepts.hpp new file mode 100644 index 00000000..ba27df6a --- /dev/null +++ b/shared/utils/size-concepts.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "type-concepts.hpp" + +namespace il2cpp_utils { + /// @brief any builtins will just have the same size as in cpp + template + struct il2cpp_size { + static constexpr auto value = sizeof(T); + }; + + /// @brief the expectation is that val types do match their size + template<::il2cpp_utils::il2cpp_value_type T> + struct il2cpp_size { + static constexpr auto value = T::VALUE_TYPE_SIZE; + }; + + /// @brief the expectation is that ref types are not going to match size + template<::il2cpp_utils::il2cpp_reference_type T> + struct il2cpp_size { + static constexpr auto value = sizeof(void*); + }; + + /// @brief shorthand to get the size value + template + static constexpr auto il2cpp_size_v = il2cpp_size::value; + + /// @brief extracting the instance size of an il2cpp type wrapper + template + struct il2cpp_instance_size; + + /// @brief ref types should declare a static member __IL2CPP_REFERENCE_TYPE_SIZE that contains their instance size + template<::il2cpp_utils::il2cpp_reference_type_wrapper T> + struct il2cpp_instance_size { + static constexpr auto value = T::__IL2CPP_REFERENCE_TYPE_SIZE; + }; + + /// @brief ref types should declare a static member __IL2CPP_REFERENCE_TYPE_SIZE that contains their instance size + template<::il2cpp_utils::il2cpp_reference_type_pointer T> + struct il2cpp_instance_size { + static constexpr auto value = std::remove_pointer_t::__IL2CPP_REFERENCE_TYPE_SIZE; + }; + + /// @brief VT wrappers always define their instance size in the lowermost type + template<::il2cpp_utils::il2cpp_value_type T> + struct il2cpp_instance_size { + static constexpr auto value = T::VALUE_TYPE_SIZE; + }; + + /// @brief type trait that checks whether the il2cpp size matches the cpp size + template + struct is_il2cpp_size_safe { + static constexpr bool value = il2cpp_size_v == sizeof(T); + }; + + /// @brief shorthand to get the size check value + template + static constexpr bool is_il2cpp_size_safe_v = is_il2cpp_size_safe::value; +} + +#define il2cpp_sizeof(...) (::il2cpp_utils::il2cpp_size<__VA_ARGS__>::value) +#define il2cpp_safe(...) (::il2cpp_utils::is_il2cpp_size_safe_v<__VA_ARGS__>) +#define il2cpp_instance_sizeof(...) (::il2cpp_utils::il2cpp_instance_size<__VA_ARGS__>::value) diff --git a/shared/utils/type-concepts.hpp b/shared/utils/type-concepts.hpp new file mode 100644 index 00000000..46b01fcb --- /dev/null +++ b/shared/utils/type-concepts.hpp @@ -0,0 +1,196 @@ +#pragma once + +#include +#include +#include + +namespace il2cpp_utils { + template + concept convertible_to = std::is_convertible_v; + + template + /// @brief A concept depicting if a type is a wrapper type. + // TODO: Make this use a static creation method instead of a constructor + concept has_il2cpp_conversion = requires (T t) { + {t.convert()} -> convertible_to; + std::is_constructible_v; + }; + +#pragma region value marker + /// @brief not all types will have the value marker + template + concept has_value_marker = requires { + { T::__IL2CPP_IS_VALUE_TYPE } -> convertible_to; + }; + + /// @brief all types will evaluate false for the check + template + struct value_marker_check { + constexpr static bool value = false; + }; + + /// @brief types that have the marker will actually evaluate the check + template + struct value_marker_check { + constexpr static bool value = T::__IL2CPP_IS_VALUE_TYPE == check; + }; + + template + constexpr bool value_marker_check_v = value_marker_check::value; + +#pragma endregion // value marker + +#pragma region val type + template + concept il2cpp_value_type_requirements = requires(T const& t) { + requires(std::is_same_v>); + requires(std::is_constructible_v); + requires(il2cpp_utils::value_marker_check_v); + }; + + /// @brief mark a T explicitly as value type, default is false + template struct ValueTypeTrait { + constexpr static bool value = false; + }; + + /// @brief anything that matches the type requirements should also be allowed to be a value type + template struct ValueTypeTrait { + constexpr static bool value = true; + }; + + /// @brief mark a generic T explicitly as value type without requiring its TArgs to be fully realized, defaults to false + template class T> struct GenValueTypeTrait { + constexpr static bool value = false; + }; + + /// @brief non generics should return false + template struct ValueDecompose { + constexpr static bool value = false; + }; + + /// @brief generics should fall back to the generic type trait + template class T, class... TArgs> struct ValueDecompose> { + constexpr static bool value = GenValueTypeTrait::value; + }; + + /// @brief concept that lets us know whether what we are dealing with is a value type or not + template + concept il2cpp_value_type = ValueDecompose::value || ValueTypeTrait::value; +#pragma endregion // val type + +#pragma region ref type + // https://godbolt.org/z/4vveWa46Y + // Standard ref type concept + // Note that this requires that type T is full instantiated + // We want to ALSO support a case where that's not the case + template + concept il2cpp_reference_type_wrapper_requirements = requires(T const& t) { + { t.convert() } -> convertible_to; + + // ensure these constructors exist + requires std::is_constructible_v; + requires std::is_constructible_v; + + requires(::il2cpp_utils::value_marker_check_v); + }; + + /// @brief mark a T explicitly as reference type, default is false + template struct RefTypeTrait; + + /// @brief anything that matches the type requirements should also be allowed to be a reference type + template struct RefTypeTrait { + constexpr static bool value = true; + }; + + /// @brief mark a generic T explicitly as reference type without requiring its TArgs to be fully realized, defaults to false + template class T> struct GenRefTypeTrait; + + /// @brief non generics should return false + template struct RefDecompose { + constexpr static bool value = false; + }; + + /// @brief generics should fall back to the generic type trait + template class T, class... TArgs> struct RefDecompose> { + constexpr static bool value = GenRefTypeTrait::value; + }; + + /// @brief concept that lets us know whether what we are dealing with is a reference type or not + template + concept il2cpp_reference_type_wrapper = RefDecompose::value || RefTypeTrait::value; +#pragma endregion // ref type + +#pragma region reference pointer type + /// @brief mark a T* explicitly as reference type, default is false + template struct RefPtrTypeTrait; + + /// @brief if reference type is said to be true we can say this is true + template + requires(value_marker_check_v) + struct RefPtrTypeTrait { + static constexpr bool value = true; + }; + + /// @brief mark a generic T explicitly as reference type without requiring its TArgs to be fully realized, defaults to false + template class T> struct GenRefPtrTypeTrait; + + /// @brief non generics should return false + template struct RefPtrDecompose { + constexpr static bool value = false; + }; + + /// @brief generics should fall back to the generic type trait + template class T, class... TArgs> struct RefPtrDecompose> { + constexpr static bool value = GenRefPtrTypeTrait::value; + }; + + /// @brief concept that lets us know whether what we are dealing with is a reference type pointer or not + template + concept il2cpp_reference_type_pointer = std::is_pointer_v && (RefPtrDecompose>::value || RefPtrTypeTrait>::value); + +#pragma endregion // reference pointer type + + /// any wrapper or pointer that matches the above requirements to be an il2cpp type is allowed + template + concept il2cpp_reference_type = il2cpp_reference_type_wrapper || il2cpp_reference_type_pointer; + + template + static constexpr inline void* il2cpp_reference_type_value(T&& t) { + if constexpr (il2cpp_reference_type_wrapper) { + return t.convert(); + } else if constexpr (il2cpp_reference_type_pointer) { + return t; + } + } + +#define MARK_REF_T(...) \ + template<> struct ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } + +#define MARK_REF_PTR_T(...) \ + template<> struct ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = true; } + +#define MARK_VAL_T(...) \ + template<> struct ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } + +#define MARK_GEN_REF_T(...) \ + template<> struct ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } + +#define MARK_GEN_REF_PTR_T(...) \ + template<> struct ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = true; } + +#define MARK_GEN_VAL_T(...) \ + template<> struct ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } + +} diff --git a/shared/utils/typedefs-array.hpp b/shared/utils/typedefs-array.hpp index 00ce852f..58d684f5 100644 --- a/shared/utils/typedefs-array.hpp +++ b/shared/utils/typedefs-array.hpp @@ -3,6 +3,7 @@ #include #include #include "il2cpp-type-check.hpp" +#include "type-concepts.hpp" #include #if __has_include() @@ -238,6 +239,7 @@ struct Array : public Il2CppArray return std::span(const_cast(values), Length()); } }; +MARK_GEN_REF_PTR_T(Array); template struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class*> { @@ -433,7 +435,7 @@ struct ArrayW { T First() { if (Length() > 0) return val->values[0]; - else + else throw ArrayException(this, "First called on empty array!"); } @@ -448,7 +450,7 @@ struct ArrayW { T Last() { if (Length() > 0) return val->values[Length() - 1]; - else + else throw ArrayException(this, "Last called on empty array!"); } @@ -501,6 +503,8 @@ struct ArrayW { private: Ptr val; }; +MARK_GEN_REF_T(ArrayW); + static_assert(il2cpp_utils::has_il2cpp_conversion*>>); template struct ::il2cpp_utils::il2cpp_type_check::need_box> { diff --git a/shared/utils/typedefs-list.hpp b/shared/utils/typedefs-list.hpp index 25abf506..c823836b 100644 --- a/shared/utils/typedefs-list.hpp +++ b/shared/utils/typedefs-list.hpp @@ -4,6 +4,7 @@ #include #include #include "il2cpp-utils-methods.hpp" +#include "type-concepts.hpp" template*> struct ListWrapper { @@ -118,6 +119,8 @@ struct ListWrapper { private: Ptr ptr; }; +MARK_GEN_REF_T(ListWrapper); +MARK_GEN_REF_PTR_T(List); // ListW for the win, just implicitly template*> diff --git a/shared/utils/typedefs-object.hpp b/shared/utils/typedefs-object.hpp index d4dea9eb..52a29409 100644 --- a/shared/utils/typedefs-object.hpp +++ b/shared/utils/typedefs-object.hpp @@ -1,5 +1,7 @@ #pragma once +#include "type-concepts.hpp" + #ifdef HAS_CODEGEN #ifdef USE_CODEGEN_FIELDS @@ -36,4 +38,6 @@ typedef struct Il2CppObject }; MonitorData *monitor; } Il2CppObject; -#endif \ No newline at end of file + +MARK_REF_PTR_T(Il2CppObject); +#endif diff --git a/shared/utils/typedefs-string.hpp b/shared/utils/typedefs-string.hpp index d7e1cbde..90012a8c 100644 --- a/shared/utils/typedefs-string.hpp +++ b/shared/utils/typedefs-string.hpp @@ -8,6 +8,7 @@ #include "il2cpp-type-check.hpp" #include "il2cpp-utils-exceptions.hpp" #include "utils-functions.h" +#include "type-concepts.hpp" struct UseBeforeInitError : il2cpp_utils::exceptions::StackTraceException { @@ -53,16 +54,10 @@ template struct ConstString { // Manually allocated string, dtor destructs in place ConstString(const char (&st)[sz]) { - if (il2cpp_functions::initialized) { - klass = il2cpp_functions::defaults->string_class; - } length = sz - 1; il2cpp_utils::detail::convstr(st, chars, sz - 1); } constexpr ConstString(const char16_t (&st)[sz]) noexcept { - if (il2cpp_functions::initialized) { - klass = il2cpp_functions::defaults->string_class; - } length = sz - 1; for (int i = 0; i < sz - 1; i++) { chars[i] = st[i]; @@ -125,9 +120,9 @@ struct ConstString { private: void* klass = nullptr; - void* monitor; - int length; - char16_t chars[sz]; + void* monitor = nullptr; + int length = 0; + char16_t chars[sz] = {}; }; struct StringW { @@ -249,6 +244,7 @@ struct StringW { private: Il2CppString* inst; }; +MARK_REF_T(StringW); template requires(!std::is_constructible_v && (std::is_constructible_v || std::is_constructible_v)) diff --git a/shared/utils/value-wrapper-type.hpp b/shared/utils/value-wrapper-type.hpp new file mode 100644 index 00000000..068def84 --- /dev/null +++ b/shared/utils/value-wrapper-type.hpp @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include "type-concepts.hpp" + +namespace bs_hook { + template + struct ValueTypeWrapper { + static constexpr auto VALUE_TYPE_SIZE = sz; + + constexpr explicit ValueTypeWrapper(std::array i) noexcept : instance(std::move(i)) {} + void* convert() const noexcept { return const_cast(static_cast(instance.data())); } + + constexpr ValueTypeWrapper() = default; + ~ValueTypeWrapper() = default; + + constexpr ValueTypeWrapper(ValueTypeWrapper&&) = default; + constexpr ValueTypeWrapper& operator=(ValueTypeWrapper&& o) { + instance = std::move(o.instance); + return *this; + } + + constexpr ValueTypeWrapper(ValueTypeWrapper const&) = default; + constexpr ValueTypeWrapper& operator=(ValueTypeWrapper const& o) { + instance = o.instance; + return *this; + } + + std::array instance; + }; + + /// @brief struct to pass a pointer to a value type into a method + struct VTPtr { + template + VTPtr(ValueTypeWrapper& v) : instance(&v) {}; + + explicit VTPtr(void* i) : instance(i) {}; + void* convert() const { return const_cast(instance); } + + void* instance; + }; + +} + +template +struct ::il2cpp_utils::ValueTypeTrait<::bs_hook::ValueTypeWrapper> { + constexpr static bool value = true; +}; diff --git a/src/tests/hook-tests.cpp b/src/tests/hook-tests.cpp index d545f935..7c0c556e 100644 --- a/src/tests/hook-tests.cpp +++ b/src/tests/hook-tests.cpp @@ -19,7 +19,7 @@ void* test2(void* one, void*) { template<> struct ::il2cpp_utils::il2cpp_type_check::MetadataGetter<&test2> { - static const MethodInfo* get() { + static const MethodInfo* methodInfo() { return nullptr; } }; @@ -34,4 +34,4 @@ MAKE_HOOK_WRAPPER(test2_hook, &test2, bs_hook::Il2CppWrapperType, bs_hook::Il2Cp // Return from overall hook is converted to a void* } #pragma clang diagnostic pop -#endif \ No newline at end of file +#endif From fde5c78bc165fb84e5db0b309158b80f8f4ccd38 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 12 Dec 2023 02:25:46 +0100 Subject: [PATCH 05/89] Codegen and ListWrapper rename --- shared/utils/il2cpp-utils.hpp | 18 +- shared/utils/typedefs-list.hpp | 20 +- shared/utils/typedefs-object.hpp | 6 +- shared/utils/typedefs.h | 325 ++++++++++++++++++++++++++----- src/tests/list-wrapper-tests.cpp | 12 +- 5 files changed, 297 insertions(+), 84 deletions(-) diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index 80e3e662..ea8785df 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -155,20 +155,20 @@ namespace il2cpp_utils { // Already cached in defaults, no need to re-cache Il2CppException* allocEx = CRASH_UNLESS(New(classof(Il2CppException*))); #if __has_feature(cxx_rtti) - const char* tName = typeid(T).name(); - int status; - char *demangled_name = abi::__cxa_demangle(tName, NULL, NULL, &status); - if (!status) { - allocEx->className = newcsstr(demangled_name); - std::free(demangled_name); - } else { - allocEx->className = newcsstr(tName); - } + #ifdef HAS_CODEGEN + allocEx->_className = newcsstr(type_name()); + #else + allocEx->className = newcsstr(type_name()); + #endif #else #warning "Do not raise C++ exceptions without rtti!" #endif if constexpr (what_able) { + #ifdef HAS_CODEGEN + allocEx->_message = newcsstr(arg.what()); + #else allocEx->message = newcsstr(arg.what()); + #endif } #if defined(UNITY_2019) || defined(UNITY_2021) raise(allocEx); diff --git a/shared/utils/typedefs-list.hpp b/shared/utils/typedefs-list.hpp index c823836b..6c5c2c97 100644 --- a/shared/utils/typedefs-list.hpp +++ b/shared/utils/typedefs-list.hpp @@ -7,20 +7,20 @@ #include "type-concepts.hpp" template*> -struct ListWrapper { +struct ListW { static_assert(sizeof(Ptr) == sizeof(void*), "Size of Ptr type must be the same as a void*!"); - constexpr ListWrapper() noexcept : ptr(nullptr) {} + constexpr ListW() noexcept : ptr(nullptr) {} // TODO: Consider requirementally constexpr-ifying this call // TODO: Apply these il2cpp conversion changes to ArrayW as well, to permit ArrayW to hold wrapper types and not pure pointers - constexpr ListWrapper(Ptr const& p) noexcept : ptr(p) {} + constexpr ListW(Ptr const& p) noexcept : ptr(p) {} template requires (std::is_pointer_v && !il2cpp_utils::has_il2cpp_conversion) - constexpr ListWrapper(void* alterInit) noexcept : ptr(reinterpret_cast(alterInit)) {} + constexpr ListW(void* alterInit) noexcept : ptr(reinterpret_cast(alterInit)) {} - constexpr ListWrapper(std::span const p) : ptr(il2cpp_utils::NewSpecific(p.size())) { + constexpr ListW(std::span const p) : ptr(il2cpp_utils::NewSpecific(p.size())) { std::copy(p.begin(), p.end(), begin()); } @@ -119,15 +119,11 @@ struct ListWrapper { private: Ptr ptr; }; -MARK_GEN_REF_T(ListWrapper); +MARK_GEN_REF_T(ListW); MARK_GEN_REF_PTR_T(List); -// ListW for the win, just implicitly -template*> -using ListW = ListWrapper; - -static_assert(il2cpp_utils::has_il2cpp_conversion*>>); +static_assert(il2cpp_utils::has_il2cpp_conversion*>>); template -struct ::il2cpp_utils::il2cpp_type_check::need_box> { +struct ::il2cpp_utils::il2cpp_type_check::need_box> { constexpr static bool value = false; }; diff --git a/shared/utils/typedefs-object.hpp b/shared/utils/typedefs-object.hpp index 52a29409..10716966 100644 --- a/shared/utils/typedefs-object.hpp +++ b/shared/utils/typedefs-object.hpp @@ -10,7 +10,7 @@ #define USE_CODEGEN_FIELDS #endif -#include "System/Object.hpp" +#include "System/zzzz__Object_def.hpp" #ifndef _HAD_CODEGEN_FIELDS #undef USE_CODEGEN_FIELDS #endif @@ -26,6 +26,7 @@ struct Il2CppObject : public System::Object { }; MonitorData *monitor; }; +#include "System/zzzz__Object_impl.hpp" #else typedef Il2CppClass Il2CppVTable; struct MonitorData; @@ -38,6 +39,5 @@ typedef struct Il2CppObject }; MonitorData *monitor; } Il2CppObject; - -MARK_REF_PTR_T(Il2CppObject); #endif +MARK_REF_PTR_T(Il2CppObject); diff --git a/shared/utils/typedefs.h b/shared/utils/typedefs.h index 0a1fd451..564c304a 100644 --- a/shared/utils/typedefs.h +++ b/shared/utils/typedefs.h @@ -122,19 +122,24 @@ DEFINE_IL2CPP_ARG_TYPE_GENERIC_CLASS(List, "System.Collections.Generic", "List`1 #ifdef HAS_CODEGEN // TODO: QiCache and Il2CppComObject ("System.__Il2CppComObject (dummy type that replaces System.__ComObject)") -#include "System/AppDomain.hpp" +#if __has_include("System/zzzz__AppDomain_def.hpp") +#include "System/zzzz__AppDomain_def.hpp" // self-typedef'd in il2cpp-class-internals.h struct Il2CppAppDomain : public System::AppDomain {}; NEED_NO_BOX(Il2CppAppDomain); +#endif -#include "System/AppDomainSetup.hpp" +#if __has_include("System/zzzz__AppDomainSetup_def.hpp") +#include "System/zzzz__AppDomainSetup_def.hpp" // self-typedef'd in il2cpp-class-internals.h struct Il2CppAppDomainSetup : public System::AppDomainSetup {}; NEED_NO_BOX(Il2CppAppDomainSetup); +#endif -#include "System/ArgumentException.hpp" +#if __has_include("System/zzzz__ArgumentException_def.hpp") +#include "System/zzzz__ArgumentException_def.hpp" typedef System::ArgumentException Il2CppArgumentException; - +#endif // TODO: Il2CppDecimal is System::Decimal? typedef enum Il2CppDecimalCompareResult @@ -146,158 +151,234 @@ typedef enum Il2CppDecimalCompareResult // TODO: Il2CppDouble, Il2CppDouble_double are System::Double? -#include "System/Exception.hpp" +#if __has_include("System/zzzz__Exception_def.hpp") +#include "System/zzzz__Exception_def.hpp" // self-typedef'd in il2cpp-api-types.h struct Il2CppException : public System::Exception {}; +#endif -#include "System/IOAsyncResult.hpp" +#if __has_include("System/zzzz__IOAsyncResult_def.hpp") +#include "System/zzzz__IOAsyncResult_def.hpp" typedef System::IOAsyncResult Il2CppIOAsyncResult; +#endif +#if __has_include("System/zzzz__IOSelectorJob_def.hpp") #include "System/IOSelectorJob.hpp" typedef System::IOSelectorJob Il2CppIOSelectorJob; +#endif -#include "System/MarshalByRefObject.hpp" +#if __has_include("System/zzzz__MarshalByRefObject_def.hpp") +#include "System/zzzz__MarshalByRefObject_def.hpp" typedef System::MarshalByRefObject Il2CppMarshalByRefObject; +#endif -#include "System/MonoAsyncCall.hpp" +#if __has_include("System/zzzz__MonoAsyncCall_def.hpp") +#include "System/zzzz__MonoAsyncCall_def.hpp" typedef System::MonoAsyncCall Il2CppAsyncCall; +#endif -#include "System/MonoType.hpp" +#if __has_include("System/zzzz__MonoType_def.hpp") +#include "System/zzzz__MonoType_def.hpp" struct Il2CppReflectionMonoType : public System::MonoType { - const Il2CppType* GetIl2CppType() const { - return reinterpret_cast(impl.value.m_value); - } + const Il2CppType* GetIl2CppType() const; }; +#endif -#include "System/RuntimeType.hpp" +#if __has_include("System/zzzz__RuntimeType_def.hpp") +#include "System/zzzz__RuntimeType_def.hpp" struct Il2CppReflectionRuntimeType : public System::RuntimeType {}; +#endif // TODO: Il2CppSingle, Il2CppSingle_float are System::Single? -#include "System/SystemException.hpp" +#if __has_include("System/zzzz__SystemException_def.hpp") +#include "System/zzzz__SystemException_def.hpp" typedef System::SystemException Il2CppSystemException; +#endif -#include "System/Type.hpp" +#if __has_include("System/zzzz__Type_def.hpp") +#include "System/zzzz__Type_def.hpp" // self-typedef'd in il2cpp-api-types.h struct Il2CppReflectionType : public System::Type {}; +#endif -#include "System/TypedReference.hpp" +#if __has_include("System/zzzz__TypedReference_def.hpp") +#include "System/zzzz__TypedReference_def.hpp" typedef System::TypedReference Il2CppTypedRef; +#endif -#include "System/Diagnostics/StackFrame.hpp" +#if __has_include("System/Diagnostics/zzzz__StackFrame_def.hpp") +#include "System/Diagnostics/zzzz__StackFrame_def.hpp" typedef System::Diagnostics::StackFrame Il2CppStackFrame; +#endif // TODO: Il2CppCalendarData is System::Globalization::CalendarData minus 4 fields at the end? // TODO: Il2CppCultureData is System::Globalization::CultureData minus 13 fields at the end? -#include "System/Globalization/CultureInfo.hpp" +#if __has_include("System/Globalization/zzzz__CultureInfo_def.hpp") +#include "System/Globalization/zzzz__CultureInfo_def.hpp" typedef System::Globalization::CultureInfo Il2CppCultureInfo; +#endif -#include "System/Globalization/DateTimeFormatInfo.hpp" +#if __has_include("System/Globalization/zzzz__DateTimeFormatInfo_def.hpp") +#include "System/Globalization/zzzz__DateTimeFormatInfo_def.hpp" typedef System::Globalization::DateTimeFormatInfo Il2CppDateTimeFormatInfo; +#endif -#include "System/Globalization/NumberFormatInfo.hpp" +#if __has_include("System/Globalization/zzzz__NumberFormatInfo_def.hpp") +#include "System/Globalization/zzzz__NumberFormatInfo_def.hpp" typedef System::Globalization::NumberFormatInfo Il2CppNumberFormatInfo; +#endif -#include "System/Globalization/RegionInfo.hpp" +#if __has_include("System/Globalization/zzzz__RegionInfo_def.hpp") +#include "System/Globalization/zzzz__RegionInfo_def.hpp" typedef System::Globalization::RegionInfo Il2CppRegionInfo; +#endif -#include "System/Globalization/SortKey.hpp" +#if __has_include("System/Globalization/zzzz__SortKey_def.hpp") +#include "System/Globalization/zzzz__SortKey_def.hpp" typedef System::Globalization::SortKey Il2CppSortKey; +#endif -#include "System/Net/SocketAddress.hpp" +#if __has_include("System/Net/zzzz__SocketAddress_def.hpp") +#include "System/Net/zzzz__SocketAddress_def.hpp" typedef System::Net::SocketAddress Il2CppSocketAddress; +#endif // "Corresponds to Mono's internal System.Net.Sockets.Socket.SocketAsyncResult class. Has no relation to Il2CppAsyncResult." -#include "System/Net/Sockets/SocketAsyncResult.hpp" +#if __has_include("System/Net/Sockets/zzzz__SocketAsyncResult_def.hpp") +#include "System/Net/Sockets/zzzz__SocketAsyncResult_def.hpp" typedef System::Net::Sockets::SocketAsyncResult Il2CppSocketAsyncResult; +#endif -#include "System/Reflection/EventInfo.hpp" +#if __has_include("System/Reflection/zzzz__EventInfo_def.hpp") +#include "System/Reflection/zzzz__EventInfo_def.hpp" typedef System::Reflection::EventInfo Il2CppReflectionEvent; +#endif -#include "System/Reflection/MonoEvent.hpp" +#if __has_include("System/Reflection/zzzz__MonoEvent_def.hpp") +#include "System/Reflection/zzzz__MonoEvent_def.hpp" typedef System::Reflection::MonoEvent Il2CppReflectionMonoEvent; +#endif -#include "System/Reflection/MonoEventInfo.hpp" +#if __has_include("System/Reflection/zzzz__MonoEventInfo_def.hpp") +#include "System/Reflection/zzzz__MonoEventInfo_def.hpp" typedef System::Reflection::MonoEventInfo Il2CppReflectionMonoEventInfo; +#endif -#include "System/Reflection/MonoField.hpp" +#if __has_include("System/Reflection/zzzz__MonoField_def.hpp") +#include "System/Reflection/zzzz__MonoField_def.hpp" typedef System::Reflection::MonoField Il2CppReflectionField; +#endif -#include "System/Reflection/MonoProperty.hpp" +#if __has_include("System/Reflection/zzzz__MonoProperty_def.hpp") +#include "System/Reflection/zzzz__MonoProperty_def.hpp" typedef System::Reflection::MonoProperty Il2CppReflectionProperty; +#endif -#include "System/Reflection/MonoMethod.hpp" +#if __has_include("System/Reflection/zzzz__MonoMethod_def.hpp") +#include "System/Reflection/zzzz__MonoMethod_def.hpp" // self-typedef'd in il2cpp-api-types.h struct Il2CppReflectionMethod : public System::Reflection::MonoMethod {}; +#endif -#if __has_include("System/Reflection/MonoGenericMethod.hpp") -#include "System/Reflection/MonoGenericMethod.hpp" +#if __has_include("System/Reflection/zzzz__MonoGenericMethod_def.hpp") +#include "System/Reflection/zzzz__MonoGenericMethod_def.hpp" typedef System::Reflection::MonoGenericMethod Il2CppReflectionGenericMethod; #endif -#include "System/Reflection/MonoMethodInfo.hpp" +#if __has_include("System/Reflection/zzzz__MonoMethodInfo_def.hpp") +#include "System/Reflection/zzzz__MonoMethodInfo_def.hpp" typedef System::Reflection::MonoMethodInfo Il2CppMethodInfo; +#endif -#include "System/Reflection/MonoPropertyInfo.hpp" +#if __has_include("System/Reflection/zzzz__MonoPropertyInfo_def.hpp") +#include "System/Reflection/zzzz__MonoPropertyInfo_def.hpp" typedef System::Reflection::MonoPropertyInfo Il2CppPropertyInfo; +#endif -#include "System/Reflection/ParameterInfo.hpp" +#if __has_include("System/Reflection/zzzz__ParameterInfo_def.hpp") +#include "System/Reflection/zzzz__ParameterInfo_def.hpp" typedef System::Reflection::ParameterInfo Il2CppReflectionParameter; +#endif -#include "System/Reflection/Module.hpp" +#if __has_include("System/Reflection/zzzz__Module_def.hpp") +#include "System/Reflection/zzzz__Module_def.hpp" typedef System::Reflection::Module Il2CppReflectionModule; +#endif -#include "System/Reflection/AssemblyName.hpp" +#if __has_include("System/Reflection/zzzz__AssemblyName_def.hpp") +#include "System/Reflection/zzzz__AssemblyName_def.hpp" typedef System::Reflection::AssemblyName Il2CppReflectionAssemblyName; +#endif -#include "System/Reflection/Assembly.hpp" +#if __has_include("System/Reflection/zzzz__Assembly_def.hpp") +#include "System/Reflection/zzzz__Assembly_def.hpp" typedef System::Reflection::Assembly Il2CppReflectionAssembly; +#endif -#include "System/Reflection/Emit/UnmanagedMarshal.hpp" +#if __has_include("System/Reflection/Emit/zzzz__UnmanagedMarshal_def.hpp") +#include "System/Reflection/Emit/zzzz__UnmanagedMarshal_def.hpp" typedef System::Reflection::Emit::UnmanagedMarshal Il2CppReflectionMarshal; +#endif /* Stripped in 1.13.5 Update -#include "System/Reflection/ManifestResourceInfo.hpp" -typedef System::Reflection::ManifestResourceInfo Il2CppManifestResourceInfo; +#include "System/Reflection/zzzz__ManifestResourceInfo_def.hpp" +typedef System::Reflection::zzzz__ManifestResourceInfo_def Il2CppManifestResourceInfo; */ -#include "System/Reflection/Pointer.hpp" +#if __has_include("System/Reflection/zzzz__Pointer_def.hpp") +#include "System/Reflection/zzzz__Pointer_def.hpp" typedef System::Reflection::Pointer Il2CppReflectionPointer; +#endif // TODO: Il2CppResourceLocation seems to be the System.Reflection.ResourceLocation enum -#include "System/Runtime/InteropServices/ErrorWrapper.hpp" +#if __has_include("System/Runtime/InteropServices/zzzz__ErrorWrapper_def.hpp") +#include "System/Runtime/InteropServices/zzzz__ErrorWrapper_def.hpp" typedef System::Runtime::InteropServices::ErrorWrapper Il2CppErrorWrapper; +#endif // "Inherited by Microsoft.Win32.SafeHandles.SafeWaitHandle" -#include "System/Runtime/InteropServices/SafeHandle.hpp" +#if __has_include("System/Runtime/InteropServices/zzzz__SafeHandle_def.hpp") +#include "System/Runtime/InteropServices/zzzz__SafeHandle_def.hpp" typedef System::Runtime::InteropServices::SafeHandle Il2CppSafeHandle; +#endif -#include "System/Runtime/Remoting/Contexts/Context.hpp" +#if __has_include("System/Runtime/Remoting/Contexts/zzzz__Context_def.hpp") +#include "System/Runtime/Remoting/Contexts/zzzz__Context_def.hpp" // self-typedef'd in il2cpp-class-internals.h struct Il2CppAppContext : public System::Runtime::Remoting::Contexts::Context {}; NEED_NO_BOX(Il2CppAppContext); +#endif -#include "System/Runtime/Remoting/Messaging/AsyncResult.hpp" +#if __has_include("System/Runtime/Remoting/Messaging/zzzz__AsyncResult_def.hpp") +#include "System/Runtime/Remoting/Messaging/zzzz__AsyncResult_def.hpp" // self-typedef'd in il2cpp-api-types.h struct Il2CppAsyncResult : public System::Runtime::Remoting::Messaging::AsyncResult {}; NEED_NO_BOX(Il2CppAsyncResult); +#endif // TODO: Il2CppCallType which "is a copy of System.Runtime.Remoting.Messaging.CallType" enum // TODO: Il2CppMethodMessage is System::Runtime::Remoting::Messaging::MonoMethodMessage minus 4 fields at the end? -#include "System/Text/StringBuilder.hpp" +#if __has_include("System/Text/zzzz__StringBuilder_def.hpp") +#include "System/Text/zzzz__StringBuilder_def.hpp" typedef System::Text::StringBuilder Il2CppStringBuilder; +#endif -#include "System/Threading/InternalThread.hpp" +#if __has_include("System/Threading/zzzz__InternalThread_def.hpp") +#include "System/Threading/zzzz__InternalThread_def.hpp" typedef System::Threading::InternalThread Il2CppInternalThread; +#endif -#include "System/Threading/Thread.hpp" +#if __has_include("System/Threading/zzzz__Thread_def.hpp") +#include "System/Threading/zzzz__Thread_def.hpp" // self-typedef'd in il2cpp-api-types.h struct Il2CppThread : public System::Threading::Thread {}; +#endif #ifndef _HAD_CODEGEN_FIELDS #undef USE_CODEGEN_FIELDS @@ -305,6 +386,145 @@ struct Il2CppThread : public System::Threading::Thread {}; #undef _HAD_CODEGEN_FIELDS +// include impls +#if __has_include("System/zzzz__AppDomain_impl.hpp") +#include "System/zzzz__AppDomain_impl.hpp" +#endif +#if __has_include("System/zzzz__AppDomainSetup_impl.hpp") +#include "System/zzzz__AppDomainSetup_impl.hpp" +#endif +#if __has_include("System/zzzz__ArgumentException_impl.hpp") +#include "System/zzzz__ArgumentException_impl.hpp" +#endif +#if __has_include("System/zzzz__Exception_impl.hpp") +#include "System/zzzz__Exception_impl.hpp" +#endif +#if __has_include("System/zzzz__IOAsyncResult_impl.hpp") +#include "System/zzzz__IOAsyncResult_impl.hpp" +#endif +#if __has_include("System/IOSelectorJob.hpp") +#include "System/IOSelectorJob.hpp" +#endif +#if __has_include("System/zzzz__MarshalByRefObject_impl.hpp") +#include "System/zzzz__MarshalByRefObject_impl.hpp" +#endif +#if __has_include("System/zzzz__MonoAsyncCall_impl.hpp") +#include "System/zzzz__MonoAsyncCall_impl.hpp" +#endif +#if __has_include("System/zzzz__MonoType_impl.hpp") +#include "System/zzzz__MonoType_impl.hpp" +// include required for this implementation +#include "System/zzzz__RuntimeTypeHandle_impl.hpp" +inline const Il2CppType* Il2CppReflectionMonoType::GetIl2CppType() const { + return reinterpret_cast(_impl.value); +} +#endif +#if __has_include("System/zzzz__RuntimeType_impl.hpp") +#include "System/zzzz__RuntimeType_impl.hpp" +#endif +#if __has_include("System/zzzz__SystemException_impl.hpp") +#include "System/zzzz__SystemException_impl.hpp" +#endif +#if __has_include("System/zzzz__Type_impl.hpp") +#include "System/zzzz__Type_impl.hpp" +#endif +#if __has_include("System/zzzz__TypedReference_impl.hpp") +#include "System/zzzz__TypedReference_impl.hpp" +#endif +#if __has_include("System/Diagnostics/zzzz__StackFrame_impl.hpp") +#include "System/Diagnostics/zzzz__StackFrame_impl.hpp" +#endif +#if __has_include("System/Globalization/zzzz__CultureInfo_impl.hpp") +#include "System/Globalization/zzzz__CultureInfo_impl.hpp" +#endif +#if __has_include("System/Globalization/zzzz__DateTimeFormatInfo_impl.hpp") +#include "System/Globalization/zzzz__DateTimeFormatInfo_impl.hpp" +#endif +#if __has_include("System/Globalization/zzzz__NumberFormatInfo_impl.hpp") +#include "System/Globalization/zzzz__NumberFormatInfo_impl.hpp" +#endif +#if __has_include("System/Globalization/zzzz__RegionInfo_impl.hpp") +#include "System/Globalization/zzzz__RegionInfo_impl.hpp" +#endif +#if __has_include("System/Globalization/zzzz__SortKey_impl.hpp") +#include "System/Globalization/zzzz__SortKey_impl.hpp" +#endif +#if __has_include("System/Net/zzzz__SocketAddress_impl.hpp") +#include "System/Net/zzzz__SocketAddress_impl.hpp" +#endif +#if __has_include("System/Net/Sockets/zzzz__SocketAsyncResult_impl.hpp") +#include "System/Net/Sockets/zzzz__SocketAsyncResult_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__EventInfo_impl.hpp") +#include "System/Reflection/zzzz__EventInfo_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoEvent_impl.hpp") +#include "System/Reflection/zzzz__MonoEvent_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoEventInfo_impl.hpp") +#include "System/Reflection/zzzz__MonoEventInfo_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoField_impl.hpp") +#include "System/Reflection/zzzz__MonoField_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoProperty_impl.hpp") +#include "System/Reflection/zzzz__MonoProperty_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoMethod_impl.hpp") +#include "System/Reflection/zzzz__MonoMethod_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoGenericMethod_impl.hpp") +#include "System/Reflection/zzzz__MonoGenericMethod_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoMethodInfo_impl.hpp") +#include "System/Reflection/zzzz__MonoMethodInfo_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__MonoPropertyInfo_impl.hpp") +#include "System/Reflection/zzzz__MonoPropertyInfo_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__ParameterInfo_impl.hpp") +#include "System/Reflection/zzzz__ParameterInfo_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__Module_impl.hpp") +#include "System/Reflection/zzzz__Module_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__AssemblyName_impl.hpp") +#include "System/Reflection/zzzz__AssemblyName_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__Assembly_impl.hpp") +#include "System/Reflection/zzzz__Assembly_impl.hpp" +#endif +#if __has_include("System/Reflection/Emit/zzzz__UnmanagedMarshal_impl.hpp") +#include "System/Reflection/Emit/zzzz__UnmanagedMarshal_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__ManifestResourceInfo_impl.hpp") +#include "System/Reflection/zzzz__ManifestResourceInfo_impl.hpp" +#endif +#if __has_include("System/Reflection/zzzz__Pointer_impl.hpp") +#include "System/Reflection/zzzz__Pointer_impl.hpp" +#endif +#if __has_include("System/Runtime/InteropServices/zzzz__ErrorWrapper_impl.hpp") +#include "System/Runtime/InteropServices/zzzz__ErrorWrapper_impl.hpp" +#endif +#if __has_include("System/Runtime/InteropServices/zzzz__SafeHandle_impl.hpp") +#include "System/Runtime/InteropServices/zzzz__SafeHandle_impl.hpp" +#endif +#if __has_include("System/Runtime/Remoting/Contexts/zzzz__Context_impl.hpp") +#include "System/Runtime/Remoting/Contexts/zzzz__Context_impl.hpp" +#endif +#if __has_include("System/Runtime/Remoting/Messaging/zzzz__AsyncResult_impl.hpp") +#include "System/Runtime/Remoting/Messaging/zzzz__AsyncResult_impl.hpp" +#endif +#if __has_include("System/Text/zzzz__StringBuilder_impl.hpp") +#include "System/Text/zzzz__StringBuilder_impl.hpp" +#endif +#if __has_include("System/Threading/zzzz__InternalThread_impl.hpp") +#include "System/Threading/zzzz__InternalThread_impl.hpp" +#endif +#if __has_include("System/Threading/zzzz__Thread_impl.hpp") +#include "System/Threading/zzzz__Thread_impl.hpp" +#endif + #else // From Runtime.cpp (some may need the * removed): DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppMulticastDelegate*, multicastdelegate); @@ -352,6 +572,7 @@ NEED_NO_BOX(Il2CppErrorWrapper); // TODO: attempt to move out of this conditional if codegen ever gets an Il2CppComObject? DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppComObject*, il2cpp_com_object); NEED_NO_BOX(Il2CppComObject); +DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppTypedRef, typed_reference); #endif DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppDelegate*, delegate); NEED_NO_BOX(Il2CppDelegate); @@ -364,10 +585,6 @@ NEED_NO_BOX(Il2CppReflectionRuntimeType); #ifndef UNITY_2021 DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionMonoEventInfo*, mono_event_info); NEED_NO_BOX(Il2CppReflectionMonoEventInfo); -#endif -DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppTypedRef*, typed_reference); -NEED_NO_BOX(Il2CppTypedRef); -#ifndef UNITY_2021 DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionMethod*, mono_method); NEED_NO_BOX(Il2CppReflectionMethod); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppMethodInfo*, mono_method_info); @@ -381,7 +598,7 @@ NEED_NO_BOX(Il2CppException); DEFINE_IL2CPP_ARG_TYPE(long double, "System", "Decimal"); template -struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class> { +struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class> { static inline Il2CppClass* get() { static auto klass = ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class*>::get(); return klass; diff --git a/src/tests/list-wrapper-tests.cpp b/src/tests/list-wrapper-tests.cpp index 8b765cb6..e3a1950b 100644 --- a/src/tests/list-wrapper-tests.cpp +++ b/src/tests/list-wrapper-tests.cpp @@ -4,7 +4,7 @@ #include "../../shared/utils/il2cpp-utils.hpp" #include -static void constDoThing(const ListWrapper& wrap) { +static void constDoThing(const ListW& wrap) { auto i = wrap[0]; assert(wrap.size() == 1); for (auto itr : wrap) { @@ -16,7 +16,7 @@ static void constDoThing(const ListWrapper& wrap) { } static void doThing() { - ListWrapper arr(*il2cpp_utils::New*>(classof(List*))); + ListW arr(*il2cpp_utils::New*>(classof(List*))); il2cpp_utils::RunMethodThrow(*reinterpret_cast**>(&arr), il2cpp_utils::FindMethod(arr, "Add"), 2); auto i = arr[0]; assert(arr.size() == 1); @@ -25,15 +25,15 @@ static void doThing() { assert(itr == i); std::cout << itr << std::endl; } - il2cpp_utils::NewSpecific>(1, 2, 3); + il2cpp_utils::NewSpecific>(1, 2, 3); std::cout << i << std::endl; } static void doThing2() { - ListWrapper arr(nullptr); + ListW arr(nullptr); MethodInfo info; il2cpp_utils::RunMethodThrow(classof(Il2CppObject*), &info, arr); - il2cpp_utils::RunMethodThrow>(classof(Il2CppObject*), &info); + il2cpp_utils::RunMethodThrow>(classof(Il2CppObject*), &info); } -#endif \ No newline at end of file +#endif From f4c44a29519220967132eed2502bf74b3b4ffb01 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Wed, 13 Dec 2023 00:11:49 +0100 Subject: [PATCH 06/89] Fix ref types --- shared/utils/typedefs-array.hpp | 2 +- shared/utils/typedefs-string.hpp | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/shared/utils/typedefs-array.hpp b/shared/utils/typedefs-array.hpp index 58d684f5..93f5f2c3 100644 --- a/shared/utils/typedefs-array.hpp +++ b/shared/utils/typedefs-array.hpp @@ -497,7 +497,7 @@ struct ArrayW { } constexpr void* convert() const noexcept { - return val; + return const_cast(static_cast(val)); } private: diff --git a/shared/utils/typedefs-string.hpp b/shared/utils/typedefs-string.hpp index 90012a8c..49598e61 100644 --- a/shared/utils/typedefs-string.hpp +++ b/shared/utils/typedefs-string.hpp @@ -137,11 +137,8 @@ struct StringW { constexpr StringW(std::nullptr_t npt) noexcept : inst(npt) {} constexpr StringW() noexcept : inst(nullptr) {} - constexpr void const* convert() const noexcept { - return inst; - } - constexpr void* convert() noexcept { - return inst; + constexpr void* convert() const noexcept { + return const_cast(static_cast(inst)); } constexpr operator Il2CppString const*() const noexcept { return inst; @@ -253,5 +250,6 @@ StringW operator+(T const lhs, StringW const& rhs) noexcept { } static_assert(sizeof(StringW) == sizeof(void*)); +static_assert(il2cpp_utils::has_il2cpp_conversion); DEFINE_IL2CPP_DEFAULT_TYPE(StringW, string); NEED_NO_BOX(StringW); From cddac07de33d6fdd39774dceeb2113afe65486ea Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Wed, 13 Dec 2023 00:12:06 +0100 Subject: [PATCH 07/89] Fix xrefs and usage of methods --- shared/utils/il2cpp-functions.hpp | 11 +- src/utils/il2cpp-functions.cpp | 183 ++++++++++++++++++----------- src/utils/il2cpp-utils-classes.cpp | 2 +- src/utils/il2cpp-utils-methods.cpp | 8 +- 4 files changed, 128 insertions(+), 76 deletions(-) diff --git a/shared/utils/il2cpp-functions.hpp b/shared/utils/il2cpp-functions.hpp index 2e6e1b3d..7b6f46cb 100644 --- a/shared/utils/il2cpp-functions.hpp +++ b/shared/utils/il2cpp-functions.hpp @@ -257,6 +257,8 @@ class il2cpp_functions { API_FUNC(void, gc_set_external_allocation_tracker, (void(*func)(void*, size_t, int))); API_FUNC(void, gc_set_external_wbarrier_tracker, (void(*func)(void**))); API_FUNC(void, gc_foreach_heap, (void(*func)(void* data, void* userData), void* userData)); + API_FUNC(void*, gc_alloc_fixed, (std::size_t size)); + API_FUNC(void, gc_free_fixed, (void* addr)); API_FUNC(void, stop_gc_world, ()); API_FUNC(void, start_gc_world, ()); #endif @@ -405,9 +407,13 @@ class il2cpp_functions { // SELECT NON-API LIBIL2CPP FUNCTIONS: API_FUNC_VISIBLE(bool, Class_Init, (Il2CppClass* klass)); - API_FUNC_VISIBLE(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeDefinitionIndex, (TypeDefinitionIndex index)); + API_FUNC_VISIBLE(Il2CppClass*, MetadataCache_GetTypeInfoFromHandle, (Il2CppMetadataTypeHandle index)); API_FUNC_VISIBLE(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeIndex, (TypeIndex index)); + API_FUNC_VISIBLE(Il2CppClass*, GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex, (TypeDefinitionIndex index)); + API_FUNC_VISIBLE(Il2CppClass*, GlobalMetadata_GetTypeInfoFromHandle, (TypeDefinitionIndex index)); + + #if defined(UNITY_2019) || defined(UNITY_2021) API_FUNC_VISIBLE(std::string, _Type_GetName_, (const Il2CppType *type, Il2CppTypeNameFormat format)); #else @@ -424,7 +430,7 @@ class il2cpp_functions { API_FUNC_VISIBLE(AssemblyVector*, Assembly_GetAllAssemblies, ()); private: - static bool find_GC_free(const uint32_t* Runtime_Shutdown); + static bool find_GC_free(); static bool find_GC_SetWriteBarrier(const uint32_t* set_wbarrier_field); static bool trace_GC_AllocFixed(const uint32_t* DomainGetCurrent); static bool find_GC_AllocFixed(const uint32_t* DomainGetCurrent); @@ -472,7 +478,6 @@ class il2cpp_functions { static GenericParameterIndex MetadataCache_GetGenericParameterIndexFromParameter(Il2CppMetadataGenericParameterHandle handle); static const Il2CppTypeDefinition* MetadataCache_GetTypeDefinition(Il2CppClass* klass); static GenericParameterIndex MetadataCache_GetGenericContainerIndex(Il2CppClass* klass); - static Il2CppClass* MetadataCache_GetTypeInfoFromHandle(Il2CppMetadataTypeHandle handle); // Whether all of the il2cpp functions have been initialized or not static bool initialized; diff --git a/src/utils/il2cpp-functions.cpp b/src/utils/il2cpp-functions.cpp index c9f87a74..7ddef149 100644 --- a/src/utils/il2cpp-functions.cpp +++ b/src/utils/il2cpp-functions.cpp @@ -137,6 +137,8 @@ API_INIT(bool, gc_has_strict_wbarriers, ()); API_INIT(void, gc_set_external_allocation_tracker, (void (*func)(void*, size_t, int))); API_INIT(void, gc_set_external_wbarrier_tracker, (void (*func)(void**))); API_INIT(void, gc_foreach_heap, (void (*func)(void* data, void* userData), void* userData)); +API_INIT(void*, gc_alloc_fixed, (std::size_t size)); +API_INIT(void, gc_free_fixed, (void* addr)); API_INIT(void, stop_gc_world, ()); API_INIT(void, start_gc_world, ()); #endif @@ -286,10 +288,16 @@ API_INIT(const char*, class_get_name_const, (const Il2CppClass* klass)); // SELECT NON-API LIBIL2CPP FUNCTIONS: API_INIT(bool, Class_Init, (Il2CppClass * klass)); - -API_INIT(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeDefinitionIndex, (TypeDefinitionIndex index)); +#ifdef UNITY_2021 +API_INIT(Il2CppClass*, MetadataCache_GetTypeInfoFromHandle, (Il2CppMetadataTypeHandle index)); +#endif API_INIT(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeIndex, (TypeIndex index)); +#ifdef UNITY_2021 +API_INIT(Il2CppClass*, GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex, (TypeDefinitionIndex index)); +API_INIT(Il2CppClass*, GlobalMetadata_GetTypeInfoFromHandle, (TypeDefinitionIndex index)); +#endif + #if defined(UNITY_2019) || defined(UNITY_2021) API_INIT(std::string, _Type_GetName_, (const Il2CppType* type, Il2CppTypeNameFormat format)); #else @@ -366,7 +374,7 @@ Il2CppClass* il2cpp_functions::MetadataCache_GetNestedTypeFromIndex(NestedTypeIn IL2CPP_ASSERT(index >= 0 && static_cast(index) <= s_GlobalMetadataHeader->nestedTypesCount / sizeof(TypeDefinitionIndex)); auto nestedTypeIndices = (const TypeDefinitionIndex*)((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->nestedTypesOffset); - return il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(nestedTypeIndices[index]); + return il2cpp_functions::GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex(nestedTypeIndices[index]); } TypeDefinitionIndex il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(const Il2CppTypeDefinition* typeDefinition) { @@ -403,13 +411,6 @@ GenericParameterIndex il2cpp_functions::MetadataCache_GetGenericContainerIndex(I return MetadataCache_GetTypeDefinition(klass)->genericContainerIndex; } -Il2CppClass* il2cpp_functions::MetadataCache_GetTypeInfoFromHandle(Il2CppMetadataTypeHandle handle) { - const Il2CppTypeDefinition* typeDefinition = reinterpret_cast(handle); - auto typeIndex = MetadataCache_GetIndexForTypeDefinition(typeDefinition); - return MetadataCache_GetTypeInfoFromTypeDefinitionIndex(typeIndex); -} - - char* il2cpp_functions::Type_GetName(const Il2CppType* type, Il2CppTypeNameFormat format) { if (!il2cpp__Type_GetName_) return nullptr; // TODO debug the ref/lifetime weirdness with _Type_GetName_ to avoid the need for explicit allocation @@ -423,32 +424,15 @@ static std::optional blrFind(cs_insn* insn) { return insn->id == ARM64_INS_BLR ? std::optional(reinterpret_cast(insn->address)) : std::nullopt; } -bool il2cpp_functions::find_GC_free(const uint32_t* Runtime_Shutdown) { - bool multipleMatches; - auto sigMatch = findUniquePatternInLibil2cpp(multipleMatches, - "f8 5f bc a9 f6 57 01 a9 f4 4f 02 a9 " - "fd 7b 03 a9 fd c3 00 91 a0 08 00 b4 f3 03 00 aa ?? ?? ?? ?? 69 82 56 d3 4a ?? ?? 91 4b 0d 09 8b 49 55 40 f9 " - "0a 9c 9c 52 0a 04 a0 72 00 cc 74 92 68 fe 56 d3 6c 01 0a 8b 0a 03 84 52", - "GC_free"); - if (sigMatch && !multipleMatches) { - il2cpp_functions::il2cpp_GC_free = reinterpret_cast(sigMatch); - return true; - } +bool il2cpp_functions::find_GC_free() { static auto logger = il2cpp_functions::getFuncLogger().WithContext("find_GC_free"); + auto gc_free_fixed = cs::findNthB<1>(reinterpret_cast(il2cpp_functions::il2cpp_gc_free_fixed)); + if (!gc_free_fixed) SAFE_ABORT_MSG("Failed to find GarbageCollector::FreeFixed!"); + + auto gc_free = cs::findNthB<1>(*gc_free_fixed); + if (!gc_free) SAFE_ABORT_MSG("Failed to find GC_free!"); - auto blrStart = cs::find_through_hooks(Runtime_Shutdown, 4096, [](auto... pairs) { - std::array tracked{ pairs... }; - // Find first blr - return cs::findNth(tracked, 1, -1, blrFind, cs::insnMatch<>); - }); - if (!blrStart) return false; - // 4th bl is call to (inlined) GarbageCollector_FreeFixed (after the blr, which we wish to skip) - auto callAddr = cs::findNthBl<4>(*blrStart + 1); - if (!callAddr) return false; - // GarbageCollector_FreeFixed has one b to GC_Free - auto gc_free_b = cs::findNthB<1>(*callAddr); - if (!gc_free_b) return false; - il2cpp_GC_free = reinterpret_cast(*gc_free_b); + il2cpp_GC_free = reinterpret_cast(*gc_free); return true; } @@ -654,6 +638,8 @@ void il2cpp_functions::Init() { API_SYM(gc_set_external_allocation_tracker); API_SYM(gc_set_external_wbarrier_tracker); API_SYM(gc_foreach_heap); + API_SYM(gc_free_fixed); + API_SYM(gc_alloc_fixed); API_SYM(stop_gc_world); API_SYM(start_gc_world); #endif @@ -815,24 +801,81 @@ void il2cpp_functions::Init() { } { - auto MetadataCache_HasAttribute_addr = cs::findNthB<1, false, -1, 1024>(reinterpret_cast(il2cpp_custom_attrs_has_attr)); - if (!MetadataCache_HasAttribute_addr) SAFE_ABORT_MSG("Failed to find MetadataCache::HasAttribute!"); - auto typeinfo = cs::findNthBl<1>(*MetadataCache_HasAttribute_addr); - if (!typeinfo) SAFE_ABORT_MSG("Failed to find MetadataCache::GetTypeInfoFromTypeIndex!"); - il2cpp_MetadataCache_GetTypeInfoFromTypeIndex = reinterpret_cast(*typeinfo); + /* + Path to method: + il2cpp_image_get_class + Image::GetType + MetadataCache::GetTypeInfoFromHandle + */ + logger.debug("il2cpp_image_get_class offset: %lX", reinterpret_cast(il2cpp_image_get_class) - getRealOffset(0)); + auto Image_GetType_addr = cs::findNthB<1, false, -1, 1024>(reinterpret_cast(il2cpp_image_get_class)); + if (!Image_GetType_addr) SAFE_ABORT_MSG("Failed to find Image::GetType!"); + logger.debug("Image::GetType found? offset: %lX", reinterpret_cast(*Image_GetType_addr) - getRealOffset(0)); + auto MetadataCache_GetTypeInfoFromHandle_addr = cs::findNthB<1, false, -1, 1024>(*Image_GetType_addr); + if (!MetadataCache_GetTypeInfoFromHandle_addr) SAFE_ABORT_MSG("Failed to find MetadataCache::GetTypeInfoFromHandle!"); + il2cpp_MetadataCache_GetTypeInfoFromHandle = reinterpret_cast(*MetadataCache_GetTypeInfoFromHandle_addr); + // MetadataCache::GetTypeInfoFromHandle. offset 0x84F764 in 1.5, 0x9F5250 in 1.7.0, 0xA7A79C in 1.8.0b1 + logger.debug("MetadataCache::GetTypeInfoFromHandle found? offset: %lX", reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromHandle) - getRealOffset(0)); + } + + { + /* + Path to method: + il2cpp_field_get_value_object + Field::GetValueObject + Field::GetValueObjectForThread + Field::GetDefaultFieldValue + BlobReader::GetConstantValueFromBlob + MetadataCache::GetTypeInfoFromTypeIndex + */ + logger.debug("il2cpp_field_get_value_object offset: %lX", reinterpret_cast(il2cpp_field_get_value_object) - getRealOffset(0)); + auto Field_GetValueObject_addr = cs::findNthB<1, false, -1, 1024>(reinterpret_cast(il2cpp_field_get_value_object)); + if (!Field_GetValueObject_addr) SAFE_ABORT_MSG("Failed to find Field::GetValueObject!"); + logger.debug("Field::GetValueObject found? offset: %lX", reinterpret_cast(*Field_GetValueObject_addr) - getRealOffset(0)); + + auto Field_GetDefaultFieldValue_addr = cs::findNthBl<4, 1>(*Field_GetValueObject_addr); + if (!Field_GetDefaultFieldValue_addr) SAFE_ABORT_MSG("Failed to find Field::GetDefaultFieldValue!"); + logger.debug("Field::GetDefaultFieldValue found? offset: %lX", reinterpret_cast(*Field_GetDefaultFieldValue_addr) - getRealOffset(0)); + + auto BlobReader_GetConstantValueFromBlob_addr = cs::findNthBl<2, 1>(*Field_GetDefaultFieldValue_addr); + if (!BlobReader_GetConstantValueFromBlob_addr) SAFE_ABORT_MSG("Failed to find BlobReader::GetConstantValueFromBlob!"); + logger.debug("Image::GetConstantValueFromBlob found? offset: %lX", reinterpret_cast(*BlobReader_GetConstantValueFromBlob_addr) - getRealOffset(0)); + + auto BlobReader_GetConstantValueFromBlob2_addr = cs::findNthBl<1, 1>(*BlobReader_GetConstantValueFromBlob_addr); + if (!BlobReader_GetConstantValueFromBlob2_addr) SAFE_ABORT_MSG("Failed to find BlobReader::GetConstantValueFromBlob!"); + logger.debug("Image::GetConstantValueFromBlob2 found? offset: %lX", reinterpret_cast(*BlobReader_GetConstantValueFromBlob2_addr) - getRealOffset(0)); + + auto MetadataCache_GetTypeInfoFromTypeIndex_addr = cs::findNthBl<2, 1>(*BlobReader_GetConstantValueFromBlob2_addr); + if (!MetadataCache_GetTypeInfoFromTypeIndex_addr) SAFE_ABORT_MSG("Failed to find MetadataCache::GetTypeInfoFromTypeIndex!"); + il2cpp_MetadataCache_GetTypeInfoFromTypeIndex = reinterpret_cast(*MetadataCache_GetTypeInfoFromTypeIndex_addr); // MetadataCache::GetTypeInfoFromTypeIndex. offset 0x84F764 in 1.5, 0x9F5250 in 1.7.0, 0xA7A79C in 1.8.0b1 logger.debug("MetadataCache::GetTypeInfoFromTypeIndex found? offset: %lX", reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromTypeIndex) - getRealOffset(0)); } { - auto Type_GetClassOrElementClass_addr = cs::findNthB<1, false, -1, 1024>(reinterpret_cast(il2cpp_type_get_class_or_element_class)); - if (!Type_GetClassOrElementClass_addr) SAFE_ABORT_MSG("Failed to find Type::GetClassOrElementClass!"); - auto result = cs::findNthB<5, false, 0>(*Type_GetClassOrElementClass_addr); - if (!result) SAFE_ABORT_MSG("Failed to find MetadataCache::GetTypeInfoFromDefinitionIndex!"); - il2cpp_MetadataCache_GetTypeInfoFromTypeDefinitionIndex = reinterpret_cast(*result); - // MetadataCache::GetTypeInfoFromTypeDefinitionIndex. offset 0x84FBA4 in 1.5, 0x9F5690 in 1.7.0, 0xA75958 in 1.8.0b1 - logger.debug("MetadataCache::GetTypeInfoFromTypeDefinitionIndex found? offset: %p, %lX", il2cpp_MetadataCache_GetTypeInfoFromTypeDefinitionIndex, - reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromTypeDefinitionIndex) - getRealOffset(0)); + /* + Path to method: + MetadataCache::GetTypeInfoFromHandle + GlobalMetadata::GetTypeInfoFromHandle + */ + logger.debug("MetadataCache::GetTypeInfoFromHandle offset: %lX", reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromHandle) - getRealOffset(0)); + auto GlobalMetadata_GetTypeInfoFromHandle_addr = cs::findNthB<1, false, -1, 1024>(reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromHandle)); + if (!GlobalMetadata_GetTypeInfoFromHandle_addr) SAFE_ABORT_MSG("Failed to find GlobalMetadata::GetTypeInfoFromHandle!"); + il2cpp_GlobalMetadata_GetTypeInfoFromHandle = reinterpret_cast(*GlobalMetadata_GetTypeInfoFromHandle_addr); + logger.debug("GlobalMetadata::GetTypeInfoFromHandle found? offset: %lX", reinterpret_cast(il2cpp_GlobalMetadata_GetTypeInfoFromHandle) - getRealOffset(0)); + } + + { + /* + Path to method: + GlobalMetadata::GetTypeInfoFromHandle + GlobalMetadata::GetTypeInfoFromTypeDefinitionIndex + */ + logger.debug("GlobalMetadata::GetTypeInfoFromHandle offset: %lX", reinterpret_cast(il2cpp_GlobalMetadata_GetTypeInfoFromHandle) - getRealOffset(0)); + auto GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex_addr = cs::findNthB<1, false, -1, 1024>(reinterpret_cast(il2cpp_GlobalMetadata_GetTypeInfoFromHandle)); + if (!GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex_addr) SAFE_ABORT_MSG("Failed to find GlobalMetadata::GetTypeInfoFromTypeDefinitionIndex!"); + il2cpp_GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex = reinterpret_cast(*GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex_addr); + logger.debug("GlobalMetadata::GetTypeInfoFromTypeDefinitionIndex found? offset: %lX", reinterpret_cast(il2cpp_GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex) - getRealOffset(0)); } { @@ -847,26 +890,24 @@ void il2cpp_functions::Init() { auto result = cs::findNthB<1, false, -1, 1024>(reinterpret_cast(il2cpp_class_from_il2cpp_type)); if (!result) SAFE_ABORT_MSG("Failed to find Class::FromIl2CppType!"); il2cpp_Class_FromIl2CppType = reinterpret_cast(*result); + logger.debug("Class::FromIl2CppType found? offset: %lX", ((uintptr_t)il2cpp_Class_FromIl2CppType) - getRealOffset(0)); } { // GenericClass::GetClass. offset 0x88DF64 in 1.5, 0xA34F20 in 1.7.0, 0xA6E4EC in 1.8.0b1 - // Skip found br - auto caseStart = cs::evalswitch<1, 1, IL2CPP_TYPE_GENERICINST>(reinterpret_cast(il2cpp_Class_FromIl2CppType)); - if (!caseStart) SAFE_ABORT_MSG("Failed to find case for IL2CPP_TYPE_GENERICINST!"); - auto result = cs::findNthB<1>(*caseStart); - if (!result) SAFE_ABORT_MSG("Failed to find GenericClass::GetClass!"); - il2cpp_GenericClass_GetClass = reinterpret_cast(*result); + // Instead of evaluating the switch, we get the 6th b + auto GenericClass_GetClass_addr = cs::findNthB<6, false, -1, 1024>(reinterpret_cast(il2cpp_Class_FromIl2CppType)); + if (!GenericClass_GetClass_addr) SAFE_ABORT_MSG("Failed to find GenericClass::GetClass!"); + il2cpp_GenericClass_GetClass = reinterpret_cast(*GenericClass_GetClass_addr); logger.debug("GenericClass::GetClass found? offset: %lX", ((uintptr_t)il2cpp_GenericClass_GetClass) - getRealOffset(0)); } { - // Class::GetPtrClass. - auto ptrCase = cs::evalswitch<1, 1, IL2CPP_TYPE_PTR>(reinterpret_cast(il2cpp_Class_FromIl2CppType)); - if (!ptrCase) SAFE_ABORT_MSG("Failed to find case for IL2CPP_TYPE_PTR!"); - auto result = cs::findNthB<1>(*ptrCase); - if (!result) SAFE_ABORT_MSG("Failed to find Class::GetPtrClass!"); - il2cpp_Class_GetPtrClass = reinterpret_cast(*result); + // Class::GetPtrClass(Il2CppClass*) + // instead of evaluating the switch, we look for the 4th b + auto Class_GetPtrClass_addr = cs::findNthB<4, false>(reinterpret_cast(il2cpp_Class_FromIl2CppType)); + if (!Class_GetPtrClass_addr) SAFE_ABORT_MSG("Failed to find Class_GetPtrClass!"); + il2cpp_Class_GetPtrClass = reinterpret_cast(*Class_GetPtrClass_addr); logger.debug("Class::GetPtrClass(Il2CppClass*) found? offset: %lX", ((uintptr_t)il2cpp_Class_GetPtrClass) - getRealOffset(0)); } @@ -881,9 +922,7 @@ void il2cpp_functions::Init() { { CRASH_UNLESS(il2cpp_shutdown); // GC_free - auto Runtime_Shutdown = cs::findNthB<1>(reinterpret_cast(il2cpp_shutdown)); - if (!Runtime_Shutdown) SAFE_ABORT_MSG("Failed to find Runtime::Shutdown!"); - if (find_GC_free(*Runtime_Shutdown)) { + if (find_GC_free()) { logger.debug("gc::GarbageCollector::FreeFixed found? offset: %lX", ((uintptr_t)il2cpp_GC_free) - getRealOffset(0)); } // GarbageCollector::SetWriteBarrier(void*) @@ -900,30 +939,34 @@ void il2cpp_functions::Init() { hasGCFuncs = il2cpp_GarbageCollector_AllocateFixed != nullptr && il2cpp_GC_free != nullptr; { + /* + il2cpp_init + Runtime::Init -> 2nd bl + */ // il2cpp_defaults. Runtime::Init is 3rd bl from init_utf16 - auto runtimeInit = cs::findNthBl<3>(reinterpret_cast(il2cpp_init_utf16)); - if (!runtimeInit) SAFE_ABORT_MSG("Failed to find Runtime::InitUtf16!"); + auto runtimeInit = cs::findNthBl<2>(reinterpret_cast(il2cpp_init)); + if (!runtimeInit) SAFE_ABORT_MSG("Failed to find Runtime::Init!"); // alternatively, could just get the 1st ADRP in Runtime::Init with dest reg x20 (or the 9th ADRP) // We DO need to skip at least one ret, though. - auto ldr = cs::findNth<6, &loadFind, &cs::insnMatch<>, 1>(*runtimeInit); - if (!ldr) SAFE_ABORT_MSG("Failed to find 6th load in Runtime::InitUtf16!"); + auto ldr = cs::findNth<8, &loadFind, &cs::insnMatch<>, 1>(*runtimeInit); + if (!ldr) SAFE_ABORT_MSG("Failed to find 8th load in Runtime::Init!"); auto defaults_addr = cs::getpcaddr<1, 1>(*ldr); - if (!defaults_addr) SAFE_ABORT_MSG("Failed to find pcaddr around 6th load in Runtime::InitUtf16!"); + if (!defaults_addr) SAFE_ABORT_MSG("Failed to find pcaddr around 8th load in Runtime::Init!"); defaults = reinterpret_cast(std::get<2>(*defaults_addr)); logger.debug("il2cpp_defaults found: %p (offset: %lX)", defaults, ((uintptr_t)defaults) - getRealOffset(0)); // FIELDS // Extract locations of s_GlobalMetadataHeader, s_Il2CppMetadataRegistration, & s_GlobalMetadata - auto tmp = cs::getpcaddr<3, 1>(reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromTypeDefinitionIndex)); + auto tmp = cs::getpcaddr<3, 1>(reinterpret_cast(il2cpp_GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex)); if (!tmp) SAFE_ABORT_MSG("Failed to find 3rd pcaddr for s_GlobalMetadataHeaderPtr!"); s_GlobalMetadataHeaderPtr = reinterpret_cast(std::get<2>(*tmp)); - tmp = cs::getpcaddr<4, 1>(reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromTypeDefinitionIndex)); + tmp = cs::getpcaddr<4, 1>(reinterpret_cast(il2cpp_GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex)); if (!tmp) SAFE_ABORT_MSG("Failed to find 4th pcaddr for s_Il2CppMetadataRegistrationPtr!"); s_Il2CppMetadataRegistrationPtr = reinterpret_cast(std::get<2>(*tmp)); - tmp = cs::getpcaddr<5, 1>(reinterpret_cast(il2cpp_MetadataCache_GetTypeInfoFromTypeDefinitionIndex)); + tmp = cs::getpcaddr<5, 1>(reinterpret_cast(il2cpp_GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex)); if (!tmp) SAFE_ABORT_MSG("Failed to find 5th pcaddr for s_GlobalMetadataPtr!"); s_GlobalMetadataPtr = reinterpret_cast(std::get<2>(*tmp)); logger.debug("%p %p %p metadata pointers", s_GlobalMetadataHeaderPtr, s_Il2CppMetadataRegistrationPtr, s_GlobalMetadataPtr); diff --git a/src/utils/il2cpp-utils-classes.cpp b/src/utils/il2cpp-utils-classes.cpp index 33e3b4f5..89558ad8 100644 --- a/src/utils/il2cpp-utils-classes.cpp +++ b/src/utils/il2cpp-utils-classes.cpp @@ -171,7 +171,7 @@ namespace il2cpp_utils { } std::string genClassName = GenericClassStandardName(genClass); - auto* typeDefClass = il2cpp_functions::MetadataCache_GetTypeInfoFromTypeDefinitionIndex(il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(genClass->cached_class)); + auto* typeDefClass = il2cpp_functions::GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex(il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(genClass->cached_class)); if (!typeDefClass) continue; classToGenericClassMap[typeDefClass][genClassName.c_str()] = genClass; diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index e6ac7340..7a48e2aa 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -1,6 +1,7 @@ #include "../../shared/utils/typedefs.h" #include "../../shared/utils/il2cpp-utils-methods.hpp" #include "../../shared/utils/hashing.hpp" +#include "utils/il2cpp-functions.hpp" #include namespace std { @@ -88,6 +89,9 @@ namespace il2cpp_utils { const MethodInfo* ResolveVtableSlot(Il2CppClass* klass, Il2CppClass* declaringClass, uint16_t slot) noexcept { il2cpp_functions::Init(); + if (!klass->initialized_and_no_error) il2cpp_functions::Class_Init(klass); + if (!declaringClass->initialized_and_no_error) il2cpp_functions::Class_Init(klass); + static auto logger = getLogger().WithContext("ResolveVtableSlot"); if(il2cpp_functions::class_is_interface(declaringClass)) { RET_DEFAULT_UNLESS(logger, slot < declaringClass->vtable_count); @@ -106,7 +110,7 @@ namespace il2cpp_utils { } return nullptr; } - + const MethodInfo* ResolveVtableSlot(Il2CppClass* klass, std::string_view declaringNamespace, std::string_view declaringClassName, uint16_t slot) noexcept { return ResolveVtableSlot(klass, GetClassFromName(declaringNamespace, declaringClassName), slot); } @@ -337,4 +341,4 @@ namespace il2cpp_utils { } return ret; } -} \ No newline at end of file +} From 94354141a08e1f575df0e1eb907a1d4dbb3bd64c Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Wed, 13 Dec 2023 00:33:59 +0100 Subject: [PATCH 08/89] Change name of test built binary and add it as a dependency to the main binary --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb893bfb..8c06a92e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,10 +97,12 @@ function(setup_target target add_test) endfunction() # compile test as one project -setup_target(${COMPILE_ID}-test YES) +setup_target(test-${COMPILE_ID} YES) # regular bs hook setup_target(${COMPILE_ID} NO) +add_dependencies(${COMPILE_ID} test-${COMPILE_ID}) + target_compile_definitions( ${COMPILE_ID} PRIVATE From bf26757e2037d388f5ade3e5895e19d1fa66de8f Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 14 Dec 2023 21:08:06 +0100 Subject: [PATCH 09/89] Fix member access for cordl --- shared/utils/typedefs-wrappers.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/utils/typedefs-wrappers.hpp b/shared/utils/typedefs-wrappers.hpp index 740365eb..3f593523 100644 --- a/shared/utils/typedefs-wrappers.hpp +++ b/shared/utils/typedefs-wrappers.hpp @@ -540,7 +540,7 @@ struct SafePtrUnity : public SafePtr { inline bool isAlive() const { #ifdef HAS_CODEGEN - return static_cast(Parent::internalHandle) && (Parent::ptr()) && Parent::ptr()->m_CachedPtr.m_value; + return static_cast(Parent::internalHandle) && (Parent::ptr()) && Parent::ptr()->m_CachedPtr; #else // offset yay // the offset as specified in the codegen header of [m_CachedPtr] is 0x10 From 23d9823c42fd9f5b9a5796812f6ca958f9ff2142 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 14 Dec 2023 22:50:49 +0100 Subject: [PATCH 10/89] Fix ListW for codegen --- shared/utils/typedefs-list.hpp | 35 ++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/shared/utils/typedefs-list.hpp b/shared/utils/typedefs-list.hpp index 6c5c2c97..e014587f 100644 --- a/shared/utils/typedefs-list.hpp +++ b/shared/utils/typedefs-list.hpp @@ -36,10 +36,10 @@ struct ListW { return ptr->size; } T& operator[](size_t i) { - return ptr->items->values[i]; + return get_items()->values[i]; } const T& operator[](size_t i) const { - return ptr->items->values[i]; + return get_items()->values[i]; } /// @brief Get a given index, performs bound checking and throws std::runtime_error on failure. @@ -47,14 +47,14 @@ struct ListW { /// @return The reference to the item. T& get(size_t i) { THROW_UNLESS(i < size() && i >= 0); - return ptr->items->values[i]; + return get_items()->values[i]; } /// @brief Get a given index, performs bound checking and throws std::runtime_error on failure. /// @param i The index to get. /// @return The const reference to the item. const T& get(size_t i) const { THROW_UNLESS(i < size() && i >= 0); - return ptr->items->values[i]; + return get_items()->values[i]; } /// @brief Tries to get a given index, performs bound checking and returns a std::nullopt on failure. /// @param i The index to get. @@ -63,7 +63,7 @@ struct ListW { if (i >= size() || i < 0) { return std::nullopt; } - return WrapperRef(ptr->items->values[i]); + return WrapperRef(get_items()->values[i]); } /// @brief Tries to get a given index, performs bound checking and returns a std::nullopt on failure. /// @param i The index to get. @@ -72,20 +72,20 @@ struct ListW { if (i >= size() || i < 0) { return std::nullopt; } - return WrapperRef(ptr->items->values[i]); + return WrapperRef(get_items()->values[i]); } iterator begin() { - return ptr->items->values; + return get_items()->values; } iterator end() { - return &ptr->items->values[size()]; + return &get_items()->values[size()]; } const_iterator begin() const { - return ptr->items->values; + return get_items()->values; } const_iterator end() const { - return &ptr->items->values[size()]; + return &get_items()->values[size()]; } operator std::span const() const { @@ -117,6 +117,21 @@ struct ListW { } private: + #ifdef HAS_CODEGEN + auto get_items() const { + return ptr->_items; + } + auto get_items() { + return ptr->_items; + } + #else + auto get_items() const { + return ptr->items; + } + auto get_items() { + return ptr->items; + } + #endif Ptr ptr; }; MARK_GEN_REF_T(ListW); From 325740dee601e01840285a5fa6583fbd36b4f004 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 14 Dec 2023 23:27:32 +0100 Subject: [PATCH 11/89] Fix ListW for codegen --- shared/utils/typedefs-list.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/utils/typedefs-list.hpp b/shared/utils/typedefs-list.hpp index e014587f..327d9001 100644 --- a/shared/utils/typedefs-list.hpp +++ b/shared/utils/typedefs-list.hpp @@ -33,7 +33,11 @@ struct ListW { using const_iterator = const_pointer; [[nodiscard]] constexpr int size() const { + #ifdef HAS_CODEGEN + return ptr->_size; + #else return ptr->size; + #endif } T& operator[](size_t i) { return get_items()->values[i]; From e83526a546675279d37576db21f7e5960cfd8317 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Fri, 15 Dec 2023 00:28:52 +0100 Subject: [PATCH 12/89] Add safety to Basic Event callback --- shared/utils/typedefs-wrappers.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shared/utils/typedefs-wrappers.hpp b/shared/utils/typedefs-wrappers.hpp index 3f593523..68bc1ed8 100644 --- a/shared/utils/typedefs-wrappers.hpp +++ b/shared/utils/typedefs-wrappers.hpp @@ -727,9 +727,18 @@ class BasicEventCallback { public: void invoke(TArgs... args) const { +#ifndef NO_EVENT_CALLBACK_INVOKE_SAFETY + // copy the callbacks so an unsubscribe during invoke of the container doesn't cause UB + auto cbs = callbacks; + for (auto& callback : cbs) { + callback(args...); + } +#else + // no safety requested, just run it from the callbacks as for (auto& callback : callbacks) { callback(args...); } +#endif } BasicEventCallback& operator+=(ThinVirtualLayer callback) { From db1987bee84f3bd5d18671c4f04e0a8d0a715afe Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Fri, 15 Dec 2023 16:22:40 +0100 Subject: [PATCH 13/89] Fix unboxing logic & fix concept for il2cpp conversion --- shared/utils/il2cpp-utils-classes.hpp | 3 ++- shared/utils/type-concepts.hpp | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/utils/il2cpp-utils-classes.hpp b/shared/utils/il2cpp-utils-classes.hpp index 461bd78a..7aa4c6b8 100644 --- a/shared/utils/il2cpp-utils-classes.hpp +++ b/shared/utils/il2cpp-utils-classes.hpp @@ -7,6 +7,7 @@ #include "il2cpp-functions.hpp" #include "il2cpp-utils-methods.hpp" #include "base-wrapper-type.hpp" +#include "type-concepts.hpp" #include #include @@ -37,7 +38,7 @@ namespace il2cpp_utils { } if constexpr (::std::is_pointer_v) { return static_cast(val); - } else if constexpr (has_il2cpp_conversion) { + } else if constexpr (il2cpp_reference_type_wrapper) { return TOut(static_cast(val)); } else { diff --git a/shared/utils/type-concepts.hpp b/shared/utils/type-concepts.hpp index 46b01fcb..54b0dec1 100644 --- a/shared/utils/type-concepts.hpp +++ b/shared/utils/type-concepts.hpp @@ -11,9 +11,8 @@ namespace il2cpp_utils { template /// @brief A concept depicting if a type is a wrapper type. // TODO: Make this use a static creation method instead of a constructor - concept has_il2cpp_conversion = requires (T t) { + concept has_il2cpp_conversion = std::is_constructible_v && requires (T t) { {t.convert()} -> convertible_to; - std::is_constructible_v; }; #pragma region value marker From 804c0de44ed4f9a90175167139b540d0de3322d2 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Fri, 15 Dec 2023 21:20:51 +0100 Subject: [PATCH 14/89] Implement klass get ters for EnumPtr and VTPtr --- shared/utils/enum-wrapper-type.hpp | 12 ++++++++++++ shared/utils/value-wrapper-type.hpp | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/shared/utils/enum-wrapper-type.hpp b/shared/utils/enum-wrapper-type.hpp index 70ffc8fb..5c506c51 100644 --- a/shared/utils/enum-wrapper-type.hpp +++ b/shared/utils/enum-wrapper-type.hpp @@ -1,6 +1,9 @@ #pragma once #include "value-wrapper-type.hpp" +#include "il2cpp-type-check.hpp" +#include "il2cpp-functions.hpp" + namespace bs_hook { // 0 special case, but otherwise the size should be an actual amount of bytes it could be @@ -29,6 +32,15 @@ namespace bs_hook { }; } +template<> +struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<::bs_hook::EnumPtr> { + static inline Il2CppClass* get() { + auto enumClass = il2cpp_utils::GetClassFromName("System", "Enum"); + static auto ptrKlass = il2cpp_functions::il2cpp_Class_GetPtrClass(enumClass); + return ptrKlass; + } +}; + template struct ::il2cpp_utils::ValueTypeTrait<::bs_hook::EnumTypeWrapper> { constexpr static bool value = true; diff --git a/shared/utils/value-wrapper-type.hpp b/shared/utils/value-wrapper-type.hpp index 068def84..2f66d90e 100644 --- a/shared/utils/value-wrapper-type.hpp +++ b/shared/utils/value-wrapper-type.hpp @@ -1,7 +1,10 @@ #pragma once + #include #include #include "type-concepts.hpp" +#include "il2cpp-type-check.hpp" +#include "il2cpp-functions.hpp" namespace bs_hook { template @@ -42,6 +45,15 @@ namespace bs_hook { } +template<> +struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<::bs_hook::VTPtr> { + static inline Il2CppClass* get() { + auto enumClass = il2cpp_utils::GetClassFromName("System", "ValueType"); + static auto ptrKlass = il2cpp_functions::il2cpp_Class_GetPtrClass(enumClass); + return ptrKlass; + } +}; + template struct ::il2cpp_utils::ValueTypeTrait<::bs_hook::ValueTypeWrapper> { constexpr static bool value = true; From d621fcb79819ccfefff10127172059a9daede690 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Sat, 16 Dec 2023 21:01:35 +0100 Subject: [PATCH 15/89] Undo include order shenanigans for Il2CppObject/System.Object --- shared/utils/typedefs-object.hpp | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/shared/utils/typedefs-object.hpp b/shared/utils/typedefs-object.hpp index 10716966..f184ffe1 100644 --- a/shared/utils/typedefs-object.hpp +++ b/shared/utils/typedefs-object.hpp @@ -2,32 +2,6 @@ #include "type-concepts.hpp" -#ifdef HAS_CODEGEN - -#ifdef USE_CODEGEN_FIELDS -#define _HAD_CODEGEN_FIELDS -#else -#define USE_CODEGEN_FIELDS -#endif - -#include "System/zzzz__Object_def.hpp" -#ifndef _HAD_CODEGEN_FIELDS -#undef USE_CODEGEN_FIELDS -#endif - -#undef _HAD_CODEGEN_FIELDS - -typedef Il2CppClass Il2CppVTable; -struct MonitorData; -struct Il2CppObject : public System::Object { - union { - Il2CppClass *klass; - Il2CppVTable *vtable; - }; - MonitorData *monitor; -}; -#include "System/zzzz__Object_impl.hpp" -#else typedef Il2CppClass Il2CppVTable; struct MonitorData; typedef struct Il2CppObject @@ -39,5 +13,5 @@ typedef struct Il2CppObject }; MonitorData *monitor; } Il2CppObject; -#endif + MARK_REF_PTR_T(Il2CppObject); From 12a6c29b7c1ccbcca23cfe3f16a6961780a52234 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Mon, 18 Dec 2023 16:13:44 +0100 Subject: [PATCH 16/89] Add global metadata checks --- src/utils/il2cpp-functions.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/il2cpp-functions.cpp b/src/utils/il2cpp-functions.cpp index 7ddef149..85f39c4d 100644 --- a/src/utils/il2cpp-functions.cpp +++ b/src/utils/il2cpp-functions.cpp @@ -389,10 +389,12 @@ TypeDefinitionIndex il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(co } TypeDefinitionIndex il2cpp_functions::MetadataCache_GetIndexForTypeDefinition(const Il2CppClass* klass) { + CheckS_GlobalMetadata(); return MetadataCache_GetIndexForTypeDefinition(reinterpret_cast(klass->typeMetadataHandle)); } GenericParameterIndex il2cpp_functions::MetadataCache_GetGenericParameterIndexFromParameter(Il2CppMetadataGenericParameterHandle handle) { + CheckS_GlobalMetadata(); const Il2CppGenericParameter* genericParameter = reinterpret_cast(handle); const Il2CppGenericParameter* genericParameters = (const Il2CppGenericParameter*)((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->genericParametersOffset); @@ -404,10 +406,12 @@ GenericParameterIndex il2cpp_functions::MetadataCache_GetGenericParameterIndexFr } const Il2CppTypeDefinition* il2cpp_functions::MetadataCache_GetTypeDefinition(Il2CppClass* klass) { + CheckS_GlobalMetadata(); return reinterpret_cast(klass->typeMetadataHandle); } GenericParameterIndex il2cpp_functions::MetadataCache_GetGenericContainerIndex(Il2CppClass* klass) { + CheckS_GlobalMetadata(); return MetadataCache_GetTypeDefinition(klass)->genericContainerIndex; } From da365152e93bf9fe7774e9c5e1dc308f3302b0bc Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 19 Dec 2023 19:12:18 +0100 Subject: [PATCH 17/89] Fix vtable lookup class init --- src/utils/il2cpp-utils-methods.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 7a48e2aa..0ed59e13 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -90,7 +90,7 @@ namespace il2cpp_utils { const MethodInfo* ResolveVtableSlot(Il2CppClass* klass, Il2CppClass* declaringClass, uint16_t slot) noexcept { il2cpp_functions::Init(); if (!klass->initialized_and_no_error) il2cpp_functions::Class_Init(klass); - if (!declaringClass->initialized_and_no_error) il2cpp_functions::Class_Init(klass); + if (!declaringClass->initialized_and_no_error) il2cpp_functions::Class_Init(declaringClass); static auto logger = getLogger().WithContext("ResolveVtableSlot"); if(il2cpp_functions::class_is_interface(declaringClass)) { From c65bc63db97c6ae2269e08138ab5ae125b39a49e Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 19 Dec 2023 19:14:38 +0100 Subject: [PATCH 18/89] Fix safe ptr GC interactions --- shared/utils/typedefs-wrappers.hpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/shared/utils/typedefs-wrappers.hpp b/shared/utils/typedefs-wrappers.hpp index 68bc1ed8..efca987c 100644 --- a/shared/utils/typedefs-wrappers.hpp +++ b/shared/utils/typedefs-wrappers.hpp @@ -273,10 +273,14 @@ struct SafePtr { // Otherwise, some other SafePtr is currently holding a reference to this instance, so keep it around. if (internalHandle.count() <= 1) { il2cpp_functions::Init(); + #ifdef UNITY_2021 + il2cpp_functions::gc_free_fixed(internalHandle.__internal_get()); + #else if (!il2cpp_functions::hasGCFuncs) { SAFE_ABORT_MSG("Cannot use SafePtr without GC functions!"); } il2cpp_functions::GC_free(internalHandle.__internal_get()); + #endif } } @@ -425,15 +429,24 @@ struct SafePtr { struct SafePointerWrapper { static SafePointerWrapper* New(T* instance) { il2cpp_functions::Init(); + // It should be safe to assume that gc_alloc_fixed returns a non-null pointer. If it does return null, we have a pretty big issue. + static constexpr auto sz = sizeof(SafePointerWrapper); + + #ifdef UNITY_2021 + auto* wrapper = reinterpret_cast(il2cpp_functions::gc_alloc_fixed(sz)); + + #else + if (!il2cpp_functions::hasGCFuncs) { -#if __has_feature(cxx_exceptions) + #if __has_feature(cxx_exceptions) throw CreatedTooEarlyException(); -#else + #else SAFE_ABORT_MSG("Cannot use a SafePtr this early/without GC functions!"); -#endif + #endif } - // It should be safe to assume that GC_AllocateFixed returns a non-null pointer. If it does return null, we have a pretty big issue. - auto* wrapper = reinterpret_cast(il2cpp_functions::GarbageCollector_AllocateFixed(sizeof(SafePointerWrapper), nullptr)); + auto* wrapper = reinterpret_cast(il2cpp_functions::GarbageCollector_AllocateFixed(sz, nullptr)); + #endif + CRASH_UNLESS(wrapper); wrapper->instancePointer = instance; return wrapper; From 71833bdbc9472093d64bd57fb9681f0dc54a14ae Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 19 Dec 2023 21:34:08 +0100 Subject: [PATCH 19/89] Fix ExtractValue --- shared/utils/il2cpp-utils-methods.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index 50d5e1a7..ea2ad1a5 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -184,7 +184,7 @@ namespace il2cpp_utils { } } return arg; - } else if constexpr (has_il2cpp_conversion) { + } else if constexpr (has_il2cpp_conversion
) { return arg.convert(); } else { From a563691159d2eeebc14e38a427efb703132e8bbb Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 19 Dec 2023 22:52:15 +0100 Subject: [PATCH 20/89] Explicitly specify value extraction --- shared/utils/il2cpp-utils-methods.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index ea2ad1a5..385e90e2 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -146,7 +146,7 @@ namespace il2cpp_utils { requires (!::std::is_same_v) #endif const MethodInfo* ResolveVtableSlot(T&& instance, TArgs&&... args) noexcept { - return ResolveVtableSlot(ExtractClass(instance), args...); + return ResolveVtableSlot(::il2cpp_utils::ExtractClass(instance), args...); } template @@ -158,7 +158,7 @@ namespace il2cpp_utils { return nullptr; } static auto& logger = getLogger(); - auto* klass = RET_0_UNLESS(logger, ExtractClass(arg)); + auto* klass = RET_0_UNLESS(logger, ::il2cpp_utils::ExtractClass(arg)); return il2cpp_functions::value_box(klass, &arg); } @@ -222,8 +222,8 @@ namespace il2cpp_utils { template ::std::vector ExtractValues(T&& arg, TArgs&& ...args) { - auto* firstVal = ExtractValue(arg); - auto otherVals = ExtractValues(args...); + auto* firstVal = ::il2cpp_utils::ExtractValue(arg); + auto otherVals = ::il2cpp_utils::ExtractValues(args...); otherVals.insert(otherVals.begin(), firstVal); // inserts at front return otherVals; } @@ -505,9 +505,9 @@ namespace il2cpp_utils { } } - void* inst = ExtractValue(instance); // null is allowed (for T = Il2CppType* or Il2CppClass*) + void* inst = ::il2cpp_utils::ExtractValue(instance); // null is allowed (for T = Il2CppType* or Il2CppClass*) Il2CppException* exp = nullptr; - std::array invokeParams{ExtractTypeValue(params)...}; + std::array invokeParams{::il2cpp_utils::ExtractTypeValue(params)...}; il2cpp_functions::Init(); auto* ret = il2cpp_functions::runtime_invoke(method, inst, invokeParams.data(), &exp); @@ -569,9 +569,9 @@ namespace il2cpp_utils { RET_NULLOPT_UNLESS(logger, ParameterMatch(method, types)); } - void* inst = ExtractValue(instance); // null is allowed (for T = Il2CppType* or Il2CppClass*) + void* inst = ::il2cpp_utils::ExtractValue(instance); // null is allowed (for T = Il2CppType* or Il2CppClass*) Il2CppException* exp = nullptr; - std::array invokeParams{ExtractValue(params)...}; + std::array invokeParams{::il2cpp_utils::ExtractValue(params)...}; il2cpp_functions::Init(); auto* ret = il2cpp_functions::runtime_invoke(method, inst, invokeParams.data(), &exp); From 351982ba8d56da53e334ddb3cdb566fceda5cc07 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 21 Dec 2023 15:11:12 +0100 Subject: [PATCH 21/89] Add util for il2cpp aware threads --- shared/utils/il2cpp-utils.hpp | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index ea8785df..9ba9f623 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -1,6 +1,7 @@ #ifndef IL2CPP_UTILS_H #define IL2CPP_UTILS_H +#include #pragma pack(push) #include @@ -23,6 +24,7 @@ #include "il2cpp-utils-fields.hpp" #include #include +#include #include #include #include @@ -696,6 +698,67 @@ namespace il2cpp_utils { } return out; } + + /// @brief method executed by the thread created in il2cpp_aware_thread + /// @param pred the predicate to use in the thread + /// @param args the args used + template + void il2cpp_aware_thread_method(Predicate pred, TArgs&&... args) { + std::stringstream loggerContext; loggerContext << "Thread " << std::this_thread::get_id(); + auto logger = getLogger().WithContext(loggerContext.str()); // logger is per thread id, can't be static + + logger.info("Attaching thread"); + auto domain = il2cpp_functions::domain_get(); + auto thread = il2cpp_functions::thread_attach(domain); + + logger.info("Invoking predicate"); + pred(args...); + + logger.info("Detaching thread"); + il2cpp_functions::thread_detach(thread); + } + + /// @brief method executed by the thread created in il2cpp_aware_thread + /// @param pred the predicate to use in the thread + /// @param args the args used + template + requires(std::is_convertible_v) + void il2cpp_aware_thread_method(T& instance, void (U::*method)(TArgs...), TArgs&&... args) { + std::stringstream loggerContext; loggerContext << "Thread " << std::this_thread::get_id(); + auto logger = getLogger().WithContext(loggerContext.str()); // logger is per thread id, can't be static + + logger.info("Attaching thread"); + auto domain = il2cpp_functions::domain_get(); + auto thread = il2cpp_functions::thread_attach(domain); + + logger.info("Invoking predicate"); + instance->*method(args...); + + logger.info("Detaching thread"); + il2cpp_functions::thread_detach(thread); + } + + /// @brief creates a thread that automatically will register with il2cpp and deregister once it exits, ensure your args live longer than the thread if they're by reference! + /// @param pred the predicate to use for the thread + /// @param args the arguments to pass to the thread (& predicate) + /// @return created thread, which is the same as you creating a default one + template + inline std::thread il2cpp_aware_thread(Predicate pred, TArgs&&... args) { + il2cpp_functions::Init(); + return std::thread(&il2cpp_aware_thread_method, pred, std::forward(args)...); + } + + /// @brief creates a thread that automatically will register with il2cpp and deregister once it exits, ensure your args live longer than the thread if they're by reference! + /// @param instance the instance on which the method will be called + /// @param method the member method to call + /// @param args the arguments to pass to the method + /// @return created thread, which is the same as you creating a default one + template + requires(std::is_convertible_v) + inline std::thread il2cpp_aware_thread(T& instance, void (U::*method)(TArgs...), TArgs&&... args) { + il2cpp_functions::Init(); + return std::thread(&il2cpp_aware_thread_method, instance, method, std::forward(args)...); + } } #pragma pack(pop) From 7f8c28101489600d79eaf88ff8ad12637ee886e7 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 21 Dec 2023 21:01:50 +0100 Subject: [PATCH 22/89] Fix il2cpp thread util to be more or less equivalent to std::thread, and add tests --- CMakeLists.txt | 1 + shared/utils/il2cpp-utils.hpp | 102 ++++++++++++++----------------- src/tests/test-check.cpp | 2 +- src/tests/thread-tests.cpp | 109 ++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 60 deletions(-) create mode 100644 src/tests/thread-tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c06a92e..e9213d6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ function(setup_target target add_test) TEST_LIST TEST_STRING TEST_HOOK + TEST_THREAD ) endif() diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index 9ba9f623..0231a59b 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -23,6 +23,7 @@ #include "il2cpp-utils-properties.hpp" #include "il2cpp-utils-fields.hpp" #include +#include #include #include #include @@ -699,66 +700,49 @@ namespace il2cpp_utils { return out; } - /// @brief method executed by the thread created in il2cpp_aware_thread - /// @param pred the predicate to use in the thread - /// @param args the args used - template - void il2cpp_aware_thread_method(Predicate pred, TArgs&&... args) { - std::stringstream loggerContext; loggerContext << "Thread " << std::this_thread::get_id(); - auto logger = getLogger().WithContext(loggerContext.str()); // logger is per thread id, can't be static - - logger.info("Attaching thread"); - auto domain = il2cpp_functions::domain_get(); - auto thread = il2cpp_functions::thread_attach(domain); - - logger.info("Invoking predicate"); - pred(args...); - - logger.info("Detaching thread"); - il2cpp_functions::thread_detach(thread); - } - - /// @brief method executed by the thread created in il2cpp_aware_thread - /// @param pred the predicate to use in the thread - /// @param args the args used - template - requires(std::is_convertible_v) - void il2cpp_aware_thread_method(T& instance, void (U::*method)(TArgs...), TArgs&&... args) { - std::stringstream loggerContext; loggerContext << "Thread " << std::this_thread::get_id(); - auto logger = getLogger().WithContext(loggerContext.str()); // logger is per thread id, can't be static - - logger.info("Attaching thread"); - auto domain = il2cpp_functions::domain_get(); - auto thread = il2cpp_functions::thread_attach(domain); - - logger.info("Invoking predicate"); - instance->*method(args...); - - logger.info("Detaching thread"); - il2cpp_functions::thread_detach(thread); - } - - /// @brief creates a thread that automatically will register with il2cpp and deregister once it exits, ensure your args live longer than the thread if they're by reference! - /// @param pred the predicate to use for the thread - /// @param args the arguments to pass to the thread (& predicate) - /// @return created thread, which is the same as you creating a default one - template - inline std::thread il2cpp_aware_thread(Predicate pred, TArgs&&... args) { - il2cpp_functions::Init(); - return std::thread(&il2cpp_aware_thread_method, pred, std::forward(args)...); - } + struct il2cpp_aware_thread : public std::thread { + public: + /// @brief method executed by the thread created in il2cpp_aware_thread + /// @param pred the predicate to use in the thread + /// @param args the args used + template + static void internal_thread(Predicate&& pred, TArgs&&... args) { + il2cpp_functions::Init(); + std::stringstream loggerContext; loggerContext << "Thread " << std::this_thread::get_id(); + auto logger = getLogger().WithContext(loggerContext.str()); // logger is per thread id, can't be static + + logger.info("Attaching thread"); + auto domain = il2cpp_functions::domain_get(); + auto thread = il2cpp_functions::thread_attach(domain); + + logger.info("Invoking predicate"); + if constexpr (sizeof...(TArgs) > 0) { + std::invoke(std::forward(pred), std::forward(args)...); + } else { + std::invoke(std::forward(pred)); + } + + logger.info("Detaching thread"); + il2cpp_functions::thread_detach(thread); + } - /// @brief creates a thread that automatically will register with il2cpp and deregister once it exits, ensure your args live longer than the thread if they're by reference! - /// @param instance the instance on which the method will be called - /// @param method the member method to call - /// @param args the arguments to pass to the method - /// @return created thread, which is the same as you creating a default one - template - requires(std::is_convertible_v) - inline std::thread il2cpp_aware_thread(T& instance, void (U::*method)(TArgs...), TArgs&&... args) { - il2cpp_functions::Init(); - return std::thread(&il2cpp_aware_thread_method, instance, method, std::forward(args)...); - } + /// @brief creates a thread that automatically will register with il2cpp and deregister once it exits, ensure your args live longer than the thread if they're by reference! + /// @param pred the predicate to use for the thread + /// @param args the arguments to pass to the thread (& predicate) + /// @return created thread, which is the same as you creating a default one + template + explicit il2cpp_aware_thread(Predicate&& pred, TArgs&&... args) : + std::thread( + &internal_thread...>, + std::forward(pred), + std::forward(args)... + ) + {} + + ~il2cpp_aware_thread() { + if (joinable()) join(); + } + }; } #pragma pack(pop) diff --git a/src/tests/test-check.cpp b/src/tests/test-check.cpp index 8810677d..aa8c6786 100644 --- a/src/tests/test-check.cpp +++ b/src/tests/test-check.cpp @@ -1,5 +1,5 @@ #ifdef NO_TEST -#if defined(TEST_CALLBACKS) || defined(TEST_SAFEPTR) || defined(TEST_BYREF) || defined(TEST_ARRAY) || defined(TEST_LIST) || defined(TEST_STRING) || defined(TEST_HOOK) +#if defined(TEST_CALLBACKS) || defined(TEST_SAFEPTR) || defined(TEST_BYREF) || defined(TEST_ARRAY) || defined(TEST_LIST) || defined(TEST_STRING) || defined(TEST_HOOK) || defined(TEST_THREAD) #error "tests are being built into the release for bs hook!" #endif #endif diff --git a/src/tests/thread-tests.cpp b/src/tests/thread-tests.cpp new file mode 100644 index 00000000..cddaf368 --- /dev/null +++ b/src/tests/thread-tests.cpp @@ -0,0 +1,109 @@ +#ifdef TEST_THREAD +#include "utils/il2cpp-utils.hpp" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wunused-parameter" + +static void thread_method() {} +static void thread_method_value(int v) {} +static void thread_method_lvalue(int& v) {} +static void thread_method_rvalue(int&& v) {} + +struct ThreadTest { + void method() {} + + void method_value(int v) {} + void method_lvalue(int& v) {} + void method_rvalue(int&& v) {} + + // can we do all the same things from an instance of a struct + void test_thread(); + + void some_call() {}; +}; + +// test both il2cpp aware thread and std::thread for equivalence +#define IL2CPP_THREAD_TEST(...) \ + il2cpp_utils::il2cpp_aware_thread(__VA_ARGS__).join(); \ + std::thread(__VA_ARGS__).join() + +void test_thread() { + // can we make a 0 arg lambda thread? + IL2CPP_THREAD_TEST([](){}); + + IL2CPP_THREAD_TEST(&thread_method); + + int v = 0; + + // can we capture? + IL2CPP_THREAD_TEST([=](){ int b = v + 1; }); + IL2CPP_THREAD_TEST([&](){ v += 1;}); + IL2CPP_THREAD_TEST([v](){ int b = v + 1; }); + IL2CPP_THREAD_TEST([value = v](){ int b = value + 1; }); + IL2CPP_THREAD_TEST([value = &v](){ *value += 1; }); + IL2CPP_THREAD_TEST([&v](){ v += 1; }); + + // can we make a lambda that accepts an integer from lvalue & rvalue? + IL2CPP_THREAD_TEST([](int v){}, v); + IL2CPP_THREAD_TEST([](int v){}, 10); + + // pass rvalue into lvalue + // IL2CPP_THREAD_TEST([]( int& v){}, 10); // should not work + // pass rvalue into rvalue + IL2CPP_THREAD_TEST([](int&& v){}, 10); // should work + // pass lvalue into lvalue + // IL2CPP_THREAD_TEST([]( int& v){}, v); // should work + // pass lvalue into rvalue + IL2CPP_THREAD_TEST([](int&& v){}, v); // should work + + // the same for a method? + IL2CPP_THREAD_TEST(&thread_method_value, v); // should work + IL2CPP_THREAD_TEST(&thread_method_value, 10); // should work + + // can we pass the value as appropriate? + // lvalue into lvalue + // IL2CPP_THREAD_TEST(&thread_method_lvalue, v); // should not work + // lvalue into rvalue + IL2CPP_THREAD_TEST(&thread_method_rvalue, v); // should work + // rvalue into lvalue + // IL2CPP_THREAD_TEST(&thread_method_lvalue, 10); // should not work + // rvalue into rvalue + IL2CPP_THREAD_TEST(&thread_method_rvalue, 10); // should work + + ThreadTest tt; + tt.test_thread(); +} + +void ThreadTest::test_thread() { + // can we make a 0 arg lambda thread? + IL2CPP_THREAD_TEST([](){}); + IL2CPP_THREAD_TEST(&ThreadTest::method, this); + + int v = 0; + + // can we capture? + IL2CPP_THREAD_TEST([this](){ some_call(); }); + IL2CPP_THREAD_TEST([=](){ int b = v + 1; }); + IL2CPP_THREAD_TEST([&](){ some_call(); }); + IL2CPP_THREAD_TEST([v](){ int b = v + 1; }); + IL2CPP_THREAD_TEST([value = v](){ int b = value + 1; }); + IL2CPP_THREAD_TEST([value = &v](){ *value += 1; }); + IL2CPP_THREAD_TEST([&v](){ v += 1; }); + + // can we make a lambda that accepts an integer from lvalue & rvalue? + IL2CPP_THREAD_TEST([](ThreadTest* self, int v){}, this, v); + IL2CPP_THREAD_TEST([](ThreadTest* self, int v){}, this, 10); + + // the same for a method? + IL2CPP_THREAD_TEST(&ThreadTest::method_value, this, v); + IL2CPP_THREAD_TEST(&ThreadTest::method_value, this, 10); + + // can we pass the value as appropriate? + // IL2CPP_THREAD_TEST(&ThreadTest::method_lvalue, this, v); // should not work + IL2CPP_THREAD_TEST(&ThreadTest::method_rvalue, this, 10); +} + +#pragma clang diagnostic pop + +#endif From d3eabaf1f111969668c2527dfa72e5097f5acc58 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 21 Dec 2023 21:17:49 +0100 Subject: [PATCH 23/89] Tweaks to thread util --- shared/utils/il2cpp-utils.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index 0231a59b..d56431f8 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -706,6 +706,7 @@ namespace il2cpp_utils { /// @param pred the predicate to use in the thread /// @param args the args used template + requires(std::is_invocable_v...>) static void internal_thread(Predicate&& pred, TArgs&&... args) { il2cpp_functions::Init(); std::stringstream loggerContext; loggerContext << "Thread " << std::this_thread::get_id(); @@ -716,11 +717,7 @@ namespace il2cpp_utils { auto thread = il2cpp_functions::thread_attach(domain); logger.info("Invoking predicate"); - if constexpr (sizeof...(TArgs) > 0) { - std::invoke(std::forward(pred), std::forward(args)...); - } else { - std::invoke(std::forward(pred)); - } + std::invoke(std::forward(pred), std::forward>(args)...); logger.info("Detaching thread"); il2cpp_functions::thread_detach(thread); @@ -731,6 +728,7 @@ namespace il2cpp_utils { /// @param args the arguments to pass to the thread (& predicate) /// @return created thread, which is the same as you creating a default one template + requires(std::is_invocable_v...>) explicit il2cpp_aware_thread(Predicate&& pred, TArgs&&... args) : std::thread( &internal_thread...>, @@ -739,6 +737,10 @@ namespace il2cpp_utils { ) {} + /// @brief defaulted move ctor + il2cpp_aware_thread(il2cpp_aware_thread&&) = default; + + /// @brief if joinable and destructed, join ~il2cpp_aware_thread() { if (joinable()) join(); } From a5e0f216ed9a23472e1bac5c84ec60591e693eec Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 21 Dec 2023 21:41:49 +0100 Subject: [PATCH 24/89] Add catch logic to il2cpp aware thread --- shared/utils/il2cpp-utils.hpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index d56431f8..6857071d 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -1,7 +1,6 @@ #ifndef IL2CPP_UTILS_H #define IL2CPP_UTILS_H -#include #pragma pack(push) #include @@ -702,6 +701,7 @@ namespace il2cpp_utils { struct il2cpp_aware_thread : public std::thread { public: + /// @brief method executed by the thread created in il2cpp_aware_thread /// @param pred the predicate to use in the thread /// @param args the args used @@ -709,7 +709,7 @@ namespace il2cpp_utils { requires(std::is_invocable_v...>) static void internal_thread(Predicate&& pred, TArgs&&... args) { il2cpp_functions::Init(); - std::stringstream loggerContext; loggerContext << "Thread " << std::this_thread::get_id(); + std::stringstream loggerContext; loggerContext << "internal_thread_" << std::this_thread::get_id(); auto logger = getLogger().WithContext(loggerContext.str()); // logger is per thread id, can't be static logger.info("Attaching thread"); @@ -717,7 +717,28 @@ namespace il2cpp_utils { auto thread = il2cpp_functions::thread_attach(domain); logger.info("Invoking predicate"); - std::invoke(std::forward(pred), std::forward>(args)...); + try { + std::invoke(std::forward(pred), std::forward>(args)...); + } catch(RunMethodException const& e) { + logger.error("Caught in mod id: " _CATCH_HANDLER_ID ": Uncaught RunMethodException! what(): %s", e.what()); + e.log_backtrace(); + if (e.ex) e.rethrow(); + il2cpp_functions::thread_detach(thread); + SAFE_ABORT(); + } catch(exceptions::StackTraceException const& e) { + logger.error("Caught in mod id: " _CATCH_HANDLER_ID ": Uncaught StackTraceException! what(): %s", e.what()); + e.log_backtrace(); + il2cpp_functions::thread_detach(thread); + SAFE_ABORT(); + } catch(std::exception& e) { + logger.error("Caught in mod id: " _CATCH_HANDLER_ID ": Uncaught C++ exception! type name: %s, what(): %s", typeid(e).name(), e.what()); + il2cpp_functions::thread_detach(thread); + SAFE_ABORT(); + } catch(...) { + logger.error("Caught in mod id: " _CATCH_HANDLER_ID ": Uncaught, unknown C++ exception (not std::exception) with no known what() method!"); + il2cpp_functions::thread_detach(thread); + SAFE_ABORT(); + } logger.info("Detaching thread"); il2cpp_functions::thread_detach(thread); From cbb910c4e12469751c2c36e44275976826dfaf13 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Thu, 21 Dec 2023 22:48:44 +0100 Subject: [PATCH 25/89] Implement nested type lookups in GetClassFromName --- src/utils/il2cpp-type-check.cpp | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/utils/il2cpp-type-check.cpp b/src/utils/il2cpp-type-check.cpp index 4a8058c9..b574d680 100644 --- a/src/utils/il2cpp-type-check.cpp +++ b/src/utils/il2cpp-type-check.cpp @@ -13,6 +13,32 @@ namespace il2cpp_utils { static std::unordered_map, Il2CppClass*, hash_pair> namesToClassesCache; static std::mutex nameHashLock; + Il2CppClass* FindNested(Il2CppClass* declaring, std::string_view typeName) { + static auto logger = getLogger().WithContext("FindNested"); + // logger.info("trying to find: %s ", typeName.data()); + + if (!declaring) return nullptr; + auto token = typeName.find("/"); + bool deeperNested = token != std::string::npos; + + auto subTypeName = deeperNested ? typeName : typeName.substr(0, token); + + void* myIter = nullptr; + Il2CppClass* found = nullptr; + while (Il2CppClass* nested = il2cpp_functions::class_get_nested_types(declaring, &myIter)) { + if (subTypeName == nested->name) { + found = nested; + break; + } + } + + if (deeperNested) { + return FindNested(found, typeName.substr(token + 1)); + } else { + return found; + } + } + Il2CppClass* GetClassFromName(std::string_view name_space, std::string_view type_name) { il2cpp_functions::Init(); static auto logger = getLogger().WithContext("GetClassFromName"); @@ -46,6 +72,26 @@ namespace il2cpp_utils { return klass; } } + + // we failed to find the class directly, time to check if it is a nested class, and if so look for it + auto token = type_name.find("/"); + bool nested = token != std::string::npos; + if (nested) { // this is a nested name + // get the first part of the nested type_name + auto declaringTypeName = std::string(type_name.substr(0, token)); + // get the first class, which is the declaring class + auto declaring = GetClassFromName(name_space, declaringTypeName); + // recursively look through the nested classes of the declaring class until we run out of tokens ('/') or we run into a problem where we don't find a class + auto klass = FindNested(declaring, type_name.substr(token + 1)); + + if (klass) { + nameHashLock.lock(); + namesToClassesCache.emplace(key, klass); + nameHashLock.unlock(); + return klass; + } + } + logger.error("Could not find class with namepace: %s and name: %s", name_space.data(), type_name.data()); return nullptr; From b9257f550535c439d265bc83321c69b992ae4db8 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Fri, 22 Dec 2023 14:24:20 +0100 Subject: [PATCH 26/89] Fix ResolveVtableSlot --- src/utils/il2cpp-utils-methods.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 0ed59e13..edeb17d0 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -94,7 +94,20 @@ namespace il2cpp_utils { static auto logger = getLogger().WithContext("ResolveVtableSlot"); if(il2cpp_functions::class_is_interface(declaringClass)) { - RET_DEFAULT_UNLESS(logger, slot < declaringClass->vtable_count); + // if the declaring class is an interface, + // vtable_count means nothing and instead method_count should be used + // their vtables are just as valid though! + if (slot >= declaringClass->method_count) { // we tried looking for a slot that is outside the bounds of the interface vtable + // dump some info so the user can know which method was attempted to be resolved + logger.error("Declaring class has a vtable that's too small, dumping resolve info:"); + logger.error("Instance class: %s::%s", klass->namespaze, klass->name); + logger.error("Instance class vtable slots: %u", klass->vtable_count); + logger.error("Declaring class: %s::%s", declaringClass->namespaze, declaringClass->name); + logger.error("Declaring class vtable slots: %u", declaringClass->vtable_count); + logger.error("Attempted slot: %u", slot); + return {}; + } + for (uint16_t i = 0; i < klass->interface_offsets_count; i++) { if(klass->interfaceOffsets[i].interfaceType == declaringClass) { int32_t offset = klass->interfaceOffsets[i].offset; From 7c1f6047ad1a74e39948c97c371bc7116054ff58 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Fri, 22 Dec 2023 15:30:00 +0100 Subject: [PATCH 27/89] If we are resolving a slot on an interface, read into the methods array --- src/utils/il2cpp-utils-methods.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index edeb17d0..d451d13d 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -115,9 +115,15 @@ namespace il2cpp_utils { return klass->vtable[offset + slot].method; } } + + // if klass is an interface itself, and we haven't found the method yet, we should look in klass->methods anyway + if (il2cpp_functions::class_is_interface(klass)) { + RET_DEFAULT_UNLESS(logger, slot < klass->method_count); + return klass->methods[slot]; + } + logger.error("could not find method in slot %i of interface '%s' in class '%s'!", slot, ClassStandardName(declaringClass).c_str(), ClassStandardName(klass).c_str()); - } - else { + } else { RET_DEFAULT_UNLESS(logger, slot < klass->vtable_count); return klass->vtable[slot].method; } From 8da2b3a831a2ccc4df9b3d5f52cc26a5b72cdccb Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Fri, 22 Dec 2023 19:25:24 -0400 Subject: [PATCH 28/89] Remove codegen usages --- .clangd | 6 + shared/rapidjson | 2 +- shared/utils/hashing.hpp | 3 + shared/utils/il2cpp-type-check.hpp | 132 ++++---- shared/utils/il2cpp-utils-classes.hpp | 2 +- shared/utils/il2cpp-utils.hpp | 4 +- shared/utils/manual-il2cpp-typedefs.h | 1 + shared/utils/size-concepts.hpp | 2 + shared/utils/type-concepts.hpp | 54 ++-- shared/utils/typedefs-delegate.hpp | 42 +-- shared/utils/typedefs-list.hpp | 17 +- shared/utils/typedefs-object.hpp | 1 + shared/utils/typedefs.h | 447 +------------------------- shared/utils/utils.h | 14 +- 14 files changed, 147 insertions(+), 580 deletions(-) create mode 100644 .clangd diff --git a/.clangd b/.clangd new file mode 100644 index 00000000..9e1f4440 --- /dev/null +++ b/.clangd @@ -0,0 +1,6 @@ +If: + # Note: This is a regexp, notice '.*' at the end of PathMatch string. + PathMatch: ./extern/.* +Index: + # Disable slow background indexing of these files. + Background: Skip \ No newline at end of file diff --git a/shared/rapidjson b/shared/rapidjson index 00dbcf2c..973dc9c0 160000 --- a/shared/rapidjson +++ b/shared/rapidjson @@ -1 +1 @@ -Subproject commit 00dbcf2c6e03c47d6c399338b6de060c71356464 +Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213 diff --git a/shared/utils/hashing.hpp b/shared/utils/hashing.hpp index f32ddb8c..9c89e018 100644 --- a/shared/utils/hashing.hpp +++ b/shared/utils/hashing.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + // TODO: Make this into a static class namespace il2cpp_utils { // A hash function used to hash a pair of any kind diff --git a/shared/utils/il2cpp-type-check.hpp b/shared/utils/il2cpp-type-check.hpp index 54746566..e51acb31 100644 --- a/shared/utils/il2cpp-type-check.hpp +++ b/shared/utils/il2cpp-type-check.hpp @@ -1,4 +1,6 @@ #include "il2cpp-functions.hpp" +#include "type-concepts.hpp" + #ifndef IL2CPP_TYPE_CHECK_H #define IL2CPP_TYPE_CHECK_H @@ -42,6 +44,7 @@ constexpr bool has_get = std::experimental::is_detected_v; #endif namespace il2cpp_utils { + // Returns the il2cpp_utils logger context singleton. LoggerContextObject& getLogger(); @@ -55,11 +58,12 @@ namespace il2cpp_utils { // Framework provided by DaNike namespace il2cpp_type_check { + namespace { template /// @brief Describes whether the type T needs to be boxed or not (for instance method invokes). /// This is true for all non-pointer types by default. /// @tparam T The type to be boxed or not. - struct need_box { + struct BS_HOOKS_HIDDEN need_box { constexpr static bool value = true; }; @@ -67,7 +71,7 @@ namespace il2cpp_utils { /// @brief Describes whether the type T needs to be boxed or not (for instance method invokes). /// This is false for T*s by default. /// @tparam T The type to be boxed or not. - struct need_box { + struct BS_HOOKS_HIDDEN need_box { constexpr static bool value = false; }; @@ -96,7 +100,7 @@ namespace il2cpp_utils { struct il2cpp_no_arg_class>>> { #else requires has_get> - struct il2cpp_no_arg_class { + struct BS_HOOKS_HIDDEN il2cpp_no_arg_class { #endif static inline Il2CppClass* get() { il2cpp_functions::Init(); @@ -119,8 +123,8 @@ namespace il2cpp_utils { #ifndef BS_HOOK_USE_CONCEPTS struct il2cpp_no_arg_class && T::__IL2CPP_IS_VALUE_TYPE>> { #else - requires (std::is_base_of_v && T::__IL2CPP_IS_VALUE_TYPE) - struct il2cpp_no_arg_class { + requires(std::is_base_of_v && T::__IL2CPP_IS_VALUE_TYPE) + struct BS_HOOKS_HIDDEN il2cpp_no_arg_class { #endif // TODO: make this work on any class with a `using declaring_type`, then remove NestedType static inline Il2CppClass* get() { @@ -165,7 +169,7 @@ namespace il2cpp_utils { struct il2cpp_no_arg_class && !T::__IL2CPP_IS_VALUE_TYPE>> { #else requires (std::is_base_of_v && !T::__IL2CPP_IS_VALUE_TYPE) - struct il2cpp_no_arg_class { + struct BS_HOOKS_HIDDEN il2cpp_no_arg_class { #endif // TODO: make this work on any class with a `using declaring_type`, then remove NestedType static inline Il2CppClass* get() { @@ -204,8 +208,8 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_arg_class { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_class { static inline Il2CppClass* get([[maybe_unused]] T arg) { if constexpr (has_get>) { return il2cpp_no_arg_class::get(); @@ -214,9 +218,9 @@ namespace il2cpp_utils { } }; - #define DEFINE_IL2CPP_DEFAULT_TYPE(type, fieldName) \ +#define DEFINE_IL2CPP_DEFAULT_TYPE(type, fieldName) \ template<> \ - struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class { \ + struct BS_HOOKS_HIDDEN ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class { \ static inline Il2CppClass* get() { \ il2cpp_functions::Init(); \ return il2cpp_functions::defaults->fieldName##_class; \ @@ -225,7 +229,7 @@ namespace il2cpp_utils { #define DEFINE_IL2CPP_ARG_TYPE(type, nameSpace, className) \ template<> \ - struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class { \ + struct BS_HOOKS_HIDDEN ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class { \ static inline Il2CppClass* get() { \ return il2cpp_utils::GetClassFromName(nameSpace, className); \ } \ @@ -258,15 +262,15 @@ namespace il2cpp_utils { NEED_NO_BOX(Il2CppClass); - template<> - struct il2cpp_arg_class { + template <> + struct BS_HOOKS_HIDDEN il2cpp_arg_class { static inline Il2CppClass* get(Il2CppClass* arg) { return arg; } }; - template<> - struct il2cpp_arg_class { + template <> + struct BS_HOOKS_HIDDEN il2cpp_arg_class { static inline Il2CppClass* get(Il2CppType* arg) { static auto& logger = getLogger(); RET_0_UNLESS(logger, arg); @@ -277,8 +281,8 @@ namespace il2cpp_utils { // TODO: is_il2cpp_object type trait? - template - struct il2cpp_arg_class { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_class { static inline Il2CppClass* get(T* arg) { using element_arg_class = il2cpp_no_arg_class; if constexpr (has_get) { @@ -315,7 +319,7 @@ namespace il2cpp_utils { struct il2cpp_no_arg_class, typename std::enable_if_t>>> { #else requires has_get> - struct il2cpp_no_arg_class> { + struct BS_HOOKS_HIDDEN il2cpp_no_arg_class> { #endif static inline Il2CppClass* get() { auto* klass = il2cpp_gen_struct_no_arg_class::get(); @@ -323,19 +327,19 @@ namespace il2cpp_utils { } }; - template class S> - struct need_box> { + template class S> + struct BS_HOOKS_HIDDEN need_box> { constexpr static bool value = need_box_gen::value; }; - template class S> - struct need_box*> { + template class S> + struct BS_HOOKS_HIDDEN need_box*> { constexpr static bool value = false; }; template class S> requires (S::__IL2CPP_IS_VALUE_TYPE && has_get::declaring_type>>) - struct il2cpp_no_arg_class> { + struct BS_HOOKS_HIDDEN il2cpp_no_arg_class> { static inline Il2CppClass* get() { // Resolve our declaring type Il2CppClass* declaring = il2cpp_no_arg_class::declaring_type>::get(); @@ -365,7 +369,7 @@ namespace il2cpp_utils { template class S> requires (!S::__IL2CPP_IS_VALUE_TYPE && has_get::declaring_type>>) - struct il2cpp_no_arg_class*> { + struct BS_HOOKS_HIDDEN il2cpp_no_arg_class*> { static inline Il2CppClass* get() { // Resolve our declaring type Il2CppClass* declaring = il2cpp_no_arg_class::declaring_type>::get(); @@ -394,7 +398,7 @@ namespace il2cpp_utils { }; template class S> - struct il2cpp_no_arg_class*> { + struct BS_HOOKS_HIDDEN il2cpp_no_arg_class*> { static inline Il2CppClass* get() { Il2CppClass* genTemplate; bool isStruct = false; @@ -417,7 +421,7 @@ namespace il2cpp_utils { #define DEFINE_IL2CPP_ARG_TYPE_GENERIC_STRUCT(templateType, nameSpace, className) \ template<> \ - struct ::il2cpp_utils::il2cpp_type_check::il2cpp_gen_struct_no_arg_class { \ + struct BS_HOOKS_HIDDEN ::il2cpp_utils::il2cpp_type_check::il2cpp_gen_struct_no_arg_class { \ static inline Il2CppClass* get() { \ static auto klass = il2cpp_utils::GetClassFromName(nameSpace, className); \ return klass; \ @@ -426,19 +430,19 @@ namespace il2cpp_utils { #define DEFINE_IL2CPP_ARG_TYPE_GENERIC_CLASS(templateType, nameSpace, className) \ template<> \ - struct ::il2cpp_utils::il2cpp_type_check::il2cpp_gen_class_no_arg_class { \ + struct BS_HOOKS_HIDDEN ::il2cpp_utils::il2cpp_type_check::il2cpp_gen_class_no_arg_class { \ static inline Il2CppClass* get() { \ static auto klass = ::il2cpp_utils::GetClassFromName(nameSpace, className); \ return klass; \ } \ }; \ template<> \ - struct ::il2cpp_utils::il2cpp_type_check::need_box_gen { \ + struct BS_HOOKS_HIDDEN ::il2cpp_utils::il2cpp_type_check::need_box_gen { \ constexpr static bool value = false; \ } - template - struct il2cpp_no_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_no_arg_type { static inline const Il2CppType* get() { static auto klass = il2cpp_no_arg_class::get(); if (klass) { @@ -448,8 +452,8 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_no_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_no_arg_type { static inline const Il2CppType* get() { static auto klass = il2cpp_no_arg_class::get(); if (klass) { @@ -459,8 +463,8 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_no_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_no_arg_type { static inline const Il2CppType* get() { static auto klass = il2cpp_no_arg_class::get(); if (klass) { @@ -470,8 +474,8 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_no_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_no_arg_type { static inline const Il2CppType* get() { static auto klass = il2cpp_no_arg_class::get(); if (klass) { @@ -481,18 +485,18 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_no_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_no_arg_type { static inline const Il2CppType* get() { return il2cpp_no_arg_type::get(); } }; - template - struct il2cpp_arg_type { }; + template + struct BS_HOOKS_HIDDEN il2cpp_arg_type {}; - template - struct il2cpp_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_type { static inline const Il2CppType* get(T& arg) { // Pure reference type is not the same as ByRef. Thus, use the byval version. // Therefore, the only way to get the byref type match for any expression is to use a ByRef. @@ -501,8 +505,8 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_type { static inline const Il2CppType* get(T* arg) { // A pointer could be passed in explicitly. In such a case, get the class of the pointer and return it non-byref. Il2CppClass* klass = il2cpp_arg_class::get(arg); @@ -510,8 +514,8 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_type { static inline const Il2CppType* get(const T& arg) { // A method cannot store a result back to a const ref. It is not a C# ref. const Il2CppClass* klass = il2cpp_arg_class::get(arg); @@ -519,22 +523,22 @@ namespace il2cpp_utils { } }; - template - struct il2cpp_arg_type { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_type { static inline const Il2CppType* get(T&& arg) { Il2CppClass* klass = il2cpp_arg_class::get(arg); return &klass->byval_arg; } }; - template - struct il2cpp_arg_ptr { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_ptr { static inline void* get(T const& arg) { return reinterpret_cast(&arg); } }; - template - struct il2cpp_arg_ptr { + template + struct BS_HOOKS_HIDDEN il2cpp_arg_ptr { static inline void* get(T* arg) { return reinterpret_cast(arg); } @@ -542,19 +546,19 @@ namespace il2cpp_utils { /// @brief Represents a specialization type that should be used for exposing metadata from particular values. /// @tparam val The value to specialize this particular metadata getter on. - template - struct MetadataGetter; + template + struct BS_HOOKS_HIDDEN MetadataGetter; - template - struct MethodDecomposer; + template + struct BS_HOOKS_HIDDEN MethodDecomposer; - template - struct MethodDecomposer { + template + struct BS_HOOKS_HIDDEN MethodDecomposer { using mPtr = R (*)(TArgs...); }; - template - struct MethodDecomposer { + template + struct BS_HOOKS_HIDDEN MethodDecomposer { using mPtr = R (*)(T*, TArgs...); }; @@ -564,16 +568,18 @@ namespace il2cpp_utils { { il2cpp_utils::il2cpp_type_check::MetadataGetter::get() } -> std::same_as; }; - template - requires (is_valid_il2cpp_method) - struct FPtrWrapper { + template + requires(is_valid_il2cpp_method) + struct BS_HOOKS_HIDDEN FPtrWrapper { static auto get() { return reinterpret_cast::mPtr>(il2cpp_utils::il2cpp_type_check::MetadataGetter::get()->methodPointer); } }; } + } } + #pragma pack(pop) #endif /* #ifndef IL2CPP_TYPE_CHECK_H */ diff --git a/shared/utils/il2cpp-utils-classes.hpp b/shared/utils/il2cpp-utils-classes.hpp index 7aa4c6b8..c8d02204 100644 --- a/shared/utils/il2cpp-utils-classes.hpp +++ b/shared/utils/il2cpp-utils-classes.hpp @@ -5,7 +5,7 @@ #include "logging.hpp" #include "il2cpp-type-check.hpp" #include "il2cpp-functions.hpp" -#include "il2cpp-utils-methods.hpp" +#include "il2cpp-utils-exceptions.hpp" #include "base-wrapper-type.hpp" #include "type-concepts.hpp" #include diff --git a/shared/utils/il2cpp-utils.hpp b/shared/utils/il2cpp-utils.hpp index 6857071d..627c4f30 100644 --- a/shared/utils/il2cpp-utils.hpp +++ b/shared/utils/il2cpp-utils.hpp @@ -29,8 +29,8 @@ #include #include -template<> -struct std::hash> { +template <> +struct BS_HOOKS_HIDDEN std::hash> { size_t operator()(const std::pair& p) const { return std::hash{}(p.first) ^ std::hash{}(p.first); } diff --git a/shared/utils/manual-il2cpp-typedefs.h b/shared/utils/manual-il2cpp-typedefs.h index cddcf0af..ee52010e 100644 --- a/shared/utils/manual-il2cpp-typedefs.h +++ b/shared/utils/manual-il2cpp-typedefs.h @@ -52,6 +52,7 @@ namespace os typedef struct Il2CppReflectionAssembly Il2CppReflectionAssembly; #include "typedefs-array.hpp" +#include "typedefs-object.hpp" // System.String typedef struct Il2CppString diff --git a/shared/utils/size-concepts.hpp b/shared/utils/size-concepts.hpp index ba27df6a..de55c25b 100644 --- a/shared/utils/size-concepts.hpp +++ b/shared/utils/size-concepts.hpp @@ -3,6 +3,7 @@ #include "type-concepts.hpp" namespace il2cpp_utils { +namespace { /// @brief any builtins will just have the same size as in cpp template struct il2cpp_size { @@ -56,6 +57,7 @@ namespace il2cpp_utils { /// @brief shorthand to get the size check value template static constexpr bool is_il2cpp_size_safe_v = is_il2cpp_size_safe::value; + } // namespace il2cpp_utils } #define il2cpp_sizeof(...) (::il2cpp_utils::il2cpp_size<__VA_ARGS__>::value) diff --git a/shared/utils/type-concepts.hpp b/shared/utils/type-concepts.hpp index 54b0dec1..7144fc6b 100644 --- a/shared/utils/type-concepts.hpp +++ b/shared/utils/type-concepts.hpp @@ -4,7 +4,18 @@ #include #include +#ifndef BS_HOOKS_ALWAYS_INLINE +// always inline attribute +#define BS_HOOKS_ALWAYS_INLINE __attribute__((alwaysinline)) +#endif + +#ifndef BS_HOOKS_HIDDEN +// hidden attribute +#define BS_HOOKS_HIDDEN __attribute__((visibility("hidden"))) +#endif + namespace il2cpp_utils { +namespace { template concept convertible_to = std::is_convertible_v; @@ -44,7 +55,7 @@ namespace il2cpp_utils { concept il2cpp_value_type_requirements = requires(T const& t) { requires(std::is_same_v>); requires(std::is_constructible_v); - requires(il2cpp_utils::value_marker_check_v); + requires(::il2cpp_utils::value_marker_check_v); }; /// @brief mark a T explicitly as value type, default is false @@ -162,34 +173,35 @@ namespace il2cpp_utils { } } +} +} + #define MARK_REF_T(...) \ - template<> struct ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ - template<> struct ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } #define MARK_REF_PTR_T(...) \ - template<> struct ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = true; } + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = true; } #define MARK_VAL_T(...) \ - template<> struct ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ - template<> struct ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::RefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::ValueTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::RefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } #define MARK_GEN_REF_T(...) \ - template<> struct ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ - template<> struct ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } #define MARK_GEN_REF_PTR_T(...) \ - template<> struct ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = true; } + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = true; } #define MARK_GEN_VAL_T(...) \ - template<> struct ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ - template<> struct ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ - template<> struct ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } - -} + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenRefTypeTrait<__VA_ARGS__> { static constexpr bool value = false; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenValueTypeTrait<__VA_ARGS__> { static constexpr bool value = true; }; \ + template<> struct BS_HOOKS_HIDDEN ::il2cpp_utils::GenRefPtrTypeTrait<__VA_ARGS__> { static constexpr bool value = false; } diff --git a/shared/utils/typedefs-delegate.hpp b/shared/utils/typedefs-delegate.hpp index 469c4a84..cc174511 100644 --- a/shared/utils/typedefs-delegate.hpp +++ b/shared/utils/typedefs-delegate.hpp @@ -1,56 +1,16 @@ #pragma once +#include "manual-il2cpp-typedefs.h" #include "typedefs-object.hpp" -#ifdef HAS_CODEGEN - -#ifdef USE_CODEGEN_FIELDS -#define _HAD_CODEGEN_FIELDS -#else -#define USE_CODEGEN_FIELDS -#endif - -#include "System/Delegate.hpp" -#ifndef _HAD_CODEGEN_FIELDS -#undef USE_CODEGEN_FIELDS -#endif - -#undef _HAD_CODEGEN_FIELDS - -// self-typedef'd in il2cpp-class-internals.h -struct Il2CppDelegate : public System::Delegate {}; -#endif -// Already included in manual il2cpp typedefs - -#ifdef HAS_CODEGEN - -#ifdef USE_CODEGEN_FIELDS -#define _HAD_CODEGEN_FIELDS -#else -#define USE_CODEGEN_FIELDS -#endif - -#include "System/MulticastDelegate.hpp" -#ifndef _HAD_CODEGEN_FIELDS -#undef USE_CODEGEN_FIELDS -#endif - -#undef _HAD_CODEGEN_FIELDS - -typedef System::MulticastDelegate Il2CppMulticastDelegate; -typedef System::MulticastDelegate MulticastDelegate; -#else // System.MulticastDelegate typedef struct MulticastDelegate : Il2CppDelegate { ::Array* delegates; } MulticastDelegate; -#endif -#ifndef HAS_CODEGEN // System.DelegateData typedef struct DelegateData : Il2CppObject { Il2CppReflectionType* target_type; Il2CppString* method_name; bool curied_first_arg; } DelegateData; -#endif \ No newline at end of file diff --git a/shared/utils/typedefs-list.hpp b/shared/utils/typedefs-list.hpp index 327d9001..2c038b5c 100644 --- a/shared/utils/typedefs-list.hpp +++ b/shared/utils/typedefs-list.hpp @@ -6,6 +6,19 @@ #include "il2cpp-utils-methods.hpp" #include "type-concepts.hpp" +#include "typedefs-array.hpp" +#include "typedefs-object.hpp" + +// System.Collections.Generic.List +template +struct List : Il2CppObject { + Array* items; + int size; + int version; + Il2CppObject* syncRoot; +}; +DEFINE_IL2CPP_ARG_TYPE_GENERIC_CLASS(List, "System.Collections.Generic", "List`1"); + template*> struct ListW { static_assert(sizeof(Ptr) == sizeof(void*), "Size of Ptr type must be the same as a void*!"); @@ -142,7 +155,7 @@ MARK_GEN_REF_T(ListW); MARK_GEN_REF_PTR_T(List); static_assert(il2cpp_utils::has_il2cpp_conversion*>>); -template -struct ::il2cpp_utils::il2cpp_type_check::need_box> { +template +struct BS_HOOKS_HIDDEN ::il2cpp_utils::il2cpp_type_check::need_box> { constexpr static bool value = false; }; diff --git a/shared/utils/typedefs-object.hpp b/shared/utils/typedefs-object.hpp index f184ffe1..c4b9b20e 100644 --- a/shared/utils/typedefs-object.hpp +++ b/shared/utils/typedefs-object.hpp @@ -1,6 +1,7 @@ #pragma once #include "type-concepts.hpp" +#include "il2cpp-config.h" typedef Il2CppClass Il2CppVTable; struct MonitorData; diff --git a/shared/utils/typedefs.h b/shared/utils/typedefs.h index 564c304a..ed39bace 100644 --- a/shared/utils/typedefs.h +++ b/shared/utils/typedefs.h @@ -35,15 +35,8 @@ extern "C" { } /* extern "C" */ #endif /* __cplusplus */ -#if __has_include("System/Array.hpp") && !defined(NO_CODEGEN_USE) -#define HAS_CODEGEN -#include -#include -#include "il2cpp-windowsruntime-types.h" -#else -// TODO: find a way to include this without putting the types in the global namespace? #include "manual-il2cpp-typedefs.h" -#endif + #include "il2cpp-functions.hpp" #include "il2cpp-utils-methods.hpp" @@ -51,8 +44,10 @@ extern "C" { #include "typedefs-array.hpp" #include "typedefs-delegate.hpp" +#include "typedefs-list.hpp" #include "typedefs-wrappers.hpp" + #include namespace il2cpp_utils { @@ -89,443 +84,9 @@ namespace il2cpp_utils { } } -#ifdef HAS_CODEGEN - -#ifdef USE_CODEGEN_FIELDS -#define _HAD_CODEGEN_FIELDS -#else -#define USE_CODEGEN_FIELDS -#endif - -#include "System/String.hpp" -struct Il2CppString : public System::String {}; -#endif - -#ifdef HAS_CODEGEN -#include "System/Collections/Generic/List_1.hpp" -template -using List = System::Collections::Generic::List_1; -#else -// System.Collections.Generic.List -template -struct List : Il2CppObject -{ - Array* items; - int size; - int version; - Il2CppObject* syncRoot; -}; -DEFINE_IL2CPP_ARG_TYPE_GENERIC_CLASS(List, "System.Collections.Generic", "List`1"); -#endif -#include "typedefs-list.hpp" - -#ifdef HAS_CODEGEN -// TODO: QiCache and Il2CppComObject ("System.__Il2CppComObject (dummy type that replaces System.__ComObject)") - -#if __has_include("System/zzzz__AppDomain_def.hpp") -#include "System/zzzz__AppDomain_def.hpp" -// self-typedef'd in il2cpp-class-internals.h -struct Il2CppAppDomain : public System::AppDomain {}; -NEED_NO_BOX(Il2CppAppDomain); -#endif - -#if __has_include("System/zzzz__AppDomainSetup_def.hpp") -#include "System/zzzz__AppDomainSetup_def.hpp" -// self-typedef'd in il2cpp-class-internals.h -struct Il2CppAppDomainSetup : public System::AppDomainSetup {}; -NEED_NO_BOX(Il2CppAppDomainSetup); -#endif - -#if __has_include("System/zzzz__ArgumentException_def.hpp") -#include "System/zzzz__ArgumentException_def.hpp" -typedef System::ArgumentException Il2CppArgumentException; -#endif -// TODO: Il2CppDecimal is System::Decimal? - -typedef enum Il2CppDecimalCompareResult -{ - IL2CPP_DECIMAL_CMP_LT = -1, - IL2CPP_DECIMAL_CMP_EQ, - IL2CPP_DECIMAL_CMP_GT -} Il2CppDecimalCompareResult; - -// TODO: Il2CppDouble, Il2CppDouble_double are System::Double? - -#if __has_include("System/zzzz__Exception_def.hpp") -#include "System/zzzz__Exception_def.hpp" -// self-typedef'd in il2cpp-api-types.h -struct Il2CppException : public System::Exception {}; -#endif - -#if __has_include("System/zzzz__IOAsyncResult_def.hpp") -#include "System/zzzz__IOAsyncResult_def.hpp" -typedef System::IOAsyncResult Il2CppIOAsyncResult; -#endif - -#if __has_include("System/zzzz__IOSelectorJob_def.hpp") -#include "System/IOSelectorJob.hpp" -typedef System::IOSelectorJob Il2CppIOSelectorJob; -#endif - -#if __has_include("System/zzzz__MarshalByRefObject_def.hpp") -#include "System/zzzz__MarshalByRefObject_def.hpp" -typedef System::MarshalByRefObject Il2CppMarshalByRefObject; -#endif - -#if __has_include("System/zzzz__MonoAsyncCall_def.hpp") -#include "System/zzzz__MonoAsyncCall_def.hpp" -typedef System::MonoAsyncCall Il2CppAsyncCall; -#endif - -#if __has_include("System/zzzz__MonoType_def.hpp") -#include "System/zzzz__MonoType_def.hpp" -struct Il2CppReflectionMonoType : public System::MonoType { - const Il2CppType* GetIl2CppType() const; -}; -#endif - -#if __has_include("System/zzzz__RuntimeType_def.hpp") -#include "System/zzzz__RuntimeType_def.hpp" -struct Il2CppReflectionRuntimeType : public System::RuntimeType {}; -#endif - -// TODO: Il2CppSingle, Il2CppSingle_float are System::Single? - -#if __has_include("System/zzzz__SystemException_def.hpp") -#include "System/zzzz__SystemException_def.hpp" -typedef System::SystemException Il2CppSystemException; -#endif - -#if __has_include("System/zzzz__Type_def.hpp") -#include "System/zzzz__Type_def.hpp" -// self-typedef'd in il2cpp-api-types.h -struct Il2CppReflectionType : public System::Type {}; -#endif -#if __has_include("System/zzzz__TypedReference_def.hpp") -#include "System/zzzz__TypedReference_def.hpp" -typedef System::TypedReference Il2CppTypedRef; -#endif -#if __has_include("System/Diagnostics/zzzz__StackFrame_def.hpp") -#include "System/Diagnostics/zzzz__StackFrame_def.hpp" -typedef System::Diagnostics::StackFrame Il2CppStackFrame; -#endif - -// TODO: Il2CppCalendarData is System::Globalization::CalendarData minus 4 fields at the end? - -// TODO: Il2CppCultureData is System::Globalization::CultureData minus 13 fields at the end? - -#if __has_include("System/Globalization/zzzz__CultureInfo_def.hpp") -#include "System/Globalization/zzzz__CultureInfo_def.hpp" -typedef System::Globalization::CultureInfo Il2CppCultureInfo; -#endif - -#if __has_include("System/Globalization/zzzz__DateTimeFormatInfo_def.hpp") -#include "System/Globalization/zzzz__DateTimeFormatInfo_def.hpp" -typedef System::Globalization::DateTimeFormatInfo Il2CppDateTimeFormatInfo; -#endif - -#if __has_include("System/Globalization/zzzz__NumberFormatInfo_def.hpp") -#include "System/Globalization/zzzz__NumberFormatInfo_def.hpp" -typedef System::Globalization::NumberFormatInfo Il2CppNumberFormatInfo; -#endif - -#if __has_include("System/Globalization/zzzz__RegionInfo_def.hpp") -#include "System/Globalization/zzzz__RegionInfo_def.hpp" -typedef System::Globalization::RegionInfo Il2CppRegionInfo; -#endif - -#if __has_include("System/Globalization/zzzz__SortKey_def.hpp") -#include "System/Globalization/zzzz__SortKey_def.hpp" -typedef System::Globalization::SortKey Il2CppSortKey; -#endif - -#if __has_include("System/Net/zzzz__SocketAddress_def.hpp") -#include "System/Net/zzzz__SocketAddress_def.hpp" -typedef System::Net::SocketAddress Il2CppSocketAddress; -#endif - -// "Corresponds to Mono's internal System.Net.Sockets.Socket.SocketAsyncResult class. Has no relation to Il2CppAsyncResult." -#if __has_include("System/Net/Sockets/zzzz__SocketAsyncResult_def.hpp") -#include "System/Net/Sockets/zzzz__SocketAsyncResult_def.hpp" -typedef System::Net::Sockets::SocketAsyncResult Il2CppSocketAsyncResult; -#endif - -#if __has_include("System/Reflection/zzzz__EventInfo_def.hpp") -#include "System/Reflection/zzzz__EventInfo_def.hpp" -typedef System::Reflection::EventInfo Il2CppReflectionEvent; -#endif - -#if __has_include("System/Reflection/zzzz__MonoEvent_def.hpp") -#include "System/Reflection/zzzz__MonoEvent_def.hpp" -typedef System::Reflection::MonoEvent Il2CppReflectionMonoEvent; -#endif - -#if __has_include("System/Reflection/zzzz__MonoEventInfo_def.hpp") -#include "System/Reflection/zzzz__MonoEventInfo_def.hpp" -typedef System::Reflection::MonoEventInfo Il2CppReflectionMonoEventInfo; -#endif - -#if __has_include("System/Reflection/zzzz__MonoField_def.hpp") -#include "System/Reflection/zzzz__MonoField_def.hpp" -typedef System::Reflection::MonoField Il2CppReflectionField; -#endif - -#if __has_include("System/Reflection/zzzz__MonoProperty_def.hpp") -#include "System/Reflection/zzzz__MonoProperty_def.hpp" -typedef System::Reflection::MonoProperty Il2CppReflectionProperty; -#endif - -#if __has_include("System/Reflection/zzzz__MonoMethod_def.hpp") -#include "System/Reflection/zzzz__MonoMethod_def.hpp" -// self-typedef'd in il2cpp-api-types.h -struct Il2CppReflectionMethod : public System::Reflection::MonoMethod {}; -#endif - -#if __has_include("System/Reflection/zzzz__MonoGenericMethod_def.hpp") -#include "System/Reflection/zzzz__MonoGenericMethod_def.hpp" -typedef System::Reflection::MonoGenericMethod Il2CppReflectionGenericMethod; -#endif - -#if __has_include("System/Reflection/zzzz__MonoMethodInfo_def.hpp") -#include "System/Reflection/zzzz__MonoMethodInfo_def.hpp" -typedef System::Reflection::MonoMethodInfo Il2CppMethodInfo; -#endif - -#if __has_include("System/Reflection/zzzz__MonoPropertyInfo_def.hpp") -#include "System/Reflection/zzzz__MonoPropertyInfo_def.hpp" -typedef System::Reflection::MonoPropertyInfo Il2CppPropertyInfo; -#endif -#if __has_include("System/Reflection/zzzz__ParameterInfo_def.hpp") -#include "System/Reflection/zzzz__ParameterInfo_def.hpp" -typedef System::Reflection::ParameterInfo Il2CppReflectionParameter; -#endif - -#if __has_include("System/Reflection/zzzz__Module_def.hpp") -#include "System/Reflection/zzzz__Module_def.hpp" -typedef System::Reflection::Module Il2CppReflectionModule; -#endif - -#if __has_include("System/Reflection/zzzz__AssemblyName_def.hpp") -#include "System/Reflection/zzzz__AssemblyName_def.hpp" -typedef System::Reflection::AssemblyName Il2CppReflectionAssemblyName; -#endif - -#if __has_include("System/Reflection/zzzz__Assembly_def.hpp") -#include "System/Reflection/zzzz__Assembly_def.hpp" -typedef System::Reflection::Assembly Il2CppReflectionAssembly; -#endif - -#if __has_include("System/Reflection/Emit/zzzz__UnmanagedMarshal_def.hpp") -#include "System/Reflection/Emit/zzzz__UnmanagedMarshal_def.hpp" -typedef System::Reflection::Emit::UnmanagedMarshal Il2CppReflectionMarshal; -#endif - -/* Stripped in 1.13.5 Update -#include "System/Reflection/zzzz__ManifestResourceInfo_def.hpp" -typedef System::Reflection::zzzz__ManifestResourceInfo_def Il2CppManifestResourceInfo; -*/ - -#if __has_include("System/Reflection/zzzz__Pointer_def.hpp") -#include "System/Reflection/zzzz__Pointer_def.hpp" -typedef System::Reflection::Pointer Il2CppReflectionPointer; -#endif - -// TODO: Il2CppResourceLocation seems to be the System.Reflection.ResourceLocation enum - -#if __has_include("System/Runtime/InteropServices/zzzz__ErrorWrapper_def.hpp") -#include "System/Runtime/InteropServices/zzzz__ErrorWrapper_def.hpp" -typedef System::Runtime::InteropServices::ErrorWrapper Il2CppErrorWrapper; -#endif - -// "Inherited by Microsoft.Win32.SafeHandles.SafeWaitHandle" -#if __has_include("System/Runtime/InteropServices/zzzz__SafeHandle_def.hpp") -#include "System/Runtime/InteropServices/zzzz__SafeHandle_def.hpp" -typedef System::Runtime::InteropServices::SafeHandle Il2CppSafeHandle; -#endif - -#if __has_include("System/Runtime/Remoting/Contexts/zzzz__Context_def.hpp") -#include "System/Runtime/Remoting/Contexts/zzzz__Context_def.hpp" -// self-typedef'd in il2cpp-class-internals.h -struct Il2CppAppContext : public System::Runtime::Remoting::Contexts::Context {}; -NEED_NO_BOX(Il2CppAppContext); -#endif - -#if __has_include("System/Runtime/Remoting/Messaging/zzzz__AsyncResult_def.hpp") -#include "System/Runtime/Remoting/Messaging/zzzz__AsyncResult_def.hpp" -// self-typedef'd in il2cpp-api-types.h -struct Il2CppAsyncResult : public System::Runtime::Remoting::Messaging::AsyncResult {}; -NEED_NO_BOX(Il2CppAsyncResult); -#endif - -// TODO: Il2CppCallType which "is a copy of System.Runtime.Remoting.Messaging.CallType" enum - -// TODO: Il2CppMethodMessage is System::Runtime::Remoting::Messaging::MonoMethodMessage minus 4 fields at the end? - -#if __has_include("System/Text/zzzz__StringBuilder_def.hpp") -#include "System/Text/zzzz__StringBuilder_def.hpp" -typedef System::Text::StringBuilder Il2CppStringBuilder; -#endif - -#if __has_include("System/Threading/zzzz__InternalThread_def.hpp") -#include "System/Threading/zzzz__InternalThread_def.hpp" -typedef System::Threading::InternalThread Il2CppInternalThread; -#endif - -#if __has_include("System/Threading/zzzz__Thread_def.hpp") -#include "System/Threading/zzzz__Thread_def.hpp" -// self-typedef'd in il2cpp-api-types.h -struct Il2CppThread : public System::Threading::Thread {}; -#endif - -#ifndef _HAD_CODEGEN_FIELDS -#undef USE_CODEGEN_FIELDS -#endif - -#undef _HAD_CODEGEN_FIELDS - -// include impls -#if __has_include("System/zzzz__AppDomain_impl.hpp") -#include "System/zzzz__AppDomain_impl.hpp" -#endif -#if __has_include("System/zzzz__AppDomainSetup_impl.hpp") -#include "System/zzzz__AppDomainSetup_impl.hpp" -#endif -#if __has_include("System/zzzz__ArgumentException_impl.hpp") -#include "System/zzzz__ArgumentException_impl.hpp" -#endif -#if __has_include("System/zzzz__Exception_impl.hpp") -#include "System/zzzz__Exception_impl.hpp" -#endif -#if __has_include("System/zzzz__IOAsyncResult_impl.hpp") -#include "System/zzzz__IOAsyncResult_impl.hpp" -#endif -#if __has_include("System/IOSelectorJob.hpp") -#include "System/IOSelectorJob.hpp" -#endif -#if __has_include("System/zzzz__MarshalByRefObject_impl.hpp") -#include "System/zzzz__MarshalByRefObject_impl.hpp" -#endif -#if __has_include("System/zzzz__MonoAsyncCall_impl.hpp") -#include "System/zzzz__MonoAsyncCall_impl.hpp" -#endif -#if __has_include("System/zzzz__MonoType_impl.hpp") -#include "System/zzzz__MonoType_impl.hpp" -// include required for this implementation -#include "System/zzzz__RuntimeTypeHandle_impl.hpp" -inline const Il2CppType* Il2CppReflectionMonoType::GetIl2CppType() const { - return reinterpret_cast(_impl.value); -} -#endif -#if __has_include("System/zzzz__RuntimeType_impl.hpp") -#include "System/zzzz__RuntimeType_impl.hpp" -#endif -#if __has_include("System/zzzz__SystemException_impl.hpp") -#include "System/zzzz__SystemException_impl.hpp" -#endif -#if __has_include("System/zzzz__Type_impl.hpp") -#include "System/zzzz__Type_impl.hpp" -#endif -#if __has_include("System/zzzz__TypedReference_impl.hpp") -#include "System/zzzz__TypedReference_impl.hpp" -#endif -#if __has_include("System/Diagnostics/zzzz__StackFrame_impl.hpp") -#include "System/Diagnostics/zzzz__StackFrame_impl.hpp" -#endif -#if __has_include("System/Globalization/zzzz__CultureInfo_impl.hpp") -#include "System/Globalization/zzzz__CultureInfo_impl.hpp" -#endif -#if __has_include("System/Globalization/zzzz__DateTimeFormatInfo_impl.hpp") -#include "System/Globalization/zzzz__DateTimeFormatInfo_impl.hpp" -#endif -#if __has_include("System/Globalization/zzzz__NumberFormatInfo_impl.hpp") -#include "System/Globalization/zzzz__NumberFormatInfo_impl.hpp" -#endif -#if __has_include("System/Globalization/zzzz__RegionInfo_impl.hpp") -#include "System/Globalization/zzzz__RegionInfo_impl.hpp" -#endif -#if __has_include("System/Globalization/zzzz__SortKey_impl.hpp") -#include "System/Globalization/zzzz__SortKey_impl.hpp" -#endif -#if __has_include("System/Net/zzzz__SocketAddress_impl.hpp") -#include "System/Net/zzzz__SocketAddress_impl.hpp" -#endif -#if __has_include("System/Net/Sockets/zzzz__SocketAsyncResult_impl.hpp") -#include "System/Net/Sockets/zzzz__SocketAsyncResult_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__EventInfo_impl.hpp") -#include "System/Reflection/zzzz__EventInfo_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoEvent_impl.hpp") -#include "System/Reflection/zzzz__MonoEvent_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoEventInfo_impl.hpp") -#include "System/Reflection/zzzz__MonoEventInfo_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoField_impl.hpp") -#include "System/Reflection/zzzz__MonoField_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoProperty_impl.hpp") -#include "System/Reflection/zzzz__MonoProperty_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoMethod_impl.hpp") -#include "System/Reflection/zzzz__MonoMethod_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoGenericMethod_impl.hpp") -#include "System/Reflection/zzzz__MonoGenericMethod_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoMethodInfo_impl.hpp") -#include "System/Reflection/zzzz__MonoMethodInfo_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__MonoPropertyInfo_impl.hpp") -#include "System/Reflection/zzzz__MonoPropertyInfo_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__ParameterInfo_impl.hpp") -#include "System/Reflection/zzzz__ParameterInfo_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__Module_impl.hpp") -#include "System/Reflection/zzzz__Module_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__AssemblyName_impl.hpp") -#include "System/Reflection/zzzz__AssemblyName_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__Assembly_impl.hpp") -#include "System/Reflection/zzzz__Assembly_impl.hpp" -#endif -#if __has_include("System/Reflection/Emit/zzzz__UnmanagedMarshal_impl.hpp") -#include "System/Reflection/Emit/zzzz__UnmanagedMarshal_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__ManifestResourceInfo_impl.hpp") -#include "System/Reflection/zzzz__ManifestResourceInfo_impl.hpp" -#endif -#if __has_include("System/Reflection/zzzz__Pointer_impl.hpp") -#include "System/Reflection/zzzz__Pointer_impl.hpp" -#endif -#if __has_include("System/Runtime/InteropServices/zzzz__ErrorWrapper_impl.hpp") -#include "System/Runtime/InteropServices/zzzz__ErrorWrapper_impl.hpp" -#endif -#if __has_include("System/Runtime/InteropServices/zzzz__SafeHandle_impl.hpp") -#include "System/Runtime/InteropServices/zzzz__SafeHandle_impl.hpp" -#endif -#if __has_include("System/Runtime/Remoting/Contexts/zzzz__Context_impl.hpp") -#include "System/Runtime/Remoting/Contexts/zzzz__Context_impl.hpp" -#endif -#if __has_include("System/Runtime/Remoting/Messaging/zzzz__AsyncResult_impl.hpp") -#include "System/Runtime/Remoting/Messaging/zzzz__AsyncResult_impl.hpp" -#endif -#if __has_include("System/Text/zzzz__StringBuilder_impl.hpp") -#include "System/Text/zzzz__StringBuilder_impl.hpp" -#endif -#if __has_include("System/Threading/zzzz__InternalThread_impl.hpp") -#include "System/Threading/zzzz__InternalThread_impl.hpp" -#endif -#if __has_include("System/Threading/zzzz__Thread_impl.hpp") -#include "System/Threading/zzzz__Thread_impl.hpp" -#endif - -#else // From Runtime.cpp (some may need the * removed): DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppMulticastDelegate*, multicastdelegate); NEED_NO_BOX(Il2CppMulticastDelegate); @@ -573,7 +134,7 @@ NEED_NO_BOX(Il2CppErrorWrapper); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppComObject*, il2cpp_com_object); NEED_NO_BOX(Il2CppComObject); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppTypedRef, typed_reference); -#endif + DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppDelegate*, delegate); NEED_NO_BOX(Il2CppDelegate); DEFINE_IL2CPP_DEFAULT_TYPE(Il2CppReflectionMonoType*, monotype); diff --git a/shared/utils/utils.h b/shared/utils/utils.h index 3cb289c7..e35fb10e 100644 --- a/shared/utils/utils.h +++ b/shared/utils/utils.h @@ -35,8 +35,10 @@ namespace std { #include #endif -template struct is_vector : std::false_type { }; -template struct is_vector > : std::true_type { }; +#include "type-concepts.hpp" + +template struct BS_HOOKS_HIDDEN is_vector : std::false_type { }; +template struct BS_HOOKS_HIDDEN is_vector > : std::true_type { }; // TODO: figure out how to write an is_vector_v that compiles properly? #define MACRO_WRAP(...) do { \ @@ -54,7 +56,7 @@ struct is_instance, U> : public std::true_type {}; // from https://gcc.gnu.org/bugzilla//show_bug.cgi?id=71579#c4, leading underscores removed namespace std { template - struct is_complete_impl + struct BS_HOOKS_HIDDEN is_complete_impl { template static true_type _S_test(int); @@ -76,7 +78,7 @@ namespace std { struct Il2CppObject; template -struct is_value_type : std::integral_constant< +struct BS_HOOKS_HIDDEN is_value_type : std::integral_constant< bool, (std::is_arithmetic_v || std::is_enum_v || std::is_pointer_v || std::is_standard_layout_v) && !std::is_base_of_v > {}; @@ -202,10 +204,10 @@ template constexpr std::false_type false_t{}; #ifdef __cplusplus template -struct identity {}; +struct BS_HOOKS_HIDDEN identity {}; template -struct identity { +struct BS_HOOKS_HIDDEN identity { using type = std::function; }; From 1a6bdd90642201143bc2b9ed744c40c35bad8335 Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Fri, 22 Dec 2023 19:26:17 -0400 Subject: [PATCH 29/89] Move inline hook to shared --- shared/inline-hook/And64InlineHook.cpp | 567 +++++++++++++++++++++++ shared/inline-hook/inlineHook.c | 427 +++++++++++++++++ shared/inline-hook/relocate.c | 617 +++++++++++++++++++++++++ 3 files changed, 1611 insertions(+) create mode 100644 shared/inline-hook/And64InlineHook.cpp create mode 100644 shared/inline-hook/inlineHook.c create mode 100644 shared/inline-hook/relocate.c diff --git a/shared/inline-hook/And64InlineHook.cpp b/shared/inline-hook/And64InlineHook.cpp new file mode 100644 index 00000000..c7f03bf6 --- /dev/null +++ b/shared/inline-hook/And64InlineHook.cpp @@ -0,0 +1,567 @@ +/* + * @date : 2018/04/18 + * @author : Rprop (r_prop@outlook.com) + * https://github.com/Rprop/And64InlineHook + */ +/* + MIT License + + Copyright (c) 2018 Rprop (r_prop@outlook.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include +#include +#include +#ifdef __aarch64__ + +#include "../../shared/inline-hook/And64InlineHook.hpp" +#define A64_MAX_INSTRUCTIONS 5 +#define A64_MAX_REFERENCES (A64_MAX_INSTRUCTIONS * 2) +#define A64_NOP 0xd503201fu +#define A64_JNIEXPORT __attribute__((visibility("default"))) +#define A64_LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "A64_HOOK", __VA_ARGS__)) +#ifndef NDEBUG +# define A64_LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "A64_HOOK", __VA_ARGS__)) +#else +# define A64_LOGI(...) ((void)0) +#endif // NDEBUG +typedef uint32_t *__restrict *__restrict instruction; +struct context +{ + struct fix_info + { + uint32_t *bp; + uint32_t ls; // left-shift counts + uint32_t ad; // & operand + }; + struct insns_info + { + union + { + uint64_t insu; + int64_t ins; + void *insp; + }; + fix_info fmap[A64_MAX_REFERENCES]; + }; + int64_t basep; + int64_t endp; + insns_info dat[A64_MAX_INSTRUCTIONS]; + + inline bool is_in_fixing_range(const int64_t absolute_addr) { + return absolute_addr >= this->basep && absolute_addr < this->endp; + } + inline intptr_t get_ref_ins_index(const int64_t absolute_addr) { + return static_cast((absolute_addr - this->basep) / sizeof(uint32_t)); + } + inline intptr_t get_and_set_current_index(uint32_t *__restrict inp, uint32_t *__restrict outp) { + intptr_t current_idx = this->get_ref_ins_index(reinterpret_cast(inp)); + this->dat[current_idx].insp = outp; + return current_idx; + } + inline void reset_current_ins(const intptr_t idx, uint32_t *__restrict outp) { + this->dat[idx].insp = outp; + } + void insert_fix_map(const intptr_t idx, uint32_t *bp, uint32_t ls = 0u, uint32_t ad = 0xffffffffu) { + for (auto &f : this->dat[idx].fmap) { + if (f.bp == NULL) { + f.bp = bp; + f.ls = ls; + f.ad = ad; + return; + } //if + } + // What? GGing.. + } + void process_fix_map(const intptr_t idx) { + for (auto &f : this->dat[idx].fmap) { + if (f.bp == NULL) break; + *(f.bp) = *(f.bp) | (((int32_t(this->dat[idx].ins - reinterpret_cast(f.bp)) >> 2) << f.ls) & f.ad); + f.bp = NULL; + } + } +}; + +//------------------------------------------------------------------------- + +static bool __fix_branch_imm(instruction inpp, instruction outpp, context *ctxp) +{ + static constexpr uint32_t mbits = 6u; + static constexpr uint32_t mask = 0xfc000000u; // 0b11111100000000000000000000000000 + static constexpr uint32_t rmask = 0x03ffffffu; // 0b00000011111111111111111111111111 + static constexpr uint32_t op_b = 0x14000000u; // "b" ADDR_PCREL26 + static constexpr uint32_t op_bl = 0x94000000u; // "bl" ADDR_PCREL26 + + const uint32_t ins = *(*inpp); + const uint32_t opc = ins & mask; + switch (opc) { + case op_b: + case op_bl: + { + intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t absolute_addr = reinterpret_cast(*inpp) + (static_cast(ins << mbits) >> (mbits - 2u)); // sign-extended + int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + // whether the branch should be converted to absolute jump + if (!special_fix_type && llabs(new_pc_offset) >= (rmask >> 1)) { + bool b_aligned = (reinterpret_cast(*outpp + 2) & 7u) == 0u; + if (opc == op_b) { + if (b_aligned != true) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + (*outpp)[0] = 0x58000051u; // LDR X17, #0x8 + (*outpp)[1] = 0xd61f0220u; // BR X17 + memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); + *outpp += 4; + } else { + if (b_aligned == true) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + (*outpp)[0] = 0x58000071u; // LDR X17, #12 + (*outpp)[1] = 0x1000009eu; // ADR X30, #16 + (*outpp)[2] = 0xd61f0220u; // BR X17 + memcpy(*outpp + 3, &absolute_addr, sizeof(absolute_addr)); + *outpp += 5; + } //if + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); + if (ref_idx <= current_idx) { + new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outpp)) >> 2; + } else { + ctxp->insert_fix_map(ref_idx, *outpp, 0u, rmask); + new_pc_offset = 0; + } //if + } //if + + (*outpp)[0] = opc | (new_pc_offset & ~mask); + ++(*outpp); + } //if + + ++(*inpp); + return ctxp->process_fix_map(current_idx), true; + } + } + return false; +} + +//------------------------------------------------------------------------- + +static bool __fix_cond_comp_test_branch(instruction inpp, instruction outpp, context *ctxp) +{ + static constexpr uint32_t lsb = 5u; + static constexpr uint32_t lmask01 = 0xff00001fu; // 0b11111111000000000000000000011111 + static constexpr uint32_t mask0 = 0xff000010u; // 0b11111111000000000000000000010000 + static constexpr uint32_t op_bc = 0x54000000u; // "b.c" ADDR_PCREL19 + static constexpr uint32_t mask1 = 0x7f000000u; // 0b01111111000000000000000000000000 + static constexpr uint32_t op_cbz = 0x34000000u; // "cbz" Rt, ADDR_PCREL19 + static constexpr uint32_t op_cbnz = 0x35000000u; // "cbnz" Rt, ADDR_PCREL19 + static constexpr uint32_t lmask2 = 0xfff8001fu; // 0b11111111111110000000000000011111 + static constexpr uint32_t mask2 = 0x7f000000u; // 0b01111111000000000000000000000000 + static constexpr uint32_t op_tbz = 0x36000000u; // 0b00110110000000000000000000000000 "tbz" Rt, BIT_NUM, ADDR_PCREL14 + static constexpr uint32_t op_tbnz = 0x37000000u; // 0b00110111000000000000000000000000 "tbnz" Rt, BIT_NUM, ADDR_PCREL14 + + const uint32_t ins = *(*inpp); + uint32_t lmask = lmask01; + if ((ins & mask0) != op_bc) { + uint32_t opc = ins & mask1; + if (opc != op_cbz && opc != op_cbnz) { + opc = ins & mask2; + if (opc != op_tbz && opc != op_tbnz) { + return false; + } //if + lmask = lmask2; + } //if + } //if + + intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t absolute_addr = reinterpret_cast(*inpp) + ((ins & ~lmask) >> (lsb - 2u)); + int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + if (!special_fix_type && llabs(new_pc_offset) >= (~lmask >> (lsb + 1))) { + if ((reinterpret_cast(*outpp + 4) & 7u) != 0u) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + (*outpp)[0] = (((8u >> 2u) << lsb) & ~lmask) | (ins & lmask); // B.C #0x8 + (*outpp)[1] = 0x14000005u; // B #0x14 + (*outpp)[2] = 0x58000051u; // LDR X17, #0x8 + (*outpp)[3] = 0xd61f0220u; // BR X17 + memcpy(*outpp + 4, &absolute_addr, sizeof(absolute_addr)); + *outpp += 6; + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); + if (ref_idx <= current_idx) { + new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outpp)) >> 2; + } else { + ctxp->insert_fix_map(ref_idx, *outpp, lsb, ~lmask); + new_pc_offset = 0; + } //if + } //if + + (*outpp)[0] = (static_cast(new_pc_offset << lsb) & ~lmask) | (ins & lmask); + ++(*outpp); + } //if + + ++(*inpp); + return ctxp->process_fix_map(current_idx), true; +} + +//------------------------------------------------------------------------- + +static bool __fix_loadlit(instruction inpp, instruction outpp, context *ctxp) +{ + const uint32_t ins = *(*inpp); + + // memory prefetch("prfm"), just skip it + // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897420050.html + if ((ins & 0xff000000u) == 0xd8000000u) { + ctxp->process_fix_map(ctxp->get_and_set_current_index(*inpp, *outpp)); + ++(*inpp); + return true; + } //if + + static constexpr uint32_t msb = 8u; + static constexpr uint32_t lsb = 5u; + static constexpr uint32_t mask_30 = 0x40000000u; // 0b01000000000000000000000000000000 + static constexpr uint32_t mask_31 = 0x80000000u; // 0b10000000000000000000000000000000 + static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 + static constexpr uint32_t mask_ldr = 0xbf000000u; // 0b10111111000000000000000000000000 + static constexpr uint32_t op_ldr = 0x18000000u; // 0b00011000000000000000000000000000 "LDR Wt/Xt, label" | ADDR_PCREL19 + static constexpr uint32_t mask_ldrv = 0x3f000000u; // 0b00111111000000000000000000000000 + static constexpr uint32_t op_ldrv = 0x1c000000u; // 0b00011100000000000000000000000000 "LDR St/Dt/Qt, label" | ADDR_PCREL19 + static constexpr uint32_t mask_ldrsw = 0xff000000u; // 0b11111111000000000000000000000000 + static constexpr uint32_t op_ldrsw = 0x98000000u; // "LDRSW Xt, label" | ADDR_PCREL19 | load register signed word + // LDR S0, #0 | 0b00011100000000000000000000000000 | 32-bit + // LDR D0, #0 | 0b01011100000000000000000000000000 | 64-bit + // LDR Q0, #0 | 0b10011100000000000000000000000000 | 128-bit + // INVALID | 0b11011100000000000000000000000000 | may be 256-bit + + uint32_t mask = mask_ldr; + uintptr_t faligned = (ins & mask_30) ? 7u : 3u; + if ((ins & mask_ldr) != op_ldr) { + mask = mask_ldrv; + if (faligned != 7u) + faligned = (ins & mask_31) ? 15u : 3u; + if ((ins & mask_ldrv) != op_ldrv) { + if ((ins & mask_ldrsw) != op_ldrsw) { + return false; + } //if + mask = mask_ldrsw; + faligned = 7u; + } //if + } //if + + intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t absolute_addr = reinterpret_cast(*inpp) + ((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u); + int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + // special_fix_type may encounter issue when there are mixed data and code + if (special_fix_type || (llabs(new_pc_offset) + (faligned + 1u - 4u) / 4u) >= (~lmask >> (lsb + 1))) { // inaccurate, but it works + while ((reinterpret_cast(*outpp + 2) & faligned) != 0u) { + *(*outpp)++ = A64_NOP; + } + ctxp->reset_current_ins(current_idx, *outpp); + + // Note that if memory at absolute_addr is writeable (non-const), we will fail to fetch it. + // And what's worse, we may unexpectedly overwrite something if special_fix_type is true... + uint32_t ns = static_cast((faligned + 1) / sizeof(uint32_t)); + (*outpp)[0] = (((8u >> 2u) << lsb) & ~mask) | (ins & lmask); // LDR #0x8 + (*outpp)[1] = 0x14000001u + ns; // B #0xc + memcpy(*outpp + 2, reinterpret_cast(absolute_addr), faligned + 1); + *outpp += 2 + ns; + } else { + faligned >>= 2; // new_pc_offset is shifted and 4-byte aligned + while ((new_pc_offset & faligned) != 0) { + *(*outpp)++ = A64_NOP; + new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; + } + ctxp->reset_current_ins(current_idx, *outpp); + + (*outpp)[0] = (static_cast(new_pc_offset << lsb) & ~mask) | (ins & lmask); + ++(*outpp); + } //if + + ++(*inpp); + return ctxp->process_fix_map(current_idx), true; +} + +//------------------------------------------------------------------------- + +static bool __fix_pcreladdr(instruction inpp, instruction outpp, context *ctxp) +{ + // Load a PC-relative address into a register + // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897645644.html + static constexpr uint32_t msb = 8u; + static constexpr uint32_t lsb = 5u; + static constexpr uint32_t mask = 0x9f000000u; // 0b10011111000000000000000000000000 + static constexpr uint32_t rmask = 0x0000001fu; // 0b00000000000000000000000000011111 + static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 + static constexpr uint32_t fmask = 0x00ffffffu; // 0b00000000111111111111111111111111 + static constexpr uint32_t max_val = 0x001fffffu; // 0b00000000000111111111111111111111 + static constexpr uint32_t op_adr = 0x10000000u; // "adr" Rd, ADDR_PCREL21 + static constexpr uint32_t op_adrp = 0x90000000u; // "adrp" Rd, ADDR_ADRP + + const uint32_t ins = *(*inpp); + intptr_t current_idx; + switch (ins & mask) { + case op_adr: + { + current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t lsb_bytes = static_cast(ins << 1u) >> 30u; + int64_t absolute_addr = reinterpret_cast(*inpp) + (((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes); + int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)); + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + if (!special_fix_type && llabs(new_pc_offset) >= (max_val >> 1)) { + if ((reinterpret_cast(*outpp + 2) & 7u) != 0u) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + + (*outpp)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 + (*outpp)[1] = 0x14000003u; // B #0xc + memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); + *outpp += 4; + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr & ~3ull); + if (ref_idx <= current_idx) { + new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outpp)); + } else { + ctxp->insert_fix_map(ref_idx, *outpp, lsb, fmask); + new_pc_offset = 0; + } //if + } //if + + // the lsb_bytes will never be changed, so we can use lmask to keep it + (*outpp)[0] = (static_cast(new_pc_offset << (lsb - 2u)) & fmask) | (ins & lmask); + ++(*outpp); + } //if + } + break; + case op_adrp: + { + current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int32_t lsb_bytes = static_cast(ins << 1u) >> 30u; + int64_t absolute_addr = (reinterpret_cast(*inpp) & ~0xfffll) + ((((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes) << 12); + A64_LOGI("ins = 0x%.8X, pc = %p, abs_addr = %p", + ins, *inpp, reinterpret_cast(absolute_addr)); + if (ctxp->is_in_fixing_range(absolute_addr)) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr/* & ~3ull*/); + if (ref_idx > current_idx) { + // the bottom 12 bits of absolute_addr are masked out, + // so ref_idx must be less than or equal to current_idx! + A64_LOGE("ref_idx must be less than or equal to current_idx!"); + } //if + + // *absolute_addr may be changed due to relocation fixing + A64_LOGI("What is the correct way to fix this?"); + *(*outpp)++ = ins; // 0x90000000u; + } else { + if ((reinterpret_cast(*outpp + 2) & 7u) != 0u) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + + (*outpp)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 + (*outpp)[1] = 0x14000003u; // B #0xc + memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); // potential overflow? + *outpp += 4; + } //if + } + break; + default: + return false; + } + + ctxp->process_fix_map(current_idx); + ++(*inpp); + return true; +} + +//------------------------------------------------------------------------- +#define __flush_cache(c, n) __builtin___clear_cache(reinterpret_cast(c), reinterpret_cast(c) + n) +static void __fix_instructions(uint32_t *__restrict inp, int32_t count, uint32_t *__restrict outp) +{ + context ctx; + ctx.basep = reinterpret_cast(inp); + ctx.endp = reinterpret_cast(inp + count); + memset(ctx.dat, 0, sizeof(ctx.dat)); + static_assert(sizeof(ctx.dat) / sizeof(ctx.dat[0]) == A64_MAX_INSTRUCTIONS, + "please use A64_MAX_INSTRUCTIONS!"); +#ifndef NDEBUG + if (count > A64_MAX_INSTRUCTIONS) { + A64_LOGE("too many fixing instructions!"); + } //if +#endif // NDEBUG + + uint32_t *const outp_base = outp; + + while (--count >= 0) { + if (__fix_branch_imm(&inp, &outp, &ctx)) continue; + if (__fix_cond_comp_test_branch(&inp, &outp, &ctx)) continue; + if (__fix_loadlit(&inp, &outp, &ctx)) continue; + if (__fix_pcreladdr(&inp, &outp, &ctx)) continue; + + // without PC-relative offset + ctx.process_fix_map(ctx.get_and_set_current_index(inp, outp)); + *(outp++) = *(inp++); + } + + static constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111 + auto callback = reinterpret_cast(inp); + auto pc_offset = static_cast(callback - reinterpret_cast(outp)) >> 2; + if (static_cast(llabs(pc_offset)) >= (mask >> 1)) { + if ((reinterpret_cast(outp + 2) & 7u) != 0u) { + outp[0] = A64_NOP; + ++outp; + } //if + outp[0] = 0x58000051u; // LDR X17, #0x8 + outp[1] = 0xd61f0220u; // BR X17 + *reinterpret_cast(outp + 2) = callback; + outp += 4; + } else { + outp[0] = 0x14000000u | (pc_offset & mask); // "B" ADDR_PCREL26 + ++outp; + } //if + + const uintptr_t total = (outp - outp_base) * sizeof(uint32_t); + __flush_cache(outp_base, total); // necessary +} + +//------------------------------------------------------------------------- + +extern "C" { +#define __attribute __attribute__ +#define aligned(x) __aligned__(x) +#define __intval(p) reinterpret_cast(p) +#define __uintval(p) reinterpret_cast(p) +#define __ptr(p) reinterpret_cast(p) +#define __page_size 4096 +#define __page_align(n) __align_up(static_cast(n), __page_size) +#define __ptr_align(x) __ptr(__align_down(reinterpret_cast(x), __page_size)) +#define __align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1)) +#define __align_down(x, n) ((x) & -(n)) +#define __countof(x) static_cast(sizeof(x) / sizeof((x)[0])) // must be signed +#define __atomic_increase(p) __sync_add_and_fetch(p, 1) +#define __sync_cmpswap(p, v, n) __sync_bool_compare_and_swap(p, v, n) +#define __predict_true(exp) __builtin_expect((exp) != 0, 1) + +#define __make_rwx(p, n) ::mprotect(__ptr_align(p), \ + __page_align(__uintval(p) + n) != __page_align(__uintval(p)) ? __page_align(n) + __page_size : __page_align(n), \ + PROT_READ | PROT_WRITE | PROT_EXEC) + + //------------------------------------------------------------------------- + + static __attribute((aligned(__page_size))) uint32_t __insns_pool[A64_MAX_BACKUPS][A64_MAX_INSTRUCTIONS * 10]; + + //------------------------------------------------------------------------- + + class A64HookInit + { + public: + A64HookInit() + { + __make_rwx(__insns_pool, sizeof(__insns_pool)); + A64_LOGI("insns pool initialized."); + } + }; + static A64HookInit __init; + + //------------------------------------------------------------------------- + + static uint32_t *FastAllocateTrampoline() + { + static_assert((A64_MAX_INSTRUCTIONS * 10 * sizeof(uint32_t)) % 8 == 0, "8-byte align"); + static volatile int32_t __index = -1; + + int32_t i = __atomic_increase(&__index); + if (__predict_true(i >= 0 && i < __countof(__insns_pool))) { + return __insns_pool[i]; + } //if + + A64_LOGE("failed to allocate trampoline!"); + return NULL; + } + + //------------------------------------------------------------------------- + + A64_JNIEXPORT void *A64HookFunctionV(void *const symbol, void *const replace, + void *const rwx, const uintptr_t rwx_size) + { + uint32_t *trampoline = static_cast(rwx), *original = static_cast(symbol); + + static_assert(A64_MAX_INSTRUCTIONS >= 5, "please fix A64_MAX_INSTRUCTIONS!"); + int32_t count = (reinterpret_cast(original + 2) & 7u) != 0u ? 5 : 4; + if (trampoline) { + if (rwx_size < count * 10u) { + // LOGW("rwx size is too small to hold %u bytes backup instructions!", count * 10u); + return NULL; + } //if + __fix_instructions(original, count, trampoline); + } //if + + if (__make_rwx(original, 5 * sizeof(uint32_t)) == 0) { + if (count == 5) { + original[0] = A64_NOP; + ++original; + } //if + original[0] = 0x58000051u; // LDR X17, #0x8 + original[1] = 0xd61f0220u; // BR X17 + *reinterpret_cast(original + 2) = __intval(replace); + __flush_cache(symbol, 5 * sizeof(uint32_t)); + + A64_LOGI("inline hook %p->%p successfully! %zu bytes overwritten", + symbol, replace, 5 * sizeof(uint32_t)); + } else { + A64_LOGE("mprotect failed with errno = %d, p = %p, size = %zu", + errno, original, 5 * sizeof(uint32_t)); + trampoline = NULL; + } //if + + return trampoline; + } + + //------------------------------------------------------------------------- + + A64_JNIEXPORT void A64HookFunction(void *const symbol, void *const replace, void **result) + { + void *trampoline = NULL; + if (result != NULL) { + trampoline = FastAllocateTrampoline(); + *result = trampoline; + if (trampoline == NULL) return; + } //if + + trampoline = A64HookFunctionV(symbol, replace, trampoline, A64_MAX_INSTRUCTIONS * 10u); + if (trampoline == NULL && result != NULL) { + *result = NULL; + } //if + } +} + +#endif // defined(__aarch64__) diff --git a/shared/inline-hook/inlineHook.c b/shared/inline-hook/inlineHook.c new file mode 100644 index 00000000..ee544357 --- /dev/null +++ b/shared/inline-hook/inlineHook.c @@ -0,0 +1,427 @@ +/* +thumb16 thumb32 arm32 inlineHook +author: ele7enxxh +mail: ele7enxxh@qq.com +website: ele7enxxh.com +modified time: 2015-01-23 +created time: 2015-11-30 +*/ +#ifndef __aarch64__ +#include +#include +#include +#include +#include +#include +// #include +#include +#include + +#include "relocate.h" +#include "inlineHook.h" + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + +#define PAGE_START(addr) (~(PAGE_SIZE - 1) & (addr)) +#define SET_BIT0(addr) (addr | 1) +#define CLEAR_BIT0(addr) (addr & 0xFFFFFFFE) +#define TEST_BIT0(addr) (addr & 1) + +#define ACTION_ENABLE 0 +#define ACTION_DISABLE 1 +#ifndef __aarch64__ +enum hook_status { + REGISTERED, + HOOKED, +}; + +struct inlineHookItem { + uint32_t target_addr; + uint32_t new_addr; + uint32_t **proto_addr; + void *orig_instructions; + int orig_boundaries[4]; + int trampoline_boundaries[20]; + int count; + void *trampoline_instructions; + int length; + int status; + int mode; +}; + +struct inlineHookInfo { + struct inlineHookItem item[1024]; + int size; +}; + +static struct inlineHookInfo info = {0}; + +static int getAllTids(pid_t exclude_tid, pid_t *tids) +{ + char dir_path[32]; + DIR *dir; + int i; + struct dirent *entry; + pid_t tid; + + if (exclude_tid < 0) { + snprintf(dir_path, sizeof(dir_path), "/proc/self/task"); + } + else { + snprintf(dir_path, sizeof(dir_path), "/proc/%d/task", exclude_tid); + } + + dir = opendir(dir_path); + if (dir == NULL) { + return 0; + } + + i = 0; + while((entry = readdir(dir)) != NULL) { + tid = atoi(entry->d_name); + if (tid != 0 && tid != exclude_tid) { + tids[i++] = tid; + } + } + closedir(dir); + return i; +} + +static bool doProcessThreadPC(struct inlineHookItem *item, struct pt_regs *regs, int action) +{ + int offset; + int i; + + switch (action) + { + case ACTION_ENABLE: + offset = regs->ARM_pc - CLEAR_BIT0(item->target_addr); + for (i = 0; i < item->count; ++i) { + if (offset == item->orig_boundaries[i]) { + regs->ARM_pc = (uint32_t) item->trampoline_instructions + item->trampoline_boundaries[i]; + return true; + } + } + break; + case ACTION_DISABLE: + offset = regs->ARM_pc - (int) item->trampoline_instructions; + for (i = 0; i < item->count; ++i) { + if (offset == item->trampoline_boundaries[i]) { + regs->ARM_pc = CLEAR_BIT0(item->target_addr) + item->orig_boundaries[i]; + return true; + } + } + break; + } + + return false; +} + +static void processThreadPC(pid_t tid, struct inlineHookItem *item, int action) +{ + struct pt_regs regs; + + if (ptrace(PTRACE_GETREGS, tid, NULL, ®s) == 0) { + if (item == NULL) { + int pos; + + for (pos = 0; pos < info.size; ++pos) { + if (doProcessThreadPC(&info.item[pos], ®s, action) == true) { + break; + } + } + } + else { + doProcessThreadPC(item, ®s, action); + } + + ptrace(PTRACE_SETREGS, tid, NULL, ®s); + } +} + +static pid_t freeze(struct inlineHookItem *item, int action) +{ + int count; + pid_t tids[1024]; + pid_t pid; + + pid = -1; + count = getAllTids(gettid(), tids); + if (count > 0) { + pid = fork(); + + if (pid == 0) { + int i; + + for (i = 0; i < count; ++i) { + if (ptrace(PTRACE_ATTACH, tids[i], NULL, NULL) == 0) { + waitpid(tids[i], NULL, WUNTRACED); + processThreadPC(tids[i], item, action); + } + } + + raise(SIGSTOP); + + for (i = 0; i < count; ++i) { + ptrace(PTRACE_DETACH, tids[i], NULL, NULL); + } + + raise(SIGKILL); + } + + else if (pid > 0) { + waitpid(pid, NULL, WUNTRACED); + } + } + + return pid; +} + +static void unFreeze(pid_t pid) +{ + if (pid < 0) { + return; + } + + kill(pid, SIGCONT); + wait(NULL); +} + +static bool isExecutableAddr(uint32_t addr) +{ + FILE *fp; + char line[1024]; + uint32_t start; + uint32_t end; + + fp = fopen("/proc/self/maps", "r"); + if (fp == NULL) { + return false; + } + + while (fgets(line, sizeof(line), fp)) { + if (strstr(line, "r-xp") || strstr(line, "rwxp")) { + start = strtoul(strtok(line, "-"), NULL, 16); + end = strtoul(strtok(NULL, " "), NULL, 16); + if (addr >= start && addr <= end) { + fclose(fp); + return true; + } + } + } + + fclose(fp); + + return false; +} + +static struct inlineHookItem *findInlineHookItem(uint32_t target_addr) +{ + int i; + + for (i = 0; i < info.size; ++i) { + if (info.item[i].target_addr == target_addr) { + return &info.item[i]; + } + } + + return NULL; +} + +static struct inlineHookItem *addInlineHookItem() { + struct inlineHookItem *item; + + if (info.size >= 1024) { + return NULL; + } + + item = &info.item[info.size]; + ++info.size; + + return item; +} + +static void deleteInlineHookItem(int pos) +{ + info.item[pos] = info.item[info.size - 1]; + --info.size; +} + +enum ele7en_status registerInlineHook(uint32_t target_addr, uint32_t new_addr, uint32_t **proto_addr) +{ + struct inlineHookItem *item; + + if (!isExecutableAddr(target_addr) || !isExecutableAddr(new_addr)) { + return ELE7EN_ERROR_NOT_EXECUTABLE; + } + + item = findInlineHookItem(target_addr); + if (item != NULL) { + if (item->status == REGISTERED) { + return ELE7EN_ERROR_ALREADY_REGISTERED; + } + else if (item->status == HOOKED) { + return ELE7EN_ERROR_ALREADY_HOOKED; + } + else { + return ELE7EN_ERROR_UNKNOWN; + } + } + + item = addInlineHookItem(); + + item->target_addr = target_addr; + item->new_addr = new_addr; + item->proto_addr = proto_addr; + + item->length = TEST_BIT0(item->target_addr) ? 12 : 8; + // TODO: Allocate this in a place where GC doesn't ruin it + item->orig_instructions = malloc(item->length); + memcpy(item->orig_instructions, (void *) CLEAR_BIT0(item->target_addr), item->length); + + item->trampoline_instructions = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + relocateInstruction(item->target_addr, item->orig_instructions, item->length, item->trampoline_instructions, item->orig_boundaries, item->trampoline_boundaries, &item->count); + + item->status = REGISTERED; + + return ELE7EN_OK; +} + +static void doInlineUnHook(struct inlineHookItem *item, int pos) +{ + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_WRITE | PROT_EXEC); + memcpy((void *) CLEAR_BIT0(item->target_addr), item->orig_instructions, item->length); + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_EXEC); + munmap(item->trampoline_instructions, PAGE_SIZE); + free(item->orig_instructions); + + deleteInlineHookItem(pos); + + cacheflush(CLEAR_BIT0(item->target_addr), CLEAR_BIT0(item->target_addr) + item->length, 0); +} + +enum ele7en_status inlineUnHook(uint32_t target_addr) +{ + int i; + + for (i = 0; i < info.size; ++i) { + if (info.item[i].target_addr == target_addr && info.item[i].status == HOOKED) { + pid_t pid; + + pid = freeze(&info.item[i], ACTION_DISABLE); + + doInlineUnHook(&info.item[i], i); + + unFreeze(pid); + + return ELE7EN_OK; + } + } + + return ELE7EN_ERROR_NOT_HOOKED; +} + +void inlineUnHookAll() +{ + pid_t pid; + int i; + + pid = freeze(NULL, ACTION_DISABLE); + + for (i = 0; i < info.size; ++i) { + if (info.item[i].status == HOOKED) { + doInlineUnHook(&info.item[i], i); + --i; + } + } + + unFreeze(pid); +} + +static void doInlineHook(struct inlineHookItem *item) +{ + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_WRITE | PROT_EXEC); + + if (item->proto_addr != NULL) { + *(item->proto_addr) = TEST_BIT0(item->target_addr) ? (uint32_t *) SET_BIT0((uint32_t) item->trampoline_instructions) : item->trampoline_instructions; + } + + if (TEST_BIT0(item->target_addr)) { + int i; + + i = 0; + if (CLEAR_BIT0(item->target_addr) % 4 != 0) { + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xBF00; // NOP + } + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF8DF; + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF000; // LDR.W PC, [PC] + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr & 0xFFFF; + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr >> 16; + } + else { + ((uint32_t *) (item->target_addr))[0] = 0xe51ff004; // LDR PC, [PC, #-4] + ((uint32_t *) (item->target_addr))[1] = item->new_addr; + } + + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, PROT_READ | PROT_EXEC); + + item->status = HOOKED; + + cacheflush(CLEAR_BIT0(item->target_addr), CLEAR_BIT0(item->target_addr) + item->length, 0); +} + +enum ele7en_status inlineHook(uint32_t target_addr) +{ + int i; + struct inlineHookItem *item; + + item = NULL; + for (i = 0; i < info.size; ++i) { + if (info.item[i].target_addr == target_addr) { + item = &info.item[i]; + break; + } + } + + if (item == NULL) { + return ELE7EN_ERROR_NOT_REGISTERED; + } + + if (item->status == REGISTERED) { + pid_t pid; + + pid = freeze(item, ACTION_ENABLE); + + doInlineHook(item); + + unFreeze(pid); + + return ELE7EN_OK; + } + else if (item->status == HOOKED) { + return ELE7EN_ERROR_ALREADY_HOOKED; + } + else { + return ELE7EN_ERROR_UNKNOWN; + } +} + +void inlineHookAll() +{ + pid_t pid; + int i; + + pid = freeze(NULL, ACTION_ENABLE); + + for (i = 0; i < info.size; ++i) { + if (info.item[i].status == REGISTERED) { + doInlineHook(&info.item[i]); + } + } + + unFreeze(pid); +} +#endif +#endif \ No newline at end of file diff --git a/shared/inline-hook/relocate.c b/shared/inline-hook/relocate.c new file mode 100644 index 00000000..bb11a20b --- /dev/null +++ b/shared/inline-hook/relocate.c @@ -0,0 +1,617 @@ +/* +relocate instruction +author: ele7enxxh +mail: ele7enxxh@qq.com +website: ele7enxxh.com +modified time: 2016-10-17 +created time: 2015-01-17 +*/ +#ifndef __aarch64__ +#include "relocate.h" + +#define ALIGN_PC(pc) (pc & 0xFFFFFFFC) + +enum INSTRUCTION_TYPE { + // B
) { - if constexpr (::std::is_base_of_v>) { - if (arg) { - auto* klass = il2cpp_functions::object_get_class(reinterpret_cast(arg)); - #ifdef UNITY_2021 - if (klass && il2cpp_functions::class_is_valuetype(klass)) { - #else - if (klass && klass->valuetype) { - #endif - // Arg is an Il2CppObject* of a value type. It needs to be unboxed. - return il2cpp_functions::object_unbox(reinterpret_cast(arg)); - } +template +void* ExtractValue(T&& arg) { + il2cpp_functions::Init(); + + using Dt = ::std::decay_t; + if constexpr (::std::is_same_v || ::std::is_same_v) { + return nullptr; + } else if constexpr (::std::is_pointer_v
) { + if constexpr (::std::is_base_of_v>) { + if (arg) { + auto* klass = il2cpp_functions::object_get_class(reinterpret_cast(arg)); +#ifdef UNITY_2021 + if (klass && il2cpp_functions::class_is_valuetype(klass)) { +#else + if (klass && klass->valuetype) { +#endif + // Arg is an Il2CppObject* of a value type. It needs to be unboxed. + return il2cpp_functions::object_unbox(reinterpret_cast(arg)); } } - return arg; - } else if constexpr (has_il2cpp_conversion
) { - return arg.convert(); - } - else { - return const_cast(&arg); } + return arg; + } else if constexpr (has_il2cpp_conversion
) { + return arg.convert(); + } else { + return const_cast(&arg); } +} - template - void* ExtractTypeValue(T& arg) { - using Dt = ::std::decay_t; - if constexpr (std::is_same_v) { - return nullptr; - } - else if constexpr (has_il2cpp_conversion) { - return arg.convert(); - } - else if constexpr (::std::is_pointer_v
) { - // Pointer type, grab class and perform deduction for unbox. - // Must be classof deducible! - auto* k = classof(Dt); - if (k && il2cpp_functions::class_is_valuetype(k)) { - // Arg is an Il2CppObject* of a value type. It needs to be unboxed. - return il2cpp_functions::object_unbox(reinterpret_cast(arg)); - } - return arg; - } - else { - return const_cast(&arg); - } +template +void* ExtractTypeValue(T& arg) { + using Dt = ::std::decay_t; + if constexpr (std::is_same_v) { + return nullptr; + } else if constexpr (has_il2cpp_conversion) { + return arg.convert(); + } else if constexpr (::std::is_pointer_v
) { + // Pointer type, grab class and perform deduction for unbox. + // Must be classof deducible! + auto* k = classof(Dt); + if (k && il2cpp_functions::class_is_valuetype(k)) { + // Arg is an Il2CppObject* of a value type. It needs to be unboxed. + return il2cpp_functions::object_unbox(reinterpret_cast(arg)); + } + return arg; + } else { + return const_cast(&arg); } +} + +inline auto ExtractValues() { + return ::std::array(); +} + +template +inline auto ExtractValues(TArgs&&... args) { + constexpr std::size_t array_count = sizeof...(TArgs); + return std::array(::il2cpp_utils::ExtractValue(args)...); +} - inline auto ExtractValues() { - return ::std::array(); - } - - template - inline auto ExtractValues(TArgs&&... args) { - constexpr std::size_t array_count = sizeof...(TArgs); - return std::array(::il2cpp_utils::ExtractValue(args)...); - } - - #if __has_feature(cxx_exceptions) - /// @brief Instantiates a generic MethodInfo* from the provided Il2CppClasses. - /// This method will throw an Il2CppUtilException if it fails for any reason. - /// @return MethodInfo* for RunMethod calls. - const MethodInfo* MakeGenericMethod(const MethodInfo* info, ::std::span const types); - /// @brief Finds the first MethodInfo* described by the given Il2CppClass*, method name, and argument count. - /// Throws an Il2CppUtilException when: klass is null, or the method could not be found. - /// @return The found MethodInfo* - /// @param klass The Il2CppClass* to search for the method - /// @param methodName The il2cpp name of the method to find - /// @param argsCount The number of arguments to match (or -1 to not match at all) - const MethodInfo* FindMethodUnsafe(const Il2CppClass* klass, ::std::string_view methodName, int argsCount); - /// @brief Find the first MethodInfo* on the given instance, described by the methodName, and argument count. - /// Throws an Il2CppUtilException when: instance is null, the Il2CppClass* could not be loaded, or the method could not be found. - /// @return The found MethodInfo* - /// @param instance The Il2CppObject* to search for the method - /// @param methodName The il2cpp name of the method to find - /// @param argsCount The number of arguments to match (or -1 to not match at all) - const MethodInfo* FindMethodUnsafe(Il2CppObject* instance, ::std::string_view methodName, int argsCount); - /// @brief Find the first MethodInfo* of the class described by the namespace and className, described by the methodName, and argument count. - /// Throws an Il2CppUtilException when: the Il2CppClass* could not be found, or the method could not be found. - /// @return The found MethodInfo* - /// @param nameSpace The namespace in which to search for the class - /// @param className The il2cpp name of the class to find - /// @param methodName The il2cpp name of the method to find - /// @param argsCount The number of arguments to match (or -1 to not match at all) - const MethodInfo* FindMethodUnsafe(::std::string_view nameSpace, ::std::string_view className, ::std::string_view methodName, int argsCount); - - /// Attempts to look for a method that best matches given the FindMethodInfo data - /// if no method is found, returns null - /// Look at il2cpp-utils-methods.cpp for more details on how this resolution takes place - const MethodInfo* FindMethod(FindMethodInfo& info); +#if __has_feature(cxx_exceptions) +/// @brief Instantiates a generic MethodInfo* from the provided Il2CppClasses. +/// This method will throw an Il2CppUtilException if it fails for any reason. +/// @return MethodInfo* for RunMethod calls. +const MethodInfo* MakeGenericMethod(const MethodInfo* info, ::std::span const types); +/// @brief Finds the first MethodInfo* described by the given Il2CppClass*, method name, and argument count. +/// Throws an Il2CppUtilException when: klass is null, or the method could not be found. +/// @return The found MethodInfo* +/// @param klass The Il2CppClass* to search for the method +/// @param methodName The il2cpp name of the method to find +/// @param argsCount The number of arguments to match (or -1 to not match at all) +const MethodInfo* FindMethodUnsafe(const Il2CppClass* klass, ::std::string_view methodName, int argsCount); +/// @brief Find the first MethodInfo* on the given instance, described by the methodName, and argument count. +/// Throws an Il2CppUtilException when: instance is null, the Il2CppClass* could not be loaded, or the method could not be found. +/// @return The found MethodInfo* +/// @param instance The Il2CppObject* to search for the method +/// @param methodName The il2cpp name of the method to find +/// @param argsCount The number of arguments to match (or -1 to not match at all) +const MethodInfo* FindMethodUnsafe(Il2CppObject* instance, ::std::string_view methodName, int argsCount); +/// @brief Find the first MethodInfo* of the class described by the namespace and className, described by the methodName, and argument count. +/// Throws an Il2CppUtilException when: the Il2CppClass* could not be found, or the method could not be found. +/// @return The found MethodInfo* +/// @param nameSpace The namespace in which to search for the class +/// @param className The il2cpp name of the class to find +/// @param methodName The il2cpp name of the method to find +/// @param argsCount The number of arguments to match (or -1 to not match at all) +const MethodInfo* FindMethodUnsafe(::std::string_view nameSpace, ::std::string_view className, ::std::string_view methodName, int argsCount); + +/// Attempts to look for a method that best matches given the FindMethodInfo data +/// if no method is found, returns null +/// Look at il2cpp-utils-methods.cpp for more details on how this resolution takes place +const MethodInfo* FindMethod(FindMethodInfo& info); #pragma region FindMethod class - /// helper constructor - template - requires( - !::std::is_convertible_v && - std::is_constructible_v, GT> && - std::is_constructible_v, AT> - ) - inline const MethodInfo* FindMethod(T&& instanceOrKlass, ::std::string_view const methodName, GT&& genTypes, AT&& argTypes) { - auto klass = ::il2cpp_utils::ExtractClass(std::forward(instanceOrKlass)); - auto genTypesSpan = std::span(std::forward(genTypes)); - auto argTypesSpan = std::span(std::forward(argTypes)); - auto info = FindMethodInfo(klass, methodName, genTypesSpan, argTypesSpan); - return ::il2cpp_utils::FindMethod(info); - } - - /// no gen args - template +/// helper constructor +template + requires(!::std::is_convertible_v && std::is_constructible_v, GT> && std::is_constructible_v, AT>) +inline const MethodInfo* FindMethod(T&& instanceOrKlass, ::std::string_view const methodName, GT&& genTypes, AT&& argTypes) { + auto klass = ::il2cpp_utils::ExtractClass(std::forward(instanceOrKlass)); + auto genTypesSpan = std::span(std::forward(genTypes)); + auto argTypesSpan = std::span(std::forward(argTypes)); + auto info = FindMethodInfo(klass, methodName, genTypesSpan, argTypesSpan); + return ::il2cpp_utils::FindMethod(info); +} + +/// no gen args +template requires(!::std::is_convertible_v) - inline const MethodInfo* FindMethod(T&& instanceOrKlass, ::std::string_view methodName, AT&& argTypes) { - return ::il2cpp_utils::FindMethod(std::forward(instanceOrKlass), methodName, std::span(), std::forward(argTypes)); - } +inline const MethodInfo* FindMethod(T&& instanceOrKlass, ::std::string_view methodName, AT&& argTypes) { + return ::il2cpp_utils::FindMethod(std::forward(instanceOrKlass), methodName, std::span(), std::forward(argTypes)); +} - /// no args - template +/// no args +template requires(!::std::is_convertible_v) - inline const MethodInfo* FindMethod(T&& instanceOrKlass, ::std::string_view methodName) { - return ::il2cpp_utils::FindMethod(std::forward(instanceOrKlass), methodName, std::span(), std::span()); - } +inline const MethodInfo* FindMethod(T&& instanceOrKlass, ::std::string_view methodName) { + return ::il2cpp_utils::FindMethod(std::forward(instanceOrKlass), methodName, std::span(), std::span()); +} #pragma endregion #pragma region FindMethod string overloads - // gen and array args - template - inline const MethodInfo* FindMethod(std::string_view namespaze, std::string_view klassName, ::std::string_view const methodName, GT&& genTypes, AT&& argTypes) { - auto klass = ::il2cpp_utils::GetClassFromName(namespaze, klassName); - return ::il2cpp_utils::FindMethod(klass, methodName, std::forward(genTypes), std::forward(argTypes)); - } +// gen and array args +template +inline const MethodInfo* FindMethod(std::string_view namespaze, std::string_view klassName, ::std::string_view const methodName, GT&& genTypes, AT&& argTypes) { + auto klass = ::il2cpp_utils::GetClassFromName(namespaze, klassName); + return ::il2cpp_utils::FindMethod(klass, methodName, std::forward(genTypes), std::forward(argTypes)); +} - /// no gen args - template - inline const MethodInfo* FindMethod(std::string_view namespaze, std::string_view klassName, ::std::string_view methodName, AT&& argTypes) { - auto klass = ::il2cpp_utils::GetClassFromName(namespaze, klassName); - return ::il2cpp_utils::FindMethod(klass, methodName, std::span(), std::forward(argTypes)); - } +/// no gen args +template +inline const MethodInfo* FindMethod(std::string_view namespaze, std::string_view klassName, ::std::string_view methodName, AT&& argTypes) { + auto klass = ::il2cpp_utils::GetClassFromName(namespaze, klassName); + return ::il2cpp_utils::FindMethod(klass, methodName, std::span(), std::forward(argTypes)); +} - /// no args - inline const MethodInfo* FindMethod(std::string_view namespaze, std::string_view klassName, ::std::string_view methodName) { - auto klass = ::il2cpp_utils::GetClassFromName(namespaze, klassName); - return ::il2cpp_utils::FindMethod(klass, methodName, std::span(), std::span()); - } +/// no args +inline const MethodInfo* FindMethod(std::string_view namespaze, std::string_view klassName, ::std::string_view methodName) { + auto klass = ::il2cpp_utils::GetClassFromName(namespaze, klassName); + return ::il2cpp_utils::FindMethod(klass, methodName, std::span(), std::span()); +} #pragma endregion - bool IsConvertibleFrom(const Il2CppType* to, const Il2CppType* from, bool asArgs = true); +bool IsConvertibleFrom(const Il2CppType* to, const Il2CppType* from, bool asArgs = true); - inline const Il2CppGenericContainer* GetGenericContainer(MethodInfo const* method) { - if (!method->is_generic) { - SAFE_ABORT_MSG("METHOD IS NOT GENERIC"); - } +inline const Il2CppGenericContainer* GetGenericContainer(MethodInfo const* method) { + if (!method->is_generic) { + SAFE_ABORT_MSG("METHOD IS NOT GENERIC"); + } - if (method->is_inflated) { - auto genMethodInfo = method->genericMethod; + if (method->is_inflated) { + auto genMethodInfo = method->genericMethod; #ifdef UNITY_2021 - return reinterpret_cast(genMethodInfo->methodDefinition->genericContainerHandle); + return reinterpret_cast(genMethodInfo->methodDefinition->genericContainerHandle); #else - return genMethodInfo->methodDefinition->genericContainerHandle; + return genMethodInfo->methodDefinition->genericContainerHandle; #endif - } else { + } else { #ifdef UNITY_2021 - return reinterpret_cast(method->genericContainerHandle); + return reinterpret_cast(method->genericContainerHandle); #else - return = method->genericContainer; + return = method->genericContainer; #endif - } } +} - /// Returns if a given MethodInfo's parameters match the Il2CppType vector - /// \param isIdenticalOut is true if every parameter type matches identically. Can be null - template - bool ParameterMatch(const MethodInfo* method, std::span const genTypes, std::span const argTypes, std::optional isIdenticalOut) { - static auto logger = getLogger().WithContext("ParameterMatch"); - il2cpp_functions::Init(); - if (method->parameters_count != argTypes.size()) { - logger.warning("Potential method match had wrong number of parameters %i (expected %lu)", method->parameters_count, argTypes.size()); - return false; - } - - const Il2CppGenericContainer* genContainer; +/// Returns if a given MethodInfo's parameters match the Il2CppType vector +/// \param isIdenticalOut is true if every parameter type matches identically. Can be null +template +bool ParameterMatch(const MethodInfo* method, std::span const genTypes, std::span const argTypes, + std::optional isIdenticalOut) { + static auto logger = getLogger().WithContext("ParameterMatch"); + il2cpp_functions::Init(); + if (method->parameters_count != argTypes.size()) { + logger.warning("Potential method match had wrong number of parameters %i (expected %lu)", method->parameters_count, argTypes.size()); + return false; + } + + const Il2CppGenericContainer* genContainer; + + int32_t genCount = 0; + if (method->is_generic) { + genContainer = GetGenericContainer(method); + genCount = genContainer->type_argc; + } + + if ((size_t)genCount != genTypes.size()) { + logger.warning("Potential method match had wrong number of generics %i (expected %lu)", genCount, genTypes.size()); + logger.warning("is generic %i is inflated %i", method->is_generic, method->is_inflated); + return false; + } + bool isIdentical = true; + bool matches = true; + // TODO: supply boolStrictMatch and use type_equals instead of IsConvertibleFrom if supplied? + for (decltype(method->parameters_count) i = 0; i < method->parameters_count; i++) { + auto* paramType = method->parameters[i]; + if (paramType->type == IL2CPP_TYPE_MVAR) { + if (genCount == 0) { + logger.warning("No generic args to extract paramIdx %i", i); + continue; + } + auto genIdx = il2cpp_functions::MetadataCache_GetGenericParameterIndexFromParameter(paramType->data.genericParameterHandle) - genContainer->genericParameterStart; + if (genIdx < 0) { + logger.warning("Extracted invalid genIdx %i from parameter %i", genIdx, i); + continue; + } + if (genIdx >= genCount) { + logger.warning( + "ParameterMatch was not supplied enough genTypes to determine type of parameter %i " + "(had %i, needed %i)!", + i, genCount, genIdx); + continue; + } - int32_t genCount = 0; - if (method->is_generic) { - genContainer = GetGenericContainer(method); - genCount = genContainer->type_argc; + auto* klass = genTypes[genIdx]; + paramType = (paramType->byref) ? &klass->this_arg : &klass->byval_arg; } + // parameters are identical if every param matches exactly! + isIdentical &= paramType == argTypes[i]; - if ((size_t)genCount != genTypes.size()) { - logger.warning("Potential method match had wrong number of generics %i (expected %lu)", genCount, genTypes.size()); - logger.warning("is generic %i is inflated %i", method->is_generic, method->is_inflated); - return false; + // TODO: just because two parameter lists match doesn't necessarily mean this is the best match... + if (!IsConvertibleFrom(paramType, argTypes[i])) { + matches = false; + break; } - bool isIdentical = true; - bool matches = true; - // TODO: supply boolStrictMatch and use type_equals instead of IsConvertibleFrom if supplied? - for (decltype(method->parameters_count) i = 0; i < method->parameters_count; i++) { - auto* paramType = method->parameters[i]; - if (paramType->type == IL2CPP_TYPE_MVAR) { - if (genCount == 0) { - logger.warning("No generic args to extract paramIdx %i", i); - continue; - } - auto genIdx = il2cpp_functions::MetadataCache_GetGenericParameterIndexFromParameter(paramType->data.genericParameterHandle) - genContainer->genericParameterStart; - if (genIdx < 0) { - logger.warning("Extracted invalid genIdx %i from parameter %i", genIdx, i); - continue; - } - if (genIdx >= genCount) { - logger.warning( - "ParameterMatch was not supplied enough genTypes to determine type of parameter %i " - "(had %i, needed %i)!", - i, genCount, genIdx); - continue; - } + } + // write to out + if (isIdenticalOut.has_value()) { + *isIdenticalOut.value() = isIdentical; + } - auto* klass = genTypes[genIdx]; - paramType = (paramType->byref) ? &klass->this_arg : &klass->byval_arg; - } - // parameters are identical if every param matches exactly! - isIdentical &= paramType == argTypes[i]; + return matches; +} - // TODO: just because two parameter lists match doesn't necessarily mean this is the best match... - if (!IsConvertibleFrom(paramType, argTypes[i])) { - matches = false; - break; - } - } - // write to out - if (isIdenticalOut.has_value()) { - *isIdenticalOut.value() = isIdentical; - } +template +auto ParameterMatch(const MethodInfo* method, ::std::span const argTypes, std::optional isIdenticalOut) { + return ParameterMatch<0, argSz>(method, std::span(), argTypes, isIdenticalOut); +} - return matches; - } +template +bool ParameterMatch(const MethodInfo* method, std::array const& genTypes, std::array const& argTypes, std::optional isIdenticalOut) { + return ParameterMatch(method, genTypes, argTypes, isIdenticalOut); +} - template - auto ParameterMatch(const MethodInfo* method, ::std::span const argTypes, std::optional isIdenticalOut) { - return ParameterMatch<0, argSz>(method, std::span(), argTypes, isIdenticalOut); - } +template +bool ParameterMatch(const MethodInfo* method, std::array const& argTypes, std::optional isIdenticalOut) { + return ParameterMatch<0, sz>(method, std::span(), argTypes, isIdenticalOut); +} - template - bool ParameterMatch(const MethodInfo* method, std::array const& genTypes, std::array const& argTypes, std::optional isIdenticalOut) { - return ParameterMatch(method, genTypes, argTypes, isIdenticalOut); +/// @brief Calls the methodPointer on the provided const MethodInfo*, but throws a RunMethodException on failure. +/// If checkTypes is false, does not perform type checking and instead is a partially unsafe wrapper around invoking the methodPointer directly. +/// This function still performs simple checks (such as void vs. non-void returns and instance vs. static method invokes) even with checkTypes as false. +/// If you wish to forward this call to runtime_invoke (for example, in order to catch exceptions), consider using RunMethod/RunMethodUnsafe instead. +/// @tparam TOut The output to return. Defaults to void. +/// @tparam checkTypes Whether to check types or not. Defaults to true. +/// @tparam T The instance type. +/// @tparam TArgs The argument types. +/// @param instance The instance to invoke with. Should almost always be `this`. +/// @param method The MethodInfo* to use for type checking and conversions. +/// @param mPtr The method pointer to invoke specifically. +/// @param params The arguments to pass into the function. +template +TOut RunMethodFnPtr(T* instance, const MethodInfo* method, Il2CppMethodPointer mPtr, TArgs&&... params) { + static auto& logger = getLogger(); + if (!method) { + throw RunMethodException("Method cannot be null!", nullptr); + } + if (!mPtr) { + throw RunMethodException("Method pointer cannot be null (don't call an abstract method directly!)", method); + } + + if constexpr (checkTypes && sizeof...(TArgs) > 0) { + std::array types{ ::il2cpp_utils::ExtractType(params)... }; + if (!ParameterMatch(method, types, std::nullopt)) { + throw RunMethodException("Parameters do not match!", method); + } + auto* outType = ExtractIndependentType(); + if (outType) { + if (!IsConvertibleFrom(outType, method->return_type, false)) { + logger.warning("User requested TOut %s does not match the method's return object of type %s!", TypeGetSimpleName(outType), TypeGetSimpleName(method->return_type)); + throw RunMethodException("Return type of method is not convertible!", method); + } + } } + // NOTE: We need to remove references from our method pointers and copy in our parameters + // This works great for all cases EXCEPT for byref types + // For byref types, because we copy in our parameters, we need to provide a wrapper type that wraps a reference + // That type then is provided and copied in. + // That type is in byref.hpp as ByRef(T&&) - template - bool ParameterMatch(const MethodInfo* method, std::array const& argTypes, std::optional isIdenticalOut) { - return ParameterMatch<0, sz>(method, std::span(), argTypes, isIdenticalOut); + // Need to potentially call Class::Init here as well + // This snippet is almost identical to what libil2cpp does + if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0 && method->klass && !method->klass->cctor_finished_or_no_cctor) { + il2cpp_functions::Class_Init(method->klass); } - - /// @brief Calls the methodPointer on the provided const MethodInfo*, but throws a RunMethodException on failure. - /// If checkTypes is false, does not perform type checking and instead is a partially unsafe wrapper around invoking the methodPointer directly. - /// This function still performs simple checks (such as void vs. non-void returns and instance vs. static method invokes) even with checkTypes as false. - /// If you wish to forward this call to runtime_invoke (for example, in order to catch exceptions), consider using RunMethod/RunMethodUnsafe instead. - /// @tparam TOut The output to return. Defaults to void. - /// @tparam checkTypes Whether to check types or not. Defaults to true. - /// @tparam T The instance type. - /// @tparam TArgs The argument types. - /// @param instance The instance to invoke with. Should almost always be `this`. - /// @param method The MethodInfo* to use for type checking and conversions. - /// @param mPtr The method pointer to invoke specifically. - /// @param params The arguments to pass into the function. - template - TOut RunMethodFnPtr(T* instance, const MethodInfo* method, Il2CppMethodPointer mPtr, TArgs&&... params) { - static auto& logger = getLogger(); - if (!method) { - throw RunMethodException("Method cannot be null!", nullptr); - } - if (!mPtr) { - throw RunMethodException("Method pointer cannot be null (don't call an abstract method directly!)", method); - } - - if constexpr (checkTypes && sizeof...(TArgs) > 0) { - std::array types{::il2cpp_utils::ExtractType(params)...}; - if (!ParameterMatch(method, types, std::nullopt)) { - throw RunMethodException("Parameters do not match!", method); + try { + if constexpr (std::is_same_v) { + // Method has void return + if (!il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { + // If the method does NOT have a void return, yet we asked for one, this fails. + // This should ALWAYS fail because it's very wrong, regardless of checkTypes. + throw RunMethodException("Return type of method is not void, yet was requested as void!", method); } - auto* outType = ExtractIndependentType(); - if (outType) { - if (!IsConvertibleFrom(outType, method->return_type, false)) { - logger.warning("User requested TOut %s does not match the method's return object of type %s!", - TypeGetSimpleName(outType), TypeGetSimpleName(method->return_type)); - throw RunMethodException("Return type of method is not convertible!", method); - } + if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0) { + // Static method + return reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); } - } - // NOTE: We need to remove references from our method pointers and copy in our parameters - // This works great for all cases EXCEPT for byref types - // For byref types, because we copy in our parameters, we need to provide a wrapper type that wraps a reference - // That type then is provided and copied in. - // That type is in byref.hpp as ByRef(T&&) - - // Need to potentially call Class::Init here as well - // This snippet is almost identical to what libil2cpp does - if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0 && method->klass && !method->klass->cctor_finished_or_no_cctor) { - il2cpp_functions::Class_Init(method->klass); - } - try { - if constexpr (std::is_same_v) { - // Method has void return - if (!il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { - // If the method does NOT have a void return, yet we asked for one, this fails. - // This should ALWAYS fail because it's very wrong, regardless of checkTypes. - throw RunMethodException("Return type of method is not void, yet was requested as void!", method); + if (il2cpp_functions::class_is_valuetype(method->klass)) { + // Value type instance method. Instance parameter is always boxed in some way. + auto boxedRepr = instance; + if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { + // Boxing is only required if we invoke to adjustor thunks instead of actual impls + // Note that for whatever reason, we have exposed methods that are compiled that use literals, yet we still need to passed boxed reprs. + if constexpr (il2cpp_type_check::need_box::value) { + // TODO: Eventually remove this dependence on il2cpp_functions::Init + il2cpp_functions::Init(); + // Yeah, we cast literally all over the place. + boxedRepr = reinterpret_cast(il2cpp_functions::value_box(classof(T), boxedRepr)); + } else { + boxedRepr = reinterpret_cast(reinterpret_cast(boxedRepr) - 2); + } } - if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0) { - // Static method - return reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); + reinterpret_cast..., const MethodInfo*)>(mPtr)(boxedRepr, params..., method); + if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { + *instance = *reinterpret_cast(il2cpp_functions::object_unbox(reinterpret_cast(boxedRepr))); } + return; + } else { + return reinterpret_cast..., const MethodInfo*)>(mPtr)(instance, params..., method); + } + + } else { + // Method has non-void return + // if (il2cpp_functions::class_from_type(method->return_type)->instance_size != sizeof(TOut)) { + // TODO: + // The return value's size must always match. We know TOut is not void, but we do not know of anything else + // If the return value of the method is of a different size than TOut we should throw. + // Note that we cannot simply check sizeof(TOut) and compare it to instance size, since a TOut pointer would not match. + // We would need to properly ensure that the type is either byval or this_arg before comparing and/or ensuring size match + // } + // As a simple check, we can make sure the method we are attempting to call is not a void method: + if (il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { + throw RunMethodException("Return type of method is void, yet was requested as non-void!", method); + } + if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0) { + // Static method + return reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); + } else { if (il2cpp_functions::class_is_valuetype(method->klass)) { - // Value type instance method. Instance parameter is always boxed in some way. auto boxedRepr = instance; if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { // Boxing is only required if we invoke to adjustor thunks instead of actual impls @@ -458,522 +492,511 @@ namespace il2cpp_utils { boxedRepr = reinterpret_cast(reinterpret_cast(boxedRepr) - 2); } } - reinterpret_cast..., const MethodInfo*)>(mPtr)(boxedRepr, params..., method); + TOut res = reinterpret_cast..., const MethodInfo*)>(mPtr)(boxedRepr, params..., method); if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { *instance = *reinterpret_cast(il2cpp_functions::object_unbox(reinterpret_cast(boxedRepr))); } - return; - } else { - return reinterpret_cast..., const MethodInfo*)>(mPtr)(instance, params..., method); + return res; } - } else { - // Method has non-void return - // if (il2cpp_functions::class_from_type(method->return_type)->instance_size != sizeof(TOut)) { - // TODO: - // The return value's size must always match. We know TOut is not void, but we do not know of anything else - // If the return value of the method is of a different size than TOut we should throw. - // Note that we cannot simply check sizeof(TOut) and compare it to instance size, since a TOut pointer would not match. - // We would need to properly ensure that the type is either byval or this_arg before comparing and/or ensuring size match - // } - // As a simple check, we can make sure the method we are attempting to call is not a void method: - if (il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { - throw RunMethodException("Return type of method is void, yet was requested as non-void!", method); - } - if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0) { - // Static method - return reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); - } else { - if (il2cpp_functions::class_is_valuetype(method->klass)) { - auto boxedRepr = instance; - if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { - // Boxing is only required if we invoke to adjustor thunks instead of actual impls - // Note that for whatever reason, we have exposed methods that are compiled that use literals, yet we still need to passed boxed reprs. - if constexpr (il2cpp_type_check::need_box::value) { - // TODO: Eventually remove this dependence on il2cpp_functions::Init - il2cpp_functions::Init(); - // Yeah, we cast literally all over the place. - boxedRepr = reinterpret_cast(il2cpp_functions::value_box(classof(T), boxedRepr)); - } else { - boxedRepr = reinterpret_cast(reinterpret_cast(boxedRepr) - 2); - } - } - TOut res = reinterpret_cast..., const MethodInfo*)>(mPtr)(boxedRepr, params..., method); - if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { - *instance = *reinterpret_cast(il2cpp_functions::object_unbox(reinterpret_cast(boxedRepr))); - } - return res; - } - - // ref type - return reinterpret_cast..., const MethodInfo*)>(mPtr)(instance, params..., method); - } + // ref type + return reinterpret_cast..., const MethodInfo*)>(mPtr)(instance, params..., method); } - } catch (Il2CppExceptionWrapper& wrapper) { - logger.error("%s: Failed with exception: %s", il2cpp_functions::method_get_name(method), - il2cpp_utils::ExceptionToString(wrapper.ex).c_str()); - throw RunMethodException(wrapper.ex, method); } + } catch (Il2CppExceptionWrapper& wrapper) { + logger.error("%s: Failed with exception: %s", il2cpp_functions::method_get_name(method), il2cpp_utils::ExceptionToString(wrapper.ex).c_str()); + throw RunMethodException(wrapper.ex, method); } +} - /// @brief Calls the methodPointer on the provided const MethodInfo*, but throws a RunMethodException on failure. - /// If checkTypes is false, does not perform type checking and instead is a partially unsafe wrapper around invoking the methodPointer directly. - /// This function still performs simple checks (such as void vs. non-void returns and instance vs. static method invokes) even with checkTypes as false. - /// @tparam TOut The output to return. Defaults to void. - /// @tparam checkTypes Whether to check types or not. Defaults to true. - /// @tparam T The instance type (either an actual instance or an Il2CppClass*/Il2CppType*). - /// @tparam TArgs The argument types. - /// @param instance The instance or Il2CppClass*/Il2CppType* to invoke with. - /// @param method The MethodInfo* to invoke. - /// @param params The arguments to pass into the function. - template - TOut RunMethodFnPtr(T* instance, const MethodInfo* method, TArgs&&... params) { - return RunMethodFnPtr(instance, method, method->methodPointer, params...); - } - - - #else - /// @brief Instantiates a generic MethodInfo* from the provided Il2CppClasses. - /// @return MethodInfo* for RunMethod calls, will be nullptr on failure - const MethodInfo* MakeGenericMethod(const MethodInfo* info, ::std::span types) noexcept; - const MethodInfo* FindMethodUnsafe(const Il2CppClass* klass, ::std::string_view methodName, int argsCount) noexcept; - const MethodInfo* FindMethodUnsafe(Il2CppObject* instance, ::std::string_view methodName, int argsCount) noexcept; - const MethodInfo* FindMethodUnsafe(::std::string_view nameSpace, ::std::string_view className, ::std::string_view methodName, int argsCount) noexcept; - const MethodInfo* FindMethod(FindMethodInfo& info) noexcept; - #ifndef BS_HOOK_USE_CONCEPTS - template ), int> = 0> - #else - template - requires (... && !::std::is_convertible_v) - #endif - const MethodInfo* FindMethod(TArgs&&... args) noexcept { - auto info = FindMethodInfo(args...); - return FindMethod(info); - } - - bool IsConvertibleFrom(const Il2CppType* to, const Il2CppType* from, bool asArgs = true); - // Returns if a given MethodInfo's parameters match the Il2CppType vector - bool ParameterMatch(const MethodInfo* method, ::std::span argTypes, std::optional perfectMatch); - - // Returns if a given MethodInfo's parameters match the Il2CppType vector and generic types vector - bool ParameterMatch(const MethodInfo* method, ::std::span genTypes, ::std::span argTypes, std::optional perfectMatch); - #endif - - // Function made by zoller27osu, modified by Sc2ad - // Logs information about the given MethodInfo* as log(DEBUG) - void LogMethod(LoggerContextObject& logger, const MethodInfo* method); - - // Created by zoller27osu - // Calls LogMethod on all methods in the given class - void LogMethods(LoggerContextObject& logger, Il2CppClass* klass, bool logParents = false); - -#pragma region Invokers - // Experiment - namespace invokers { - // TODO: invoker concept - +/// @brief Calls the methodPointer on the provided const MethodInfo*, but throws a RunMethodException on failure. +/// If checkTypes is false, does not perform type checking and instead is a partially unsafe wrapper around invoking the methodPointer directly. +/// This function still performs simple checks (such as void vs. non-void returns and instance vs. static method invokes) even with checkTypes as false. +/// @tparam TOut The output to return. Defaults to void. +/// @tparam checkTypes Whether to check types or not. Defaults to true. +/// @tparam T The instance type (either an actual instance or an Il2CppClass*/Il2CppType*). +/// @tparam TArgs The argument types. +/// @param instance The instance or Il2CppClass*/Il2CppType* to invoke with. +/// @param method The MethodInfo* to invoke. +/// @param params The arguments to pass into the function. +template +TOut RunMethodFnPtr(T* instance, const MethodInfo* method, TArgs&&... params) { + return RunMethodFnPtr(instance, method, method->methodPointer, params...); +} - // TODO: Fix - template - ::std::variant FnPtrInvoker(T* instance, const MethodInfo* method, TArgs&&... params) noexcept { - bool isStatic = method->flags & METHOD_ATTRIBUTE_STATIC; - if (isStatic && method->klass && !method->klass->cctor_finished_or_no_cctor) { - il2cpp_functions::Class_Init(method->klass); - } +#else +/// @brief Instantiates a generic MethodInfo* from the provided Il2CppClasses. +/// @return MethodInfo* for RunMethod calls, will be nullptr on failure +const MethodInfo* MakeGenericMethod(const MethodInfo* info, ::std::span types) noexcept; +const MethodInfo* FindMethodUnsafe(const Il2CppClass* klass, ::std::string_view methodName, int argsCount) noexcept; +const MethodInfo* FindMethodUnsafe(Il2CppObject* instance, ::std::string_view methodName, int argsCount) noexcept; +const MethodInfo* FindMethodUnsafe(::std::string_view nameSpace, ::std::string_view className, ::std::string_view methodName, int argsCount) noexcept; +const MethodInfo* FindMethod(FindMethodInfo& info) noexcept; +#ifndef BS_HOOK_USE_CONCEPTS +template ), int> = 0> +#else +template + requires(... && !::std::is_convertible_v) +#endif +const MethodInfo* FindMethod(TArgs&&... args) noexcept { + auto info = FindMethodInfo(args...); + return FindMethod(info); +} - if (!method) { - return RunMethodException("Method cannot be null!", nullptr); - } +bool IsConvertibleFrom(const Il2CppType* to, const Il2CppType* from, bool asArgs = true); +// Returns if a given MethodInfo's parameters match the Il2CppType vector +bool ParameterMatch(const MethodInfo* method, ::std::span argTypes, std::optional perfectMatch); - auto mPtr = method->methodPointer; +// Returns if a given MethodInfo's parameters match the Il2CppType vector and generic types vector +bool ParameterMatch(const MethodInfo* method, ::std::span genTypes, ::std::span argTypes, std::optional perfectMatch); +#endif - if (!mPtr) { - return RunMethodException("Method pointer cannot be null (don't call an abstract method directly!)", method); - } +// Function made by zoller27osu, modified by Sc2ad +// Logs information about the given MethodInfo* as log(DEBUG) +void LogMethod(LoggerContextObject& logger, const MethodInfo* method); - if constexpr (std::is_same_v) { - // Method has void return - if (!il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { - // If the method does NOT have a void return, yet we asked for one, this fails. - // This should ALWAYS fail because it's very wrong, regardless of checkTypes. - return RunMethodException("Return type of method is not void, yet was requested as void!", method); - } - } else { - // Method does not void return - if (il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { - return RunMethodException("Return type of method is void, yet was requested as non-void!", method); - } - } +// Created by zoller27osu +// Calls LogMethod on all methods in the given class +void LogMethods(LoggerContextObject& logger, Il2CppClass* klass, bool logParents = false); - try { - if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0) { - // Static method - return reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); - } +#pragma region Invokers +// Experiment +namespace invokers { +// TODO: invoker concept - auto castedMPtr = reinterpret_cast..., const MethodInfo*)>(mPtr); +// TODO: Fix +template +::std::variant, RunMethodException> FnPtrInvoker(T* instance, const MethodInfo* method, TArgs&&... params) noexcept { + bool isStatic = method->flags & METHOD_ATTRIBUTE_STATIC; + if (isStatic && method->klass && !method->klass->cctor_finished_or_no_cctor) { + il2cpp_functions::Class_Init(method->klass); + } - auto boxedRepr = instance; - if (il2cpp_functions::class_is_valuetype(method->klass)) { - if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { - // Boxing is only required if we invoke to adjustor thunks instead of actual impls - // Note that for whatever reason, we have exposed methods that are compiled that use literals, yet we still need to passed boxed reprs. - if constexpr (il2cpp_type_check::need_box::value) { - // TODO: Eventually remove this dependence on il2cpp_functions::Init - il2cpp_functions::Init(); - // Yeah, we cast literally all over the place. - boxedRepr = reinterpret_cast(il2cpp_functions::value_box(classof(T), boxedRepr)); - } else { - boxedRepr = reinterpret_cast(reinterpret_cast(boxedRepr) - 2); - } - } - } + if (!method) { + return RunMethodException("Method cannot be null!", nullptr); + } - // update value type - auto unbox_value_type = [&]() { - if constexpr (il2cpp_type_check::need_box::value && sizeof(Il2CppCodeGenModule) <= 104) { - *instance = *reinterpret_cast(il2cpp_functions::object_unbox(reinterpret_cast(boxedRepr))); - } - }; + auto mPtr = method->methodPointer; - if constexpr (std::is_same_v) { - castedMPtr(boxedRepr, params..., method); - unbox_value_type(); - } else { - TOut res = castedMPtr(boxedRepr, params..., method); - unbox_value_type(); - return res; - } + if (!mPtr) { + return RunMethodException("Method pointer cannot be null (don't call an abstract method directly!)", method); + } - } catch (Il2CppExceptionWrapper& wrapper) { - return RunMethodException(wrapper.ex, method); + if constexpr (std::is_same_v) { + // Method has void return + if (!il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { + // If the method does NOT have a void return, yet we asked for one, this fails. + // This should ALWAYS fail because it's very wrong, regardless of checkTypes. + return RunMethodException("Return type of method is not void, yet was requested as void!", method); + } + } else { + // Method does not void return + if (il2cpp_functions::type_equals(method->return_type, &il2cpp_functions::defaults->void_class->byval_arg)) { + return RunMethodException("Return type of method is void, yet was requested as non-void!", method); } } - template - ::std::variant Il2CppInvoker(Il2CppObject* obj, const MethodInfo* method, TArgs&&... params) noexcept { - il2cpp_functions::Init(); - Il2CppException* exp = nullptr; - std::array invokeParams{ ::il2cpp_utils::ExtractTypeValue(params)... }; - auto* ret = il2cpp_functions::runtime_invoke(method, obj, invokeParams.data(), &exp); - - if (exp) { - return RunMethodException(exp, method); + try { + if ((method->flags & METHOD_ATTRIBUTE_STATIC) > 0) { + // Static method + return reinterpret_cast..., const MethodInfo*)>(mPtr)(params..., method); } - if constexpr (!std::is_same_v) { - // return type is not void, we should return something! - // type check boxing - // TODO: Type check boxing is needed? - // if constexpr (checkTypes && ret != nullptr) { - // auto constexpr must_box = ::il2cpp_utils::il2cpp_type_check::need_box::value; - // auto is_boxed = il2cpp_functions::class_is_valuetype(ret->klass); - // if (is_boxed != must_box) { - // throw RunMethodException(string_format("Klass %s requires boxing: %i Klass %s is boxed %i", - // ::il2cpp_utils::ClassStandardName(classof(TOut)), must_box, - // ::il2cpp_utils::ClassStandardName(ret->klass), is_boxed)); - // } - // } + auto castedMPtr = reinterpret_cast..., const MethodInfo*)>(mPtr); - // FIXME: what if the return type is a ByRef ? - if constexpr (::il2cpp_utils::il2cpp_type_check::need_box::value) { // value type returns from runtime invoke are boxed - // FIXME: somehow allow the gc free as an out of scope instead of having to temporarily save the retval? - auto retval = ::il2cpp_utils::Unbox(ret); - il2cpp_functions::il2cpp_GC_free(ret); - return retval; - } else if constexpr (::il2cpp_utils::il2cpp_reference_type_wrapper) { // ref type returns are just that, ref type returns - return TOut(ret); - } else { - // probably ref type pointer - return static_cast(static_cast(ret)); + auto boxedRepr = instance; + if (il2cpp_functions::class_is_valuetype(method->klass)) { + if constexpr (sizeof(Il2CppCodeGenModule) <= 104) { + // Boxing is only required if we invoke to adjustor thunks instead of actual impls + // Note that for whatever reason, we have exposed methods that are compiled that use literals, yet we still need to passed boxed reprs. + if constexpr (il2cpp_type_check::need_box::value) { + // TODO: Eventually remove this dependence on il2cpp_functions::Init + il2cpp_functions::Init(); + // Yeah, we cast literally all over the place. + boxedRepr = reinterpret_cast(il2cpp_functions::value_box(classof(T), boxedRepr)); + } else { + boxedRepr = reinterpret_cast(reinterpret_cast(boxedRepr) - 2); + } } } - } - } // namespace invokers -#pragma endregion - - template - // Runs a MethodInfo with the specified parameters and instance, with return type TOut. - // Assumes a static method if instance == nullptr. May fail due to exception or wrong name, hence the ::std::optional. - ::std::variant RunMethod(T&& wrappedInstance, const MethodInfo* method, TArgs&&... params) noexcept { - static auto& logger = getLogger(); - RET_NULLOPT_UNLESS(logger, method); - if constexpr (checkTypes) { - // only check args if TArgs is > 0 - if (method->parameters_count != sizeof...(TArgs)) { - logger.warning("MethodInfo parameter count %i does not match actual parameter count %i", method->parameters_count, sizeof...(TArgs)); - } - - if constexpr (sizeof...(TArgs) > 0) { - std::array types{ ::il2cpp_utils::ExtractType(params)... }; - if (!ParameterMatch(method, types, std::nullopt)) { - return RunMethodException("Parameters do not match!", method); - } + // update value type + auto unbox_value_type = [&]() { + if constexpr (il2cpp_type_check::need_box::value && sizeof(Il2CppCodeGenModule) <= 104) { + *instance = *reinterpret_cast(il2cpp_functions::object_unbox(reinterpret_cast(boxedRepr))); } + }; - if constexpr (!std::is_same_v) { - auto* outType = ExtractIndependentType(); - if (outType) { - if (!IsConvertibleFrom(outType, method->return_type, false)) { - logger.warning("User requested TOut %s does not match the method's return object of type %s!", TypeGetSimpleName(outType), TypeGetSimpleName(method->return_type)); - return RunMethodException(string_format("Return type of method is not convertible to: %s!", TypeGetSimpleName(outType)), method); - } - } - } + if constexpr (std::is_same_v) { + castedMPtr(boxedRepr, params..., method); + unbox_value_type(); + } else { + TOut res = castedMPtr(boxedRepr, params..., method); + unbox_value_type(); + return res; } - void* inst = ::il2cpp_utils::ExtractValue(wrappedInstance); // null is allowed (for T = Il2CppType* or Il2CppClass*) + } catch (Il2CppExceptionWrapper& wrapper) { + return RunMethodException(wrapper.ex, method); + } +} - auto isStatic = method->flags & METHOD_ATTRIBUTE_STATIC; - if (!isStatic && !inst) { - return RunMethodException("Method is static but instance is null!", method); +template +::std::variant, RunMethodException> Il2CppInvoker(Il2CppObject* obj, const MethodInfo* method, TArgs&&... params) noexcept { + il2cpp_functions::Init(); + Il2CppException* exp = nullptr; + std::array invokeParams{ ::il2cpp_utils::ExtractTypeValue(params)... }; + auto* ret = il2cpp_functions::runtime_invoke(method, obj, invokeParams.data(), &exp); + + if (exp) { + return RunMethodException(exp, method); + } + + if constexpr (!std::is_same_v) { + // return type is not void, we should return something! + // type check boxing + // TODO: Type check boxing is needed? + // if constexpr (checkTypes && ret != nullptr) { + // auto constexpr must_box = ::il2cpp_utils::il2cpp_type_check::need_box::value; + // auto is_boxed = il2cpp_functions::class_is_valuetype(ret->klass); + // if (is_boxed != must_box) { + // throw RunMethodException(string_format("Klass %s requires boxing: %i Klass %s is boxed %i", + // ::il2cpp_utils::ClassStandardName(classof(TOut)), must_box, + // ::il2cpp_utils::ClassStandardName(ret->klass), is_boxed)); + // } + // } + + // FIXME: what if the return type is a ByRef ? + if constexpr (::il2cpp_utils::il2cpp_type_check::need_box::value) { // value type returns from runtime invoke are boxed + // FIXME: somehow allow the gc free as an out of scope instead of having to temporarily save the retval? + auto retval = ::il2cpp_utils::Unbox(ret); + il2cpp_functions::il2cpp_GC_free(ret); + return retval; + } else if constexpr (::il2cpp_utils::il2cpp_reference_type_wrapper) { // ref type returns are just that, ref type returns + return TOut(ret); + } else { + // probably ref type pointer + return static_cast(static_cast(ret)); } + } +} +} // namespace invokers +#pragma endregion - // Experiment - // return Invoker(inst, method, std::forward(params)...); +template +using MethodResult = ::std::variant, RunMethodException>; - Il2CppException* exp = nullptr; - std::array invokeParams{ ::il2cpp_utils::ExtractTypeValue(params)... }; - il2cpp_functions::Init(); - auto* ret = il2cpp_functions::runtime_invoke(method, inst, invokeParams.data(), &exp); +template + requires(!::std::is_convertible_v || std::is_same_v) +// Runs a MethodInfo with the specified parameters and instance, with return type TOut. +// Assumes a static method if instance == nullptr. May fail due to exception or wrong name, hence the ::std::optional. +MethodResult RunMethod(T&& wrappedInstance, const MethodInfo* method, TArgs&&... params) noexcept { + static auto& logger = getLogger(); + RET_NULLOPT_UNLESS(logger, method); - if (exp) { - return RunMethodException(exp, method); + if constexpr (checkTypes) { + // only check args if TArgs is > 0 + if (method->parameters_count != sizeof...(TArgs)) { + logger.warning("MethodInfo parameter count %i does not match actual parameter count %lu", method->parameters_count, sizeof...(TArgs)); } - if constexpr (!std::is_same_v) { - if constexpr (checkTypes) { - if (ret) { - // By using this instead of ExtractType, we avoid unboxing because the ultimate type in that case would depend on the - // method in the first place - auto* outType = ExtractIndependentType(); - if (outType) { - auto* retType = ExtractType(ret); - if (!IsConvertibleFrom(outType, retType, false)) { - logger.warning("User requested TOut %s does not match the method's return object of type %s!", TypeGetSimpleName(outType), TypeGetSimpleName(retType)); - } - } - } + if constexpr (sizeof...(TArgs) > 0) { + std::array types{ ::il2cpp_utils::ExtractType(params)... }; + if (!ParameterMatch(method, types, std::nullopt)) { + return RunMethodException("Parameters do not match!", method); } + } - // return type is not void, we should return something! - // type check boxing - // TODO: Type check boxing is needed? - // if constexpr (checkTypes && ret != nullptr) { - // auto constexpr must_box = ::il2cpp_utils::il2cpp_type_check::need_box::value; - // auto is_boxed = il2cpp_functions::class_is_valuetype(ret->klass); - // if (is_boxed != must_box) { - // throw RunMethodException(string_format("Klass %s requires boxing: %i Klass %s is boxed %i", - // ::il2cpp_utils::ClassStandardName(classof(TOut)), must_box, - // ::il2cpp_utils::ClassStandardName(ret->klass), is_boxed)); - // } - // } - - // FIXME: what if the return type is a ByRef ? - if constexpr (::il2cpp_utils::il2cpp_type_check::need_box::value) { // value type returns from runtime invoke are boxed - // FIXME: somehow allow the gc free as an out of scope instead of having to temporarily save the retval? - auto retval = ::il2cpp_utils::Unbox(ret); - il2cpp_functions::il2cpp_GC_free(ret); - return retval; - } else if constexpr (::il2cpp_utils::il2cpp_reference_type_wrapper) { // ref type returns are just that, ref type returns - return TOut(ret); - } else { - // probably ref type pointer - return static_cast(static_cast(ret)); + if constexpr (!std::is_same_v) { + auto* outType = ExtractIndependentType(); + if (outType) { + if (!IsConvertibleFrom(outType, method->return_type, false)) { + logger.warning("User requested TOut %s does not match the method's return object of type %s!", TypeGetSimpleName(outType), TypeGetSimpleName(method->return_type)); + return RunMethodException(string_format("Return type of method is not convertible to: %s!", TypeGetSimpleName(outType)), method); + } } } } - /// @brief Runs the provided method and rethrows any exception that occurs. Will throw a RunMethodException. - /// If checkTypes is false, does not perform type checking and instead is an unsafe wrapper around runtime_invoke. - /// @tparam TOut The output to return. Defaults to void. - /// @tparam checkTypes Whether to check types or not. Defaults to true. - /// @tparam T The instance type (an actual instance or nullptr Il2CppClass*, etc.) - /// @tparam TArgs The argument types. - /// @param instance The instance or nullptr Il2CppClass* to invoke with. - /// @param method The MethodInfo* to invoke. - /// @param params The arguments to pass into the function. - /// @return The result from the function, or will throw. - template - inline TOut RunMethodRethrow(TArgs&&... params) { - auto result = ::il2cpp_utils::RunMethod(std::forward(params)...); - - if (auto exception = std::get_if<::il2cpp_utils::RunMethodException>(&result)) { - throw exception; - } + void* inst = ::il2cpp_utils::ExtractValue(wrappedInstance); // null is allowed (for T = Il2CppType* or Il2CppClass*) - return std::get(std::move(result)); + auto isStatic = method->flags & METHOD_ATTRIBUTE_STATIC; + if (!isStatic && !inst) { + return RunMethodException("Method is static but instance is null!", method); } - /// @brief Runs the provided method and rethrows any exception that occurs. Will throw a RunMethodException. - /// If checkTypes is false, does not perform type checking and instead is an unsafe wrapper around runtime_invoke. - /// @tparam TOut The output to return. Defaults to void. - /// @tparam checkTypes Whether to check types or not. Defaults to true. - /// @tparam T The instance type (an actual instance or nullptr Il2CppClass*, etc.) - /// @tparam TArgs The argument types. - /// @param instance The instance or nullptr Il2CppClass* to invoke with. - /// @param method The MethodInfo* to invoke. - /// @param params The arguments to pass into the function. - /// @return The result from the function, or will throw. - template - inline std::optional RunMethodOpt(TArgs&&... params) noexcept { - auto result = ::il2cpp_utils::RunMethod(std::forward(params)...); - - if (auto exception = std::get_if<::il2cpp_utils::RunMethodException>(&result)) { - static auto& logger = getLogger(); - logger.error("%s: Failed with exception: %s", il2cpp_functions::method_get_name(exception->info), il2cpp_utils::ExceptionToString(exception->ex).c_str()); - return std::nullopt; - } + // Experiment + // return Invoker(inst, method, std::forward(params)...); - return std::get(std::move(result)); - } + Il2CppException* exp = nullptr; + std::array invokeParams{ ::il2cpp_utils::ExtractTypeValue(params)... }; + il2cpp_functions::Init(); + auto* ret = il2cpp_functions::runtime_invoke(method, inst, invokeParams.data(), &exp); - /// @brief Instantiates a generic MethodInfo* from the provided Il2CppClasses and invokes it. - /// @n This method will not crash. - /// @tparam TOut The return type of the method to invoke - /// @tparam T Instance type - /// @tparam TArgs Parameters to RunMethod - /// @param instance Instance to RunMethod, or null/class - /// @param info Generic MethodInfo* to invoke - /// @param genTypes Types to instantiate the generic MethodInfo* with - /// @param params Parameters to RunMethod - template - ::std::variant RunGenericMethod(T&& instance, const MethodInfo* info, ::std::span genTypes, TArgs&&... params) noexcept { - static auto& logger = getLogger(); - auto* createdMethod = RET_NULLOPT_UNLESS(logger, MakeGenericMethod(info, genTypes)); - return RunMethod(instance, createdMethod, params...); + if (exp) { + return RunMethodException(exp, method); } - template - ::std::variant RunGenericMethod(T&& classOrInstance, ::std::string_view methodName, ::std::span genTypes, TArgs&&... params) noexcept { - static auto& logger = getLogger(); - std::array types{::il2cpp_utils::ExtractType(params)...}; - - auto* info = RET_NULLOPT_UNLESS(logger, FindMethod(classOrInstance, NoArgClass(), methodName, genTypes, types)); - return RunGenericMethod(classOrInstance, info, genTypes, params...); + // void return + if constexpr (std::is_same_v) { + return MethodResult(); } - template - // Runs a static generic method with the specified method name and arguments, on the class with the specified namespace and class name. - // The method also has return type TOut. - ::std::variant RunGenericMethod(::std::string_view nameSpace, ::std::string_view klassName, ::std::string_view methodName, ::std::span genTypes, - TArgs&&... params) noexcept { - static auto& logger = getLogger(); - auto* klass = RET_NULLOPT_UNLESS(logger, GetClassFromName(nameSpace, klassName)); - return RunGenericMethod(klass, methodName, genTypes, params...); - } - - template - // Creates a new object of the given class using the given constructor parameters - // Will only run a .ctor whose parameter types match the given arguments. - ::std::optional New(Il2CppClass* klass, TArgs&& ...args) { - static auto& logger = getLogger(); - il2cpp_functions::Init(); - Il2CppObject* obj; - if constexpr (creationType == CreationType::Temporary) { - // object_new call - obj = RET_NULLOPT_UNLESS(logger, il2cpp_functions::object_new(klass)); - } else { - obj = RET_NULLOPT_UNLESS(logger, createManual(klass)); - } - // runtime_invoke constructor with right type(s) of arguments, return null if constructor errors - std::array types{::il2cpp_utils::ExtractType(args)...}; - auto* method = RET_NULLOPT_UNLESS(logger, FindMethod(klass, ".ctor", types)); - RET_NULLOPT_UNLESS(logger, RunMethodOpt(obj, method, args...)); - return FromIl2CppObject(obj); - } - - // TODO: Rename to New, rename existing New to NewObject or equivalent - /// @brief Allocates a new instance of a particular Il2CppClass*, either allowing it to be GC'd normally or manually controlled. - /// The Il2CppClass* is derived from the TOut template parameter. - /// The found constructor method will be cached. - /// Will throw either an il2cpp_utils::exceptions::StackTraceException or il2cpp_utils::RunMethodException if errors occur. - /// @tparam TOut The type to create. - /// @tparam creationType The way to create the instance. - /// @tparam TArgs The arguments to call the constructor with. - /// @param args The arguments to call the constructor with. - template - TOut NewSpecific(TArgs&&... args) { - static auto* klass = classof(TOut); - Il2CppObject* obj; - if constexpr (creationType == CreationType::Temporary) { - // object_new call - obj = il2cpp_functions::object_new(klass); - if (!obj) { - throw exceptions::StackTraceException("Failed to allocate new object via object_new!"); + if constexpr (checkTypes) { + if (ret) { + // By using this instead of ExtractType, we avoid unboxing because the ultimate type in that case would depend on the + // method in the first place + auto* outType = ExtractIndependentType(); + if (outType) { + auto* retType = ExtractType(ret); + if (!IsConvertibleFrom(outType, retType, false)) { + logger.warning("User requested TOut %s does not match the method's return object of type %s!", TypeGetSimpleName(outType), TypeGetSimpleName(retType)); + } } - } else { - obj = createManualThrow(klass); - } - // Only need to extract based off of types, since we are asusming our TOut is classof-able already - static auto ctorMethod = FindMethod(klass, ".ctor", std::array{ExtractIndependentType>()...}); - if (!ctorMethod) { - throw exceptions::StackTraceException(string_format("Failed to find a matching .ctor method during construction of type: %s", ClassStandardName(klass).c_str())); } - ::il2cpp_utils::RunMethodRethrow(obj, ctorMethod, args...); - if constexpr (std::is_pointer_v) { - return reinterpret_cast(obj); - } else if constexpr (has_il2cpp_conversion) { - // Handle construction for wrapper types, construct from void*s - return TOut(reinterpret_cast(obj)); + } + + if constexpr (!std::is_same_v) { + // return type is not void, we should return something! + // type check boxing + // TODO: Type check boxing is needed? + // if constexpr (checkTypes && ret != nullptr) { + // auto constexpr must_box = ::il2cpp_utils::il2cpp_type_check::need_box::value; + // auto is_boxed = il2cpp_functions::class_is_valuetype(ret->klass); + // if (is_boxed != must_box) { + // throw RunMethodException(string_format("Klass %s requires boxing: %i Klass %s is boxed %i", + // ::il2cpp_utils::ClassStandardName(classof(TOut)), must_box, + // ::il2cpp_utils::ClassStandardName(ret->klass), is_boxed)); + // } + // } + + // FIXME: what if the return type is a ByRef ? + if constexpr (::il2cpp_utils::il2cpp_type_check::need_box::value) { // value type returns from runtime invoke are boxed + // FIXME: somehow allow the gc free as an out of scope instead of having to temporarily save the retval? + auto retval = ::il2cpp_utils::Unbox(ret); + il2cpp_functions::il2cpp_GC_free(ret); + return retval; + } else if constexpr (::il2cpp_utils::il2cpp_reference_type_wrapper) { // ref type returns are just that, ref type returns + return TOut(ret); } else { - static_assert(false_t, "Cannot C# construct the provided value type that is not a wrapper type!"); + // probably ref type pointer + return static_cast(static_cast(ret)); } } +} - template - // Creates a new object of the given class using the given constructor parameters - // DOES NOT PERFORM ARGUMENT TYPE CHECKING! Uses the first .ctor with the right number of parameters it sees. - ::std::optional NewUnsafe(const Il2CppClass* klass, TArgs&& ...args) { - static auto& logger = getLogger(); - il2cpp_functions::Init(); +template +// Runs a (static) method with the specified method name, with return type TOut. +// Checks the types of the parameters against the candidate methods. +MethodResult RunMethod(T&& classOrInstance, ::std::string_view methodName, TArgs&&... params) { + static auto& logger = getLogger(); - Il2CppObject* obj; - if constexpr (creationType == CreationType::Temporary) { - // object_new call - obj = RET_NULLOPT_UNLESS(logger, il2cpp_functions::object_new(klass)); - } else { - obj = RET_NULLOPT_UNLESS(logger, createManual(klass)); - } - // runtime_invoke constructor with right number of args, return null if constructor errors - RET_NULLOPT_UNLESS(logger, RunMethodUnsafe(obj, ".ctor", args...)); - return FromIl2CppObject(obj); - } - - template - // Creates a new object of the returned type using the given constructor parameters - // Will only run a .ctor whose parameter types match the given arguments. - #ifndef BS_HOOK_USE_CONCEPTS - ::std::enable_if_t<(... && ((!::std::is_same_v || !::std::is_same_v) && !::std::is_convertible_v)), ::std::optional> - #else - requires (... && ((!::std::is_same_v || !::std::is_same_v) && !::std::is_convertible_v)) ::std::optional - #endif - New(TArgs&& ...args) { - static auto& logger = getLogger(); - auto* klass = RET_NULLOPT_UNLESS(logger, (NoArgClass())); - return New(klass, args...); + std::array const types{ ::il2cpp_utils::ExtractType(params)... }; + auto* method = RET_NULLOPT_UNLESS(logger, FindMethod(classOrInstance, methodName, types)); + + return RunMethod(std::forward(classOrInstance), method, std::forward(params)...); +} + +template +// Runs a static method with the specified method name and arguments, on the class with the specified namespace and class name. +// The method also has return type TOut. +MethodResult RunMethod(::std::string_view nameSpace, ::std::string_view klassName, ::std::string_view methodName, TArgs&&... params) { + static auto& logger = getLogger(); + auto* klass = RET_NULLOPT_UNLESS(logger, GetClassFromName(nameSpace, klassName)); + return RunMethod(klass, methodName, params...); +} + +/// @brief Runs the provided method and rethrows any exception that occurs. Will throw a RunMethodException. +/// If checkTypes is false, does not perform type checking and instead is an unsafe wrapper around runtime_invoke. +/// @tparam TOut The output to return. Defaults to void. +/// @tparam checkTypes Whether to check types or not. Defaults to true. +/// @tparam T The instance type (an actual instance or nullptr Il2CppClass*, etc.) +/// @tparam TArgs The argument types. +/// @param instance The instance or nullptr Il2CppClass* to invoke with. +/// @param method The MethodInfo* to invoke. +/// @param params The arguments to pass into the function. +/// @return The result from the function, or will throw. +template +inline TOut RunMethodRethrow(TArgs&&... params) { + auto result = ::il2cpp_utils::RunMethod(std::forward(params)...); + + if (auto exception = std::get_if<::il2cpp_utils::RunMethodException>(&result)) { + throw exception; + } + if constexpr (!std::is_same_v) { + return std::get(std::move(result)); } +} - template - // Creates a new object of the class with the given nameSpace and className using the given constructor parameters. - // Will only run a .ctor whose parameter types match the given arguments. - ::std::optional New(::std::string_view nameSpace, ::std::string_view className, TArgs&& ...args) { +/// @brief Runs the provided method and rethrows any exception that occurs. Will throw a RunMethodException. +/// If checkTypes is false, does not perform type checking and instead is an unsafe wrapper around runtime_invoke. +/// @tparam TOut The output to return. Defaults to void. +/// @tparam checkTypes Whether to check types or not. Defaults to true. +/// @tparam T The instance type (an actual instance or nullptr Il2CppClass*, etc.) +/// @tparam TArgs The argument types. +/// @param instance The instance or nullptr Il2CppClass* to invoke with. +/// @param method The MethodInfo* to invoke. +/// @param params The arguments to pass into the function. +/// @return The result from the function, or will throw. +template +inline std::optional> RunMethodOpt(TArgs&&... params) noexcept { + auto result = ::il2cpp_utils::RunMethod(std::forward(params)...); + + if (auto exception = std::get_if<::il2cpp_utils::RunMethodException>(&result)) { static auto& logger = getLogger(); - auto* klass = RET_0_UNLESS(logger, GetClassFromName(nameSpace, className)); - return New(klass, args...); + logger.error("%s: Failed with exception: %s", il2cpp_functions::method_get_name(exception->info), il2cpp_utils::ExceptionToString(exception->ex).c_str()); + return std::nullopt; } - template - // Creates a new object of the class with the given nameSpace and className using the given constructor parameters. - // DOES NOT PERFORM ARGUMENT TYPE CHECKING! Uses the first .ctor with the right number of parameters it sees. - ::std::optional NewUnsafe(::std::string_view nameSpace, ::std::string_view className, TArgs* ...args) { - static auto& logger = getLogger(); - auto* klass = RET_0_UNLESS(logger, GetClassFromName(nameSpace, className)); - return NewUnsafe(klass, args...); + return std::get>(std::move(result)); +} + + +/// @brief Instantiates a generic MethodInfo* from the provided Il2CppClasses and invokes it. +/// @n This method will not crash. +/// @tparam TOut The return type of the method to invoke +/// @tparam T Instance type +/// @tparam TArgs Parameters to RunMethod +/// @param instance Instance to RunMethod, or null/class +/// @param info Generic MethodInfo* to invoke +/// @param genTypes Types to instantiate the generic MethodInfo* with +/// @param params Parameters to RunMethod +template +::std::variant RunGenericMethod(T&& instance, const MethodInfo* info, ::std::span genTypes, TArgs&&... params) noexcept { + static auto& logger = getLogger(); + auto* createdMethod = RET_NULLOPT_UNLESS(logger, MakeGenericMethod(info, genTypes)); + return RunMethod(instance, createdMethod, params...); +} + +template +::std::variant RunGenericMethod(T&& classOrInstance, ::std::string_view methodName, ::std::span genTypes, TArgs&&... params) noexcept { + static auto& logger = getLogger(); + std::array types{ ::il2cpp_utils::ExtractType(params)... }; + + auto* info = RET_NULLOPT_UNLESS(logger, FindMethod(classOrInstance, NoArgClass(), methodName, genTypes, types)); + return RunGenericMethod(classOrInstance, info, genTypes, params...); +} +template +// Runs a static generic method with the specified method name and arguments, on the class with the specified namespace and class name. +// The method also has return type TOut. +::std::variant RunGenericMethod(::std::string_view nameSpace, ::std::string_view klassName, ::std::string_view methodName, ::std::span genTypes, + TArgs&&... params) noexcept { + static auto& logger = getLogger(); + auto* klass = RET_NULLOPT_UNLESS(logger, GetClassFromName(nameSpace, klassName)); + return RunGenericMethod(klass, methodName, genTypes, params...); +} + +template +// Creates a new object of the given class using the given constructor parameters +// Will only run a .ctor whose parameter types match the given arguments. +::std::optional New(Il2CppClass* klass, TArgs&&... args) { + static auto& logger = getLogger(); + il2cpp_functions::Init(); + + Il2CppObject* obj; + if constexpr (creationType == CreationType::Temporary) { + // object_new call + obj = RET_NULLOPT_UNLESS(logger, il2cpp_functions::object_new(klass)); + } else { + obj = RET_NULLOPT_UNLESS(logger, createManual(klass)); + } + // runtime_invoke constructor with right type(s) of arguments, return null if constructor errors + std::array types{ ::il2cpp_utils::ExtractType(args)... }; + auto* method = RET_NULLOPT_UNLESS(logger, FindMethod(klass, ".ctor", types)); + RET_NULLOPT_UNLESS(logger, RunMethodOpt(obj, method, args...)); + return FromIl2CppObject(obj); +} + +// TODO: Rename to New, rename existing New to NewObject or equivalent +/// @brief Allocates a new instance of a particular Il2CppClass*, either allowing it to be GC'd normally or manually controlled. +/// The Il2CppClass* is derived from the TOut template parameter. +/// The found constructor method will be cached. +/// Will throw either an il2cpp_utils::exceptions::StackTraceException or il2cpp_utils::RunMethodException if errors occur. +/// @tparam TOut The type to create. +/// @tparam creationType The way to create the instance. +/// @tparam TArgs The arguments to call the constructor with. +/// @param args The arguments to call the constructor with. +template +TOut NewSpecific(TArgs&&... args) { + static auto* klass = classof(TOut); + Il2CppObject* obj; + if constexpr (creationType == CreationType::Temporary) { + // object_new call + obj = il2cpp_functions::object_new(klass); + if (!obj) { + throw exceptions::StackTraceException("Failed to allocate new object via object_new!"); + } + } else { + obj = createManualThrow(klass); + } + // Only need to extract based off of types, since we are asusming our TOut is classof-able already + static auto ctorMethod = FindMethod(klass, ".ctor", std::array{ ExtractIndependentType>()... }); + if (!ctorMethod) { + throw exceptions::StackTraceException(string_format("Failed to find a matching .ctor method during construction of type: %s", ClassStandardName(klass).c_str())); + } + ::il2cpp_utils::RunMethodRethrow(obj, ctorMethod, args...); + if constexpr (std::is_pointer_v) { + return reinterpret_cast(obj); + } else if constexpr (has_il2cpp_conversion) { + // Handle construction for wrapper types, construct from void*s + return TOut(reinterpret_cast(obj)); + } else { + static_assert(false_t, "Cannot C# construct the provided value type that is not a wrapper type!"); } } +template +// Creates a new object of the given class using the given constructor parameters +// DOES NOT PERFORM ARGUMENT TYPE CHECKING! Uses the first .ctor with the right number of parameters it sees. +::std::optional NewUnsafe(const Il2CppClass* klass, TArgs&&... args) { + static auto& logger = getLogger(); + il2cpp_functions::Init(); + + Il2CppObject* obj; + if constexpr (creationType == CreationType::Temporary) { + // object_new call + obj = RET_NULLOPT_UNLESS(logger, il2cpp_functions::object_new(klass)); + } else { + obj = RET_NULLOPT_UNLESS(logger, createManual(klass)); + } + // runtime_invoke constructor with right number of args, return null if constructor errors + RET_NULLOPT_UNLESS(logger, RunMethodUnsafe(obj, ".ctor", args...)); + return FromIl2CppObject(obj); +} + +template +// Creates a new object of the returned type using the given constructor parameters +// Will only run a .ctor whose parameter types match the given arguments. +#ifndef BS_HOOK_USE_CONCEPTS +::std::enable_if_t<(... && ((!::std::is_same_v || !::std::is_same_v)&&!::std::is_convertible_v)), ::std::optional> +#else + requires(... && ((!::std::is_same_v || !::std::is_same_v)&&!::std::is_convertible_v))::std::optional +#endif +New(TArgs&&... args) { + static auto& logger = getLogger(); + auto* klass = RET_NULLOPT_UNLESS(logger, (NoArgClass())); + return New(klass, args...); +} + +template +// Creates a new object of the class with the given nameSpace and className using the given constructor parameters. +// Will only run a .ctor whose parameter types match the given arguments. +::std::optional New(::std::string_view nameSpace, ::std::string_view className, TArgs&&... args) { + static auto& logger = getLogger(); + auto* klass = RET_0_UNLESS(logger, GetClassFromName(nameSpace, className)); + return New(klass, args...); +} + +template +// Creates a new object of the class with the given nameSpace and className using the given constructor parameters. +// DOES NOT PERFORM ARGUMENT TYPE CHECKING! Uses the first .ctor with the right number of parameters it sees. +::std::optional NewUnsafe(::std::string_view nameSpace, ::std::string_view className, TArgs*... args) { + static auto& logger = getLogger(); + auto* klass = RET_0_UNLESS(logger, GetClassFromName(nameSpace, className)); + return NewUnsafe(klass, args...); +} +} // namespace il2cpp_utils + #pragma pack(pop) #endif diff --git a/src/tests/array-wrapper-tests.cpp b/src/tests/array-wrapper-tests.cpp index 4dc5ec80..085cb2b3 100644 --- a/src/tests/array-wrapper-tests.cpp +++ b/src/tests/array-wrapper-tests.cpp @@ -76,8 +76,8 @@ static void doThing() { static void doThing2() { ArrayW arr(2); MethodInfo info; - il2cpp_utils::RunMethodThrow(static_cast(nullptr), &info, arr); - il2cpp_utils::RunMethodThrow>(static_cast(nullptr), &info); + il2cpp_utils::RunMethodRethrow(static_cast(nullptr), &info, arr); + il2cpp_utils::RunMethodRethrow>(static_cast(nullptr), &info); il2cpp_utils::RunMethod>(static_cast(nullptr), &info); if (arr) { ArrayW x(static_cast>(arr)); diff --git a/src/tests/byref-tests.cpp b/src/tests/byref-tests.cpp index 6b85a518..7d6811af 100644 --- a/src/tests/byref-tests.cpp +++ b/src/tests/byref-tests.cpp @@ -1,3 +1,4 @@ +#include "utils/il2cpp-utils-methods.hpp" #ifdef TEST_BYREF #include "../../shared/utils/byref.hpp" #include "../../shared/utils/il2cpp-utils.hpp" @@ -13,9 +14,9 @@ static void test() { MethodInfo inf; int x = 3; il2cpp_utils::RunMethod((Il2CppClass*)nullptr, &inf, byref(x)); - il2cpp_utils::RunMethodThrow((Il2CppClass*)nullptr, &inf, byref(x)); - il2cpp_utils::RunMethodThrow(&x, &inf); - il2cpp_utils::RunStaticMethod(&inf, byref(x)); + il2cpp_utils::RunMethodRethrow((Il2CppClass*)nullptr, &inf, byref(x)); + il2cpp_utils::RunMethodRethrow(&x, &inf); + il2cpp_utils::RunMethod(nullptr, &inf, byref(x)); il2cpp_utils::ExtractIndependentType>(); il2cpp_utils::ExtractIndependentType(); const ByRef testit(x); diff --git a/src/tests/list-wrapper-tests.cpp b/src/tests/list-wrapper-tests.cpp index 70624701..41148bcb 100644 --- a/src/tests/list-wrapper-tests.cpp +++ b/src/tests/list-wrapper-tests.cpp @@ -17,7 +17,7 @@ static void constDoThing(const ListW& wrap) { static void doThing() { ListW arr(*il2cpp_utils::New*>(classof(List*))); - il2cpp_utils::RunMethodThrow(*reinterpret_cast**>(&arr), il2cpp_utils::FindMethod(arr, "Add"), 2); + il2cpp_utils::RunMethodRethrow(*reinterpret_cast**>(&arr), il2cpp_utils::FindMethod(arr, "Add"), 2); auto i = arr[0]; assert(arr.size() == 1); for (auto itr : arr) { @@ -50,8 +50,8 @@ static void doThing() { static void doThing2() { ListW arr(nullptr); MethodInfo info; - il2cpp_utils::RunMethodThrow(classof(Il2CppObject*), &info, arr); - il2cpp_utils::RunMethodThrow>(classof(Il2CppObject*), &info); + il2cpp_utils::RunMethodRethrow(classof(Il2CppObject*), &info, arr); + il2cpp_utils::RunMethodRethrow>(classof(Il2CppObject*), &info); } #endif diff --git a/src/tests/string-tests.cpp b/src/tests/string-tests.cpp index 9dd3f6b7..83a3afe4 100644 --- a/src/tests/string-tests.cpp +++ b/src/tests/string-tests.cpp @@ -5,7 +5,7 @@ static bool test1() { using namespace il2cpp_utils; static auto cs1 = newcsstr("test"); auto cs2 = newcsstr("test2"); - return CRASH_UNLESS(RunMethod(cs1, "Equals", cs2)); + return CRASH_UNLESS(RunMethodOpt(cs1, "Equals", cs2)); } #include "../../shared/utils/typedefs-string.hpp" diff --git a/src/utils/il2cpp-utils-exceptions.cpp b/src/utils/il2cpp-utils-exceptions.cpp index c0494900..381fdcb4 100644 --- a/src/utils/il2cpp-utils-exceptions.cpp +++ b/src/utils/il2cpp-utils-exceptions.cpp @@ -8,7 +8,7 @@ namespace il2cpp_utils { #define EXCEPTION_MESSAGE_SIZE 4096 #endif // Returns a legible string from an Il2CppException* - std::string ExceptionToString(Il2CppException* exp) noexcept { + std::string ExceptionToString(const Il2CppException* exp) noexcept { il2cpp_functions::Init(); char msg[EXCEPTION_MESSAGE_SIZE]; diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index aa607bf8..9d686346 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -102,7 +102,7 @@ namespace il2cpp_utils { } // Call instance function on infoObj to MakeGeneric // Does not need to perform type checking, since if this does not match, it will fail more miserably. - auto res = il2cpp_utils::RunMethod(infoObj, "MakeGenericMethod", arr); + auto res = il2cpp_utils::RunMethodOpt(infoObj, "MakeGenericMethod", arr); const auto* returnedInfoObj = RET_0_UNLESS(logger, res); if (!returnedInfoObj) { logger.error("Failed to get Il2CppReflectionMethod from MakeGenericMethod!"); diff --git a/src/utils/il2cpp-utils.cpp b/src/utils/il2cpp-utils.cpp index 5471ee82..9092d75b 100644 --- a/src/utils/il2cpp-utils.cpp +++ b/src/utils/il2cpp-utils.cpp @@ -5,6 +5,7 @@ #include "../../shared/utils/il2cpp-utils.hpp" #include "../../shared/utils/utils.h" #include "../../shared/utils/il2cpp-functions.hpp" +#include "utils/il2cpp-utils-methods.hpp" #include #include #include @@ -198,7 +199,7 @@ namespace il2cpp_utils { if (klass->has_cctor && !klass->cctor_finished_or_no_cctor && !klass->cctor_started) { obj->klass->cctor_started = true; auto* m = RET_0_UNLESS(logger, FindMethodUnsafe(klass, ".cctor", 0)); - RET_0_UNLESS(logger, il2cpp_utils::RunStaticMethodUnsafe(m)); + RET_0_UNLESS(logger, il2cpp_utils::RunMethodOpt(nullptr, m)); obj->klass->cctor_finished_or_no_cctor = true; } return obj; @@ -220,7 +221,7 @@ namespace il2cpp_utils { if (!m) { throw exceptions::StackTraceException("Failed to find .cctor method!"); } - if (!il2cpp_utils::RunStaticMethodUnsafe(m)) { + if (!il2cpp_utils::RunMethodOpt(nullptr, m)) { throw exceptions::StackTraceException("Failed to run .cctor method!"); } obj->klass->cctor_finished_or_no_cctor = true; diff --git a/src/utils/typedefs-wrapper.cpp b/src/utils/typedefs-wrapper.cpp index 258f94c7..86126a34 100644 --- a/src/utils/typedefs-wrapper.cpp +++ b/src/utils/typedefs-wrapper.cpp @@ -5,6 +5,7 @@ #include "../../shared/utils/typedefs-string.hpp" #include "../../shared/utils/typedefs-wrappers.hpp" #include "../../shared/utils/typedefs.h" +#include "utils/il2cpp-utils-methods.hpp" #include "utils/typedefs-string.hpp" std::unordered_map Counter::addrRefCount; @@ -64,7 +65,7 @@ Il2CppString* alloc_str(std::u16string_view str) { Il2CppString* CreateString(int length) { static MethodInfo const* methodInfo = il2cpp_utils::FindMethod(classof(Il2CppString*), "CreateString", std::array{ il2cpp_utils::ExtractIndependentType(), il2cpp_utils::ExtractIndependentType() }); - return CRASH_UNLESS(il2cpp_utils::RunStaticMethodUnsafe(methodInfo, Il2CppChar('\0'), length)); + return CRASH_UNLESS(il2cpp_utils::RunMethodOpt(nullptr, methodInfo, Il2CppChar('\0'), length)); } Il2CppString* strappend(Il2CppString const* lhs, Il2CppString const* rhs) noexcept { From 236b48a4e187402a1f6586b8bdf1d49e9b75ad32 Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:05:41 -0400 Subject: [PATCH 68/89] Use MethodResult --- shared/utils/il2cpp-utils-methods.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index 51d0089b..f5c59aaa 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -559,6 +559,9 @@ void LogMethod(LoggerContextObject& logger, const MethodInfo* method); // Calls LogMethod on all methods in the given class void LogMethods(LoggerContextObject& logger, Il2CppClass* klass, bool logParents = false); +template +using MethodResult = ::std::variant, RunMethodException>; + #pragma region Invokers // Experiment namespace invokers { @@ -566,7 +569,7 @@ namespace invokers { // TODO: Fix template -::std::variant, RunMethodException> FnPtrInvoker(T* instance, const MethodInfo* method, TArgs&&... params) noexcept { +MethodResult FnPtrInvoker(T* instance, const MethodInfo* method, TArgs&&... params) noexcept { bool isStatic = method->flags & METHOD_ATTRIBUTE_STATIC; if (isStatic && method->klass && !method->klass->cctor_finished_or_no_cctor) { il2cpp_functions::Class_Init(method->klass); @@ -642,7 +645,7 @@ ::std::variant, RunMethodException> FnPtrInvoker(T* instan } template -::std::variant, RunMethodException> Il2CppInvoker(Il2CppObject* obj, const MethodInfo* method, TArgs&&... params) noexcept { +MethodResult Il2CppInvoker(Il2CppObject* obj, const MethodInfo* method, TArgs&&... params) noexcept { il2cpp_functions::Init(); Il2CppException* exp = nullptr; std::array invokeParams{ ::il2cpp_utils::ExtractTypeValue(params)... }; @@ -683,8 +686,7 @@ ::std::variant, RunMethodException> Il2CppInvoker(Il2CppOb } // namespace invokers #pragma endregion -template -using MethodResult = ::std::variant, RunMethodException>; + template requires(!::std::is_convertible_v || std::is_same_v) From 0fe43bc3f98e659eb19d6789d33ad0e411203ce2 Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 10:37:11 -0400 Subject: [PATCH 69/89] Use Result for smaller footprint --- shared/utils/il2cpp-utils-methods.hpp | 20 ++- shared/utils/result.hpp | 182 ++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 shared/utils/result.hpp diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index f5c59aaa..c9d9f39b 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -16,6 +16,7 @@ #include "il2cpp-utils-boxing.hpp" #include "il2cpp-utils-classes.hpp" #include "il2cpp-utils-exceptions.hpp" +#include "result.hpp" #include "logging.hpp" #include "utils.h" @@ -52,10 +53,6 @@ enum struct CreationType { Manual }; -/// Converts void to std::monostate for variant -template -using TypeOrMonostate = std::conditional_t, std::monostate, T>; - /// @brief Manually creates an instance of the provided Il2CppClass*. /// The created instance's type initializer will NOT execute on another thread! Be warned! /// Must be freed using gc_free_specific! @@ -560,7 +557,7 @@ void LogMethod(LoggerContextObject& logger, const MethodInfo* method); void LogMethods(LoggerContextObject& logger, Il2CppClass* klass, bool logParents = false); template -using MethodResult = ::std::variant, RunMethodException>; +using MethodResult = ::il2cpp_utils::Result; #pragma region Invokers // Experiment @@ -822,11 +819,12 @@ template inline TOut RunMethodRethrow(TArgs&&... params) { auto result = ::il2cpp_utils::RunMethod(std::forward(params)...); - if (auto exception = std::get_if<::il2cpp_utils::RunMethodException>(&result)) { - throw exception; + // TODO: Move here? + if (auto exceptionOpt = result.as_optional_exception()) { + throw exceptionOpt.value(); } if constexpr (!std::is_same_v) { - return std::get(std::move(result)); + return result.move_result(); } } @@ -844,13 +842,13 @@ template inline std::optional> RunMethodOpt(TArgs&&... params) noexcept { auto result = ::il2cpp_utils::RunMethod(std::forward(params)...); - if (auto exception = std::get_if<::il2cpp_utils::RunMethodException>(&result)) { + if (auto const exception = result.as_optional_exception()) { static auto& logger = getLogger(); - logger.error("%s: Failed with exception: %s", il2cpp_functions::method_get_name(exception->info), il2cpp_utils::ExceptionToString(exception->ex).c_str()); + logger.error("%s: Failed with exception: %s", il2cpp_functions::method_get_name(exception.value()->info), il2cpp_utils::ExceptionToString(exception.value()->ex).c_str()); return std::nullopt; } - return std::get>(std::move(result)); + return result.move_result(); } diff --git a/shared/utils/result.hpp b/shared/utils/result.hpp new file mode 100644 index 00000000..bbbb71b2 --- /dev/null +++ b/shared/utils/result.hpp @@ -0,0 +1,182 @@ +#pragma once + +#include +#include +#include + +#include "il2cpp-utils-exceptions.hpp" + +namespace il2cpp_utils { + +namespace exceptions { +struct ResultException : il2cpp_utils::exceptions::StackTraceException { + ResultException(std::string_view msg) : il2cpp_utils::exceptions::StackTraceException(msg) {} +}; +} // namespace exceptions + +namespace { + +/// Converts void to std::monostate for variant +template +using TypeOrMonostate = std::conditional_t, std::monostate, T>; + +template + requires(!std::is_same_v) +struct Result { + using SuccessValue = TypeOrMonostate; + using ExceptionValue = E; + + Result() + requires(std::is_default_constructible_v || std::is_void_v) + : Result(SuccessValue()) {} + + Result(Result&&) noexcept = default; + Result(Result const&) noexcept = default; + + Result(SuccessValue&& result) noexcept + + : success(true), result(std::forward(result)) {} + + Result(SuccessValue const& result) noexcept : success(true), result(std::forward(result)) {} + + /// exception + Result(E&& exception) noexcept : success(false), exception(std::forward(exception)) {} + Result(E const& exception) noexcept : success(false), exception(std::forward(exception)) {} + + ~Result() { + if (success) { + this->result.~SuccessValue(); + + } else { + this->exception.~ExceptionValue(); + } + } + + [[nodiscard]] inline bool has_result() const noexcept { + return success; + } + [[nodiscard]] inline bool has_exception() const noexcept { + return !success; + } + + [[nodiscard]] constexpr SuccessValue const& get_result() const { + if (!success) throw il2cpp_utils::exceptions::ResultException("Result does not contain a success result!"); + + return result; + } + + [[nodiscard]] constexpr SuccessValue& get_result() { + if (!success) throw il2cpp_utils::exceptions::ResultException("Result does not contain a success result!"); + + return result; + } + + /// move result value out of this wrapper + [[nodiscard]] constexpr SuccessValue move_result() { + if (!success) throw il2cpp_utils::exceptions::ResultException("Result does not contain a success result!"); + + return std::move(result); + } + + [[nodiscard]] constexpr ExceptionValue const& get_exception() const { + if (success) throw il2cpp_utils::exceptions::ResultException("Result does not contain an exception result!"); + + return exception; + } + [[nodiscard]] constexpr ExceptionValue& get_exception() { + if (success) throw il2cpp_utils::exceptions::ResultException("Result does not contain an exception result!"); + + return exception; + } + + /// move result value out of this wrapper + [[nodiscard]] constexpr ExceptionValue move_exception() { + if (success) throw il2cpp_utils::exceptions::ResultException("Result does not contain an exception result!"); + + return std::move(exception); + } + + /// Moves this result into a variant + /// if T is void, returns std::monostate + [[nodiscard]] constexpr std::variant into_variant() + { + if (success) { + return this->move_result(); + } + + return this->move_exception(); + } + +#pragma region Optional Result + /// Moves this result into an optional + /// if T is void, returns std::monostate + [[nodiscard]] constexpr std::optional into_optional_result() + { + if (success) { + return this->move_result(); + } + + return std::nullopt; + } + + /// Moves this result into an optional + /// if T is void, returns std::monostate + [[nodiscard]] constexpr std::optional as_optional_result() + { + if (success) { + return &this->get_result(); + } + + return std::nullopt; + } + /// Moves this result into an optional + /// if T is void, returns std::monostate + [[nodiscard]] constexpr std::optional as_optional_result() const + { + if (success) { + return &this->get_result(); + } + + return std::nullopt; + } +#pragma endregion + +#pragma region Optional Exception + /// Moves this result into an optional + /// if T is void, returns std::monostate + [[nodiscard]] constexpr std::optional into_optional_exception() { + if (!success) { + return this->move_exception(); + } + + return std::nullopt; + } + + /// Wraps this exception into an optional + [[nodiscard]] constexpr std::optional as_optional_exception() { + if (!success) { + return &this->get_exception(); + } + + return std::nullopt; + } + /// Wraps this exception into an optional + [[nodiscard]] constexpr std::optional as_optional_exception() const { + if (!success) { + return &this->get_exception(); + } + + return std::nullopt; + } +#pragma endregion + + private: + bool success; + + union { + SuccessValue result; + ExceptionValue exception; + }; +}; +} // namespace +} // namespace il2cpp_utils \ No newline at end of file From 53eba9f1276c7fb003d5fa7f531efac5e22ba29f Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 10:40:36 -0400 Subject: [PATCH 70/89] Remove clangd auto import and cleanup --- shared/utils/il2cpp-utils-methods.hpp | 1 - shared/utils/result.hpp | 16 +++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index c9d9f39b..39e6dd21 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -4,7 +4,6 @@ #include #include #include -#include "utils/il2cpp-utils-exceptions.hpp" #pragma pack(push) #include diff --git a/shared/utils/result.hpp b/shared/utils/result.hpp index bbbb71b2..48ba141e 100644 --- a/shared/utils/result.hpp +++ b/shared/utils/result.hpp @@ -33,9 +33,7 @@ struct Result { Result(Result&&) noexcept = default; Result(Result const&) noexcept = default; - Result(SuccessValue&& result) noexcept - - : success(true), result(std::forward(result)) {} + Result(SuccessValue&& result) noexcept : success(true), result(std::forward(result)) {} Result(SuccessValue const& result) noexcept : success(true), result(std::forward(result)) {} @@ -98,8 +96,7 @@ struct Result { /// Moves this result into a variant /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::variant into_variant() - { + [[nodiscard]] constexpr std::variant into_variant() { if (success) { return this->move_result(); } @@ -110,8 +107,7 @@ struct Result { #pragma region Optional Result /// Moves this result into an optional /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::optional into_optional_result() - { + [[nodiscard]] constexpr std::optional into_optional_result() { if (success) { return this->move_result(); } @@ -121,8 +117,7 @@ struct Result { /// Moves this result into an optional /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::optional as_optional_result() - { + [[nodiscard]] constexpr std::optional as_optional_result() { if (success) { return &this->get_result(); } @@ -131,8 +126,7 @@ struct Result { } /// Moves this result into an optional /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::optional as_optional_result() const - { + [[nodiscard]] constexpr std::optional as_optional_result() const { if (success) { return &this->get_result(); } From ae0b6318ce33a5c9192c016680b4f8f82f2f39b3 Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 10:58:10 -0400 Subject: [PATCH 71/89] Fix default return if method info is null --- shared/utils/il2cpp-utils-methods.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index 39e6dd21..c7d448f9 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -690,7 +690,10 @@ template RunMethod(T&& wrappedInstance, const MethodInfo* method, TArgs&&... params) noexcept { static auto& logger = getLogger(); - RET_NULLOPT_UNLESS(logger, method); + + if (!method) { + return RunMethodException("MethodInfo cannot be null!", nullptr); + } if constexpr (checkTypes) { // only check args if TArgs is > 0 From 1f5c1dd4c2125da8beab4527f791d85e103a4d54 Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 16:12:02 -0400 Subject: [PATCH 72/89] Log ambigious methods --- src/utils/il2cpp-utils-methods.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 9d686346..c27a0c6b 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -422,7 +422,16 @@ namespace il2cpp_utils { // if no perfect match, look for lowest weighted if (!target) { + logger.warning("Found multiple methods that match for %s.%s", ClassStandardName(klass).c_str(), info.name.data()); + for (auto const& [method, weight] : weightMap) { + logger.warning("Weight %lu Method %s", weight, method->name); + LogMethod(logger, method); + } + target = std::min_element(weightMap.begin(), weightMap.end(), [](auto a, auto b) { return a.second < b.second; })->first; + logger.warning("Using the following:"); + CRASH_UNLESS(target); + LogMethod(logger, target); } } } From 157e94f395d5f442038e71af486c42229470ebdd Mon Sep 17 00:00:00 2001 From: FernTheDev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 16:53:30 -0400 Subject: [PATCH 73/89] Avoid unnecessary move --- shared/utils/il2cpp-utils-methods.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index c7d448f9..1a277cc6 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -821,12 +821,11 @@ template inline TOut RunMethodRethrow(TArgs&&... params) { auto result = ::il2cpp_utils::RunMethod(std::forward(params)...); - // TODO: Move here? if (auto exceptionOpt = result.as_optional_exception()) { throw exceptionOpt.value(); } if constexpr (!std::is_same_v) { - return result.move_result(); + return result.get_result(); } } @@ -850,7 +849,7 @@ inline std::optional> RunMethodOpt(TArgs&&... params) noex return std::nullopt; } - return result.move_result(); + return result.get_result(); } From 2741d59b44fb00b90fd10dd632c939c7aafb9df5 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 21:41:10 -0400 Subject: [PATCH 74/89] Fix error message --- shared/utils/il2cpp-utils-methods.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index 1a277cc6..e5aee69a 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -723,7 +723,7 @@ MethodResult RunMethod(T&& wrappedInstance, const MethodInfo* method, TArg auto isStatic = method->flags & METHOD_ATTRIBUTE_STATIC; if (!isStatic && !inst) { - return RunMethodException("Method is static but instance is null!", method); + return RunMethodException("Method is instance but instance is null!", method); } // Experiment From 45fde58db4c475e58c7346e9531a883547486c14 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Sun, 21 Jan 2024 22:39:25 -0400 Subject: [PATCH 75/89] Cache make generic method methodInfo --- src/utils/il2cpp-utils-methods.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index c27a0c6b..46a1caf7 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -102,7 +102,11 @@ namespace il2cpp_utils { } // Call instance function on infoObj to MakeGeneric // Does not need to perform type checking, since if this does not match, it will fail more miserably. - auto res = il2cpp_utils::RunMethodOpt(infoObj, "MakeGenericMethod", arr); + static auto const* MakeGenericMethodInfo = ::il2cpp_utils::FindMethod(infoObj, + "MakeGenericMethod", + std::array{ il2cpp_utils::ExtractType(arr) } + ); + auto res = il2cpp_utils::RunMethodOpt(infoObj, MakeGenericMethodInfo, arr); const auto* returnedInfoObj = RET_0_UNLESS(logger, res); if (!returnedInfoObj) { logger.error("Failed to get Il2CppReflectionMethod from MakeGenericMethod!"); From 7cebb7638d6ba957404eb86ee348d0bde8bcb96d Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Mon, 22 Jan 2024 06:49:30 -0400 Subject: [PATCH 76/89] Add more utility methods to Result --- shared/utils/result.hpp | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/shared/utils/result.hpp b/shared/utils/result.hpp index 48ba141e..6490a439 100644 --- a/shared/utils/result.hpp +++ b/shared/utils/result.hpp @@ -94,9 +94,33 @@ struct Result { return std::move(exception); } + /// Gets the current success result or rethrows if an exception + [[nodiscard]] constexpr SuccessValue const& get_or_rethrow() const { + if (!success) { + throw this->get_exception(); + } + + return this->get_result(); + } + /// Gets the current success result or rethrows if an exception + [[nodiscard]] constexpr SuccessValue& get_or_rethrow() { + if (!success) { + throw this->get_exception(); + } + + return this->get_result(); + } + /// Gets the current success result or rethrows if an exception + constexpr void rethrow() const { + if (success) { + return; + } + throw this->get_exception(); + } + /// Moves this result into a variant /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::variant into_variant() { + [[nodiscard]] constexpr std::variant into_variant() noexcept { if (success) { return this->move_result(); } @@ -107,7 +131,7 @@ struct Result { #pragma region Optional Result /// Moves this result into an optional /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::optional into_optional_result() { + [[nodiscard]] constexpr std::optional into_optional_result() noexcept { if (success) { return this->move_result(); } @@ -117,7 +141,7 @@ struct Result { /// Moves this result into an optional /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::optional as_optional_result() { + [[nodiscard]] constexpr std::optional as_optional_result() noexcept { if (success) { return &this->get_result(); } @@ -126,7 +150,7 @@ struct Result { } /// Moves this result into an optional /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::optional as_optional_result() const { + [[nodiscard]] constexpr std::optional as_optional_result() const noexcept { if (success) { return &this->get_result(); } @@ -138,7 +162,7 @@ struct Result { #pragma region Optional Exception /// Moves this result into an optional /// if T is void, returns std::monostate - [[nodiscard]] constexpr std::optional into_optional_exception() { + [[nodiscard]] constexpr std::optional into_optional_exception() noexcept { if (!success) { return this->move_exception(); } @@ -147,7 +171,7 @@ struct Result { } /// Wraps this exception into an optional - [[nodiscard]] constexpr std::optional as_optional_exception() { + [[nodiscard]] constexpr std::optional as_optional_exception() noexcept { if (!success) { return &this->get_exception(); } @@ -155,7 +179,7 @@ struct Result { return std::nullopt; } /// Wraps this exception into an optional - [[nodiscard]] constexpr std::optional as_optional_exception() const { + [[nodiscard]] constexpr std::optional as_optional_exception() const noexcept { if (!success) { return &this->get_exception(); } From 8042164461cf7e97cd6d92f26ce0475f2434916b Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:19:27 -0400 Subject: [PATCH 77/89] Initialize if needed --- src/utils/il2cpp-utils-methods.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 46a1caf7..1b5a32ef 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -322,6 +322,11 @@ namespace il2cpp_utils { } } + // initialize klass if needed + if (!klass->initialized_and_no_error) { + il2cpp_functions::Class_Init(klass); + } + // Ok we look through all the methods that have the following: // - matches name // - parameters match as defined in ::il2cpp_utils::ParametersMatch From 050ca85da09af9c10974309ba9b6e30d52ab9e37 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:21:27 -0400 Subject: [PATCH 78/89] Fix VAR substitution in FindMethod --- src/utils/il2cpp-utils-methods.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 1b5a32ef..4b400389 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -280,7 +280,9 @@ namespace il2cpp_utils { // return getTypeClass(genType, genClass, methodInfo); // } if (t->type == IL2CPP_TYPE_VAR) { - genContainer = reinterpret_cast(methodInfo); + // idk which is more correct, yolo + // genContainer = reinterpret_cast(methodInfo->klass->genericContainerHandle); + genContainer = il2cpp_utils::GetGenericContainer(methodInfo); genericInst = methodInfo->genericMethod->context.method_inst; } if (t->type == IL2CPP_TYPE_MVAR) { From f16eed26e7b1fdd64d119251c3667130caa79d0a Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:23:45 -0400 Subject: [PATCH 79/89] Add weight to interfaces --- src/utils/il2cpp-utils-methods.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 4b400389..7c7b292c 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -405,6 +405,12 @@ namespace il2cpp_utils { auto distance = 0; + if (methodParamClass->flags & TYPE_ATTRIBUTE_INTERFACE) { + // if interface, just add lots of weight + // so we choose a concrete type instead + distance += 100; + } + if (!methodParamClass || !expectedParamClass) { distance = 1; } else { From f16e13adabf320ab67fef0415a7c81021cc113f1 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:24:28 -0400 Subject: [PATCH 80/89] Take interfaces into account for weight --- src/utils/il2cpp-utils-methods.cpp | 90 +++++++++++++++++++----------- 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 7c7b292c..9dd9f2cd 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -6,6 +6,7 @@ #include "utils/il2cpp-utils-methods.hpp" #include "utils/utils.h" #include +#include #include #include #include @@ -303,6 +304,59 @@ namespace il2cpp_utils { return nullptr; } + std::int64_t calculateWeightOfParam(Il2CppClass* methodParamClass, Il2CppClass* expectedParamClass) { + if (!methodParamClass || !expectedParamClass) { + return 1; + } + + std::span methodInterfaces = { methodParamClass->implementedInterfaces, methodParamClass->interfaces_count }; + + std::int64_t distance = 0; + + if (methodParamClass == expectedParamClass) { + return distance; + } + + bool isMethodInterface = methodParamClass->flags & TYPE_ATTRIBUTE_INTERFACE; + bool isExpectedInterface = expectedParamClass->flags & TYPE_ATTRIBUTE_INTERFACE; + + // method is an interface, we expect a concrete type + // avoid + if (isExpectedInterface && !isMethodInterface) { + return 1000; + } + + if (isMethodInterface) { + // if interface, just add lots of weight + // so we choose a concrete type instead + distance += 100; + } + + std::span expectedInterfaces = { expectedParamClass->implementedInterfaces, expectedParamClass->interfaces_count }; + + // find all interfaces which intersect with our expected type + std::vector interfaceIntersections; + interfaceIntersections.reserve(expectedInterfaces.size()); + std::set_intersection(expectedInterfaces.begin(), expectedInterfaces.end(), methodInterfaces.begin(), methodInterfaces.end(), std::back_inserter(interfaceIntersections)); + std::size_t interfaceSharing = interfaceIntersections.size(); + + while (expectedParamClass && expectedParamClass != methodParamClass) { + if (!il2cpp_functions::class_is_assignable_from(methodParamClass, expectedParamClass)) { + break; + } + + expectedParamClass = expectedParamClass->parent; + distance++; + } + + // subtract distance by specifity of interface + // since it allows specifity + distance -= interfaceSharing; + + + return distance; + } + #if __has_feature(cxx_exceptions) const MethodInfo* FindMethod(FindMethodInfo& info) #else @@ -386,13 +440,13 @@ namespace il2cpp_utils { // multiple methods // time to method overload resolution if (!target) { - std::vector> weightMap; + std::vector> weightMap; weightMap.reserve(matches.size()); // iterate methods for (auto const& method : matches) { // overload resolution - std::size_t weight = 0; + std::int64_t weight = 0; // weigh the methods based on their distance to the expected // parameters @@ -401,37 +455,9 @@ namespace il2cpp_utils { auto const& methodParamType = method->parameters[i]; auto const methodParamClass = getTypeClass(methodParamType, method); - auto expectedParamClass = getTypeClass(expectedParamType, method); - - auto distance = 0; - - if (methodParamClass->flags & TYPE_ATTRIBUTE_INTERFACE) { - // if interface, just add lots of weight - // so we choose a concrete type instead - distance += 100; - } - - if (!methodParamClass || !expectedParamClass) { - distance = 1; - } else { - while (expectedParamClass && expectedParamClass != methodParamClass) { - if (!il2cpp_functions::class_is_assignable_from(methodParamClass, expectedParamClass)) { - break; - } - - expectedParamClass = expectedParamClass->parent; - distance++; - } - } - - weight += distance; - } + auto const expectedParamClass = getTypeClass(expectedParamType, method); - // found a method that matches perfectly - // return now! - if (weight == 0) { - target = method; - break; + weight += calculateWeightOfParam(methodParamClass, expectedParamClass); } weightMap.emplace_back(method, weight); From da0b0b29fab486d570ad5926982ad8fc041f8c91 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:45:38 -0400 Subject: [PATCH 81/89] Improve method resolution performance and search for parents properly --- shared/utils/il2cpp-utils-methods.hpp | 2 +- src/utils/il2cpp-utils-methods.cpp | 68 +++++++++++++-------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/shared/utils/il2cpp-utils-methods.hpp b/shared/utils/il2cpp-utils-methods.hpp index e5aee69a..94cd789b 100644 --- a/shared/utils/il2cpp-utils-methods.hpp +++ b/shared/utils/il2cpp-utils-methods.hpp @@ -218,7 +218,7 @@ const MethodInfo* FindMethodUnsafe(::std::string_view nameSpace, ::std::string_v /// Attempts to look for a method that best matches given the FindMethodInfo data /// if no method is found, returns null /// Look at il2cpp-utils-methods.cpp for more details on how this resolution takes place -const MethodInfo* FindMethod(FindMethodInfo& info); +const MethodInfo* FindMethod(FindMethodInfo const& info); #pragma region FindMethod class /// helper constructor diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 9dd9f2cd..163900e6 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -358,9 +358,9 @@ namespace il2cpp_utils { } #if __has_feature(cxx_exceptions) - const MethodInfo* FindMethod(FindMethodInfo& info) + const MethodInfo* FindMethod(FindMethodInfo const& info) #else - const MethodInfo* FindMethod(FindMethodInfo& info) noexcept + const MethodInfo* FindMethod(FindMethodInfo const& info) noexcept #endif { static auto logger = getLogger().WithContext("FindMethod"); @@ -403,37 +403,46 @@ namespace il2cpp_utils { const MethodInfo* target = nullptr; - // Does NOT automatically recurse through klass's parents - for (std::size_t i = 0; i < info.klass->method_count; i++) { - auto current = info.klass->methods[i]; + auto addMethodsToMatches = [&](Il2CppClass const* targetKlass) { + // Does NOT automatically recurse through klass's parents + auto const methodsSpan = std::span(targetKlass->methods, targetKlass->method_count); + for (auto const& current : methodsSpan) { + if (info.name != current->name) { + logger.debug("Method name does not match for method %s", current->name); + continue; + } - if (info.name != current->name) { - logger.debug("Method name does not match for method %s", current->name); - continue; - } + // strict equal + bool isPerfect; + if (!ParameterMatch(current, std::span(info.genTypes), std::span(info.argTypes), &isPerfect)) { + logger.debug("Parameters do not match for method %s", current->name); + continue; + } - // strict equal - bool isPerfect; - if (!ParameterMatch(current, std::span(info.genTypes), std::span(info.argTypes), &isPerfect)) { - logger.debug("Parameters do not match for method %s", current->name); - continue; - } + // if true, perfect match + if (isPerfect) { + target = current; + break; + } - // if true, perfect match - if (isPerfect) { - target = current; - break; - } + auto methodParams = std::span(current->parameters, current->parameters_count); - auto methodParams = std::span(current->parameters, current->parameters_count); + matches.push_back(current); + } + }; - matches.push_back(current); + // now look for matches recursively + auto targetKlass = info.klass; + // if we reached no parent or perfect target is found, we're done + while (targetKlass != nullptr && target == nullptr) { + addMethodsToMatches(targetKlass); + targetKlass = targetKlass->parent; } - // Look for method in matches - if (!matches.empty()) { + // Method overload resolution + if (!target && !matches.empty()) { // Only one method found, use it - if (!target && matches.size() == 1) { + if (matches.size() == 1) { target = matches.front(); } @@ -479,15 +488,6 @@ namespace il2cpp_utils { } } - // Use parent's methods to look for the appropiate method - if (!target && klass->parent && klass->parent != klass) { - logger.debug("Method does not exist in %s, looking at parent %s", ClassStandardName(klass).c_str(), ClassStandardName(klass->parent).c_str()); - - info.klass = klass->parent; - target = FindMethod(info); - info.klass = klass; - } - // add to cache { std::unique_lock lock(classTypesMethodsLock); From 92673cf95095ebde8d214bd87d0442557e877309 Mon Sep 17 00:00:00 2001 From: Fernthedev <15272073+Fernthedev@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:01:44 -0400 Subject: [PATCH 82/89] reduce weight for interfaces --- src/utils/il2cpp-utils-methods.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/il2cpp-utils-methods.cpp b/src/utils/il2cpp-utils-methods.cpp index 163900e6..acab3be4 100644 --- a/src/utils/il2cpp-utils-methods.cpp +++ b/src/utils/il2cpp-utils-methods.cpp @@ -329,7 +329,7 @@ namespace il2cpp_utils { if (isMethodInterface) { // if interface, just add lots of weight // so we choose a concrete type instead - distance += 100; + distance += 5; } std::span expectedInterfaces = { expectedParamClass->implementedInterfaces, expectedParamClass->interfaces_count }; From 38e152d2df25979095bcbbb03065e8b224c3d2e7 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 23 Jan 2024 13:49:06 +0100 Subject: [PATCH 83/89] Fix SetWriteBarrier call --- shared/utils/typedefs-array.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/utils/typedefs-array.hpp b/shared/utils/typedefs-array.hpp index ef2a9455..ba0d1e0e 100644 --- a/shared/utils/typedefs-array.hpp +++ b/shared/utils/typedefs-array.hpp @@ -408,7 +408,7 @@ struct ArrayW { array->val->values[idx] = static_cast(v); // writes on ref types should happen with wbarrier if constexpr (il2cpp_utils::il2cpp_reference_type) { - il2cpp_functions::GarbageCollector_SetWriteBarrier(array->val->values + idx); + il2cpp_functions::GarbageCollector_SetWriteBarrier(reinterpret_cast(array->val->values + idx)); } return array->val->values[idx]; } From 62bce347a6df214e7af208589876edfaa0e696a6 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 23 Jan 2024 13:57:01 +0100 Subject: [PATCH 84/89] Fix workflows --- .github/workflows/build-ndk.yml | 2 +- .github/workflows/publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ndk.yml b/.github/workflows/build-ndk.yml index 024fca31..0786516b 100644 --- a/.github/workflows/build-ndk.yml +++ b/.github/workflows/build-ndk.yml @@ -32,7 +32,7 @@ jobs: - name: Create ndkpath.txt run: | - echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt + echo ${{ steps.setup-ndk.outputs.path }} > ${GITHUB_WORKSPACE}/ndkpath.txt cat ${GITHUB_WORKSPACE}/ndkpath.txt - name: QPM Rust Action diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 14313d22..ec1f1f5c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: - name: Create ndkpath.txt run: | - echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt + echo ${{ steps.setup-ndk.outputs.path }} > ${GITHUB_WORKSPACE}/ndkpath.txt cat ${GITHUB_WORKSPACE}/ndkpath.txt - name: Get Tag Version From 1d3061e990dd2b94bcf7e10cb00c1f5def5c9fc2 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 23 Jan 2024 14:00:04 +0100 Subject: [PATCH 85/89] Update version getting --- .github/workflows/publish.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ec1f1f5c..7b95acb3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -31,12 +31,11 @@ jobs: echo ${{ steps.setup-ndk.outputs.path }} > ${GITHUB_WORKSPACE}/ndkpath.txt cat ${GITHUB_WORKSPACE}/ndkpath.txt - - name: Get Tag Version - id: get_tag_version + - name: Extract version + id: version run: | - echo ${GITHUB_REF#refs/tags/} - echo ::set-output name=TAG::${GITHUB_REF#refs/tags/} - echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} + echo "TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_OUTPUT} + echo "VERSION=${GITHUB_REF#refs/tags/v}" >> ${GITHUB_OUTPUT} - name: QPM Rust Action uses: Fernthedev/qpm-rust-action@main @@ -50,8 +49,8 @@ jobs: publish: true publish_token: ${{secrets.PUBLISH_KEY}} - version: ${{ steps.get_tag_version.outputs.VERSION }} - tag: ${{ steps.get_tag_version.outputs.TAG }} + version: ${{ steps.version.outputs.VERSION }} + tag: ${{ steps.version.outputs.TAG }} # set to true if applicable, ASSUMES the file is already a release asset qpm_release_bin: true From b3bd485265076223a7ee271184a5c4f74d42639e Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 23 Jan 2024 14:12:23 +0100 Subject: [PATCH 86/89] Fix ndkpath output --- .github/workflows/build-ndk.yml | 2 +- .github/workflows/publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ndk.yml b/.github/workflows/build-ndk.yml index 0786516b..7787e0e1 100644 --- a/.github/workflows/build-ndk.yml +++ b/.github/workflows/build-ndk.yml @@ -32,7 +32,7 @@ jobs: - name: Create ndkpath.txt run: | - echo ${{ steps.setup-ndk.outputs.path }} > ${GITHUB_WORKSPACE}/ndkpath.txt + echo ${{ steps.setup-ndk.outputs.ndk-path }} > ${GITHUB_WORKSPACE}/ndkpath.txt cat ${GITHUB_WORKSPACE}/ndkpath.txt - name: QPM Rust Action diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7b95acb3..485af7dc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: - name: Create ndkpath.txt run: | - echo ${{ steps.setup-ndk.outputs.path }} > ${GITHUB_WORKSPACE}/ndkpath.txt + echo ${{ steps.setup-ndk.outputs.ndk-path }} > ${GITHUB_WORKSPACE}/ndkpath.txt cat ${GITHUB_WORKSPACE}/ndkpath.txt - name: Extract version From e560980c8b14f61e4f76a9843d6ba4d47835dbd2 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 23 Jan 2024 14:26:04 +0100 Subject: [PATCH 87/89] Fix publish to reuse release --- .github/workflows/publish.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 485af7dc..df7a2921 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -73,8 +73,6 @@ jobs: id: upload_file_release uses: softprops/action-gh-release@v0.1.15 with: - name: ${{ github.event.inputs.release_msg }} - tag_name: ${{ github.event.inputs.version }} files: | ./build/${{ steps.libname.outputs.NAME }} ./build/debug/${{ steps.libname.outputs.NAME }} From 4b24d26aa34a8f4590908867c208d49c7592524e Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 23 Jan 2024 14:30:17 +0100 Subject: [PATCH 88/89] Fix publish to reuse release --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index df7a2921..2dfc89fe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -73,6 +73,7 @@ jobs: id: upload_file_release uses: softprops/action-gh-release@v0.1.15 with: + tag_name: ${{ steps.version.outputs.TAG }} files: | ./build/${{ steps.libname.outputs.NAME }} ./build/debug/${{ steps.libname.outputs.NAME }} From 62e8f6fac6a7e6846e5513c3e033bce237cd7d52 Mon Sep 17 00:00:00 2001 From: RedBrumbler Date: Tue, 23 Jan 2024 14:45:23 +0100 Subject: [PATCH 89/89] Fix publish uploading debug asset --- .github/workflows/publish.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2dfc89fe..7937dc14 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -67,15 +67,16 @@ jobs: cd ./build/ pattern="lib${module_id}*.so" files=( $pattern ) - echo ::set-output name=NAME::"${files[0]}" + echo "NAME=${files[0]}" >> ${GITHUB_OUTPUT} + - name: Rename debug lib + run: mv ./build/debug/${{ steps.libname.outputs.NAME }} ./build/debug_${{ steps.libname.outputs.NAME }} - name: Upload to Release - id: upload_file_release uses: softprops/action-gh-release@v0.1.15 with: tag_name: ${{ steps.version.outputs.TAG }} files: | ./build/${{ steps.libname.outputs.NAME }} - ./build/debug/${{ steps.libname.outputs.NAME }} + ./build/debug_${{ steps.libname.outputs.NAME }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}