diff --git a/.clang-format b/.clang-format index 1e59dced..f0ce6af9 100644 --- a/.clang-format +++ b/.clang-format @@ -1,13 +1,63 @@ +Language: Cpp BasedOnStyle: Google -AllowShortBlocksOnASingleLine: false -AllowShortFunctionsOnASingleLine: Empty -AllowShortIfStatementsOnASingleLine: true -ColumnLimit: 200 -CommentPragmas: NOLINT:.* +AccessModifierOffset: -1 +AlignAfterOpenBracket: BlockIndent +AlignOperands: AlignAfterOperator +AlignTrailingComments: + Kind: Never +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakAfterAttributes: Never +BreakBeforeConceptDeclarations: Always +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: false +ColumnLimit: 150 DerivePointerAlignment: false -IncludeBlocks: Preserve +FixNamespaceComments: false +IncludeBlocks: Regroup +IncludeCategories: + - Regex: "^<[^.>]+>$" + Priority: 2 + SortPriority: 3 + CaseSensitive: false + - Regex: '^<.+\.(h|hpp)>$' + Priority: 2 + SortPriority: 4 + CaseSensitive: false + - Regex: '^"[A-Z].*\/.+\.hpp"$' + Priority: 5 + CaseSensitive: true + - Regex: "^.+/shared/.+" + Priority: 1 + SortPriority: 2 + CaseSensitive: true + - Regex: ".*" + Priority: 1 + SortPriority: 1 + CaseSensitive: false +IndentExternBlock: Indent +IndentRequiresClause: false IndentWidth: 4 -PointerAlignment: Left -TabWidth: 4 -UseTab: Never -Cpp11BracedListStyle: false \ No newline at end of file +InsertBraces: true +InsertNewlineAtEOF: true +KeepEmptyLinesAtTheStartOfBlocks: true +LineEnding: LF +NamespaceIndentation: All +PackConstructorInitializers: CurrentLine +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 50 +QualifierAlignment: Right +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SkipMacroDefinitionBody: true +SpaceAfterCStyleCast: true +SpacesBeforeTrailingComments: 2 diff --git a/.clangd b/.clangd index 9e1f4440..c06631a4 100644 --- a/.clangd +++ b/.clangd @@ -1,6 +1,9 @@ +Diagnostics: + UnusedIncludes: None +--- 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 + Background: Skip diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c57fd5d..fd966408 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,154 +1,82 @@ -# include some defines automatically made by qpm +cmake_minimum_required(VERSION 3.22) + +# Include definitions generated by QPM on restore include(qpm_defines.cmake) -cmake_minimum_required(VERSION 3.22) project(${COMPILE_ID}) -# c++ standard -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED 20) -# LTO -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +# C++ standard +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED 23) + +# Provide compile commands so they can be used for intellisense set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) -# define that stores the actual source directory -set(SHARED_DIR ${CMAKE_CURRENT_SOURCE_DIR}/shared) -set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) -set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) +# Set compiler options +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +add_compile_options(-frtti -fexceptions -O3) +add_compile_options(-Wall -Wextra -Werror -Wno-extra-qualification -Wno-vla-cxx-extension) -# compile options used -add_compile_options(-frtti -fPIE -fPIC -fexceptions -flto) -add_link_options(-Wl,--exclude-libs,ALL) -add_compile_options(-Wall -Wextra -Werror -Wno-unused-function -Wno-extra-qualification) # clangd bug https://github.com/clangd/clangd/issues/1167 add_compile_options(-Wno-pragma-pack) -add_compile_options(-Wno-vla-cxx-extension) -# compile definitions used -add_compile_definitions(VERSION=\"${MOD_VERSION}\") -add_compile_definitions(MOD_ID=\"${MOD_ID}\") +# add_compile_options(-Wno-vla-cxx-extension) +# Expose definitions from qpm_defines.cmake +add_compile_definitions(VERSION="${MOD_VERSION}") +add_compile_definitions(MOD_ID="${MOD_ID}") + add_compile_definitions(VERSION_NUMBER=300150000) add_compile_definitions(UNITY_6) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# Trim source folder in logcat messages +string(LENGTH "${CMAKE_CURRENT_SOURCE_DIR}/" FOLDER_LENGTH) +add_compile_definitions(PAPER_ROOT_FOLDER_LENGTH=${FOLDER_LENGTH}) -function(setup_target target add_test) -message(STATUS "Setting up target ${target}") - -string(LENGTH "${CMAKE_CURRENT_LIST_DIR}" FOLDER_LENGTH) -add_compile_definitions("PAPER_ROOT_FOLDER_LENGTH=(${FOLDER_LENGTH} + 1)") +# Define the code directories +set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) +set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) -add_library( - ${target} - SHARED - ) - - if (add_test) - target_compile_definitions( - ${target} - PRIVATE - - TEST_CALLBACKS - TEST_BETTER_SPAN - TEST_SAFEPTR - TEST_BYREF - TEST_ARRAY - TEST_LIST - TEST_STRING - TEST_HOOK - TEST_THREAD - TEST_UNITYW - ) - 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}) - - # recursively get all src files - RECURSE_FILES(cpp_file_list_hook ${SHARED_DIR}/inline-hook/*.cpp) - RECURSE_FILES(c_file_list_hook ${SHARED_DIR}/inline-hook/*.c) - target_sources(${target} PRIVATE ${cpp_file_list_hook}) - target_sources(${target} PRIVATE ${c_file_list_hook}) - - 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_include_directories(${target} SYSTEM PRIVATE ${EXTERN_DIR}/includes/libil2cpp/il2cpp/external/baselib/Include) - target_include_directories(${target} SYSTEM PRIVATE ${EXTERN_DIR}/includes/libil2cpp/il2cpp/external/baselib/Platforms/Android/Include) - - target_include_directories(${target} SYSTEM PRIVATE ${EXTERN_DIR}/includes/fmt/fmt/include/) - target_compile_options(${target} PRIVATE -DFMT_HEADER_ONLY) - - 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} +function(shared_setup TARGET) + # Add include directories to compilation + target_include_directories(${TARGET} PUBLIC ${SHARED_DIR}) + + # Link necessary Android libraries + target_link_libraries(${TARGET} PRIVATE -llog) + + # include extern.cmake with the correct compile id + set(OLD_COMPILE_ID ${COMPILE_ID}) + set(COMPILE_ID ${TARGET}) + include(extern.cmake) + set(COMPILE_ID ${OLD_COMPILE_ID}) + + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "debug" + COMMAND ${CMAKE_COMMAND} -E rename "lib${TARGET}.so" "debug/lib${TARGET}.so" + COMMAND ${CMAKE_STRIP} -d --strip-all "debug/lib${TARGET}.so" -o "lib${TARGET}.so" + COMMENT "Create stripped and debug libraries" ) - - 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(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 - NO_TEST -) - -# 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 - "lib${COMPILE_ID}.so" -o "stripped_lib${COMPILE_ID}.so" - COMMENT "Strip debug symbols done on final binary.") - -add_custom_command(TARGET ${COMPILE_ID} POST_BUILD - 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" - ) - -add_custom_command(TARGET ${COMPILE_ID} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E rename stripped_lib${COMPILE_ID}.so lib${COMPILE_ID}.so - COMMENT "Rename the stripped lib to regular" - ) +# Non-recursively get all source files +FILE(GLOB cpp_file_list ${SOURCE_DIR}/*.cpp) +FILE(GLOB c_file_list ${SOURCE_DIR}/*.c) + +# Add all source files to compilation +add_library(${COMPILE_ID} SHARED ${cpp_file_list} ${c_file_list}) + +shared_setup(${COMPILE_ID}) + +# Create test target +set(TEST_ID test-${COMPILE_ID}) + +# Recursively get all source files +recurse_files(cpp_test_list ${SOURCE_DIR}/tests/*.cpp) +recurse_files(c_test_list ${SOURCE_DIR}/tests/*.c) + +add_library(${TEST_ID} SHARED ${cpp_test_list} ${c_test_list}) + +shared_setup(${TEST_ID}) + +add_dependencies(${TEST_ID} ${COMPILE_ID}) +target_link_libraries(${TEST_ID} PRIVATE ${COMPILE_ID}) diff --git a/README.md b/README.md index dce10d5e..f14b7cb4 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,69 @@ -# Beat Saber Quest (il2cpp) modloader/function Hooking Framework +# Beat Saber Quest (il2cpp) C++ modding framework _known by literally no one as pineappl_ ## Information -This library offers a wide realm of C++11 to C++20 features for modding il2cpp games on Android devices (specifically meant for the Oculus Quest 1 and 2). +This library offers a wide realm of C++20 features for modding il2cpp games on Android devices (specifically meant for the Oculus Quest 1/2/3). Do not be put off by the name: `beatsaber-hook`, this is entirely game agnostic. -The only dependency it requires is a way of determining the application ID, this is currently done by calling a modloader exposed function, from: [my proxy libmain modloader](https://github.com/sc2ad/QuestLoader/tree/staticModloader) +The only dependencies it requires are a way of determining the application ID, currently done by calling a modloader exposed function from: [scotland2](https://github.com/sc2ad/scotland2), a [logging library](https://github.com/Fernthedev/paperlog), and a [hooking api](https://github.com/sc2ad/Flamingo). -It also requires that the game is of a unity version >= 2018.0 and < 2020.x. +It also requires that the game is of a unity version >= 6000. Some other versions are theoretically supported with compile defines, but are currently not tested. -It is also MUCH more useful if il2cpp functionality is not stripped, ex: exports from the `libil2cpp.so` match most of the symbol names here (with an `il2cpp_` prefix): [shared/utils/il2cpp-functions.hpp](https://github.com/sc2ad/beatsaber-hook/blob/master/shared/utils/il2cpp-functions.hpp#L89). -This will provide a good deal of functionality, and some degree of these functions will be resolved by xref tracing to find non-exported symbols for use in other cases. +It is also required that il2cpp functionality is not stripped, ex: exports from the `libil2cpp.so` match most of the symbol names here (with an `il2cpp_` prefix): [shared/api.hpp](./shared/api.hpp#L30). ## High Level Functionality This framework provides a good deal of functionality, including, but not limited to, the following: -- `il2cpp_utils` API, for accessing the exported API in a more ergonomic and type safe manner. -- Opt-in runtime type checking -- Detailed exceptions, opt-in-able -- Helpers for installing inline hooks (build within each mod: `And64InlineHook.hpp`) -- Helpers for parsing many ARM64 instructions (`capstone-utils.hpp`) -- Macros for hook installation + trampoline allocation, type safety conversions -- Performant context logging (including to file) -- Interop with [the modloader](https://github.com/sc2ad/QuestLoader/tree/staticModloader) +- `i2c` API, for accessing the exported API in a more ergonomic and type safe manner +- Runtime type checking +- Detailed exceptions with backtraces +- Method hooking +- Helpers for parsing many ARM64 instructions (`capstone.hpp`) +- Interop with [the modloader](https://github.com/sc2ad/scotland2) - Exposal of many non-exported il2cpp API functions -- And many other things that I forgot while writing this list +- Wrappers for commonly used types +- [Rapidjson](https://github.com/Tencent/rapidjson) ## Usage -`beatsaber-hook` is what I consider to be a _library_. Thus, it does not do anything on ELF load, instead only when functions are called from mods does it do anything. +`beatsaber-hook` is what I consider to be a _library_. Thus, it does not do anything on ELF load, instead only when functions are called from mods does it do anything. (This is technically not quite true, as it does initialize capstone, but nothing else.) The most important thing to note is timing: **Most beatsaber-hook functions require `il2cpp_init` to be called!** -This means that if you have a modloader that performs things _before_ that call (as mine does), you will need to delay calling any of these functions (`il2cpp_utils` or `il2cpp_functions`) until that point. +This means that if you have a modloader that performs things _before_ that call (as mine does), you will need to delay calling any of these functions (in `i2c`) until that point. Typically, the workflow for a mod involves hooking into some existing game functionality on `load` (called after `il2cpp_init`) and initializing the bs-hook API, which will cache most of its accesses. -This is done simply by calling `il2cpp_functions::Init()`, or by calling any `il2cpp_utils` API function that uses `il2cpp_functions` internally. +This is done simply by calling `i2c::functions::initialize()`, or by calling any `i2c` API function that uses `i2c::functions` internally. ## Building + Contributing -All contributions are welcome, [license available here](https://github.com/sc2ad/beatsaber-hook/blob/master/LICENSE) +All contributions are welcome, [license available here](./LICENSE). -In order to build, you require the 64/32 bit version of the modloader that you need to link against, along with its headers. - -This can be done either by building/downloading a version of [my modloader](https://github.com/sc2ad/QuestLoader/tree/staticModloader) or by using [qpm](https://github.com/sc2ad/QuestPackageManager) and performing a `qpm restore` - -When building, you can use the `build.ps1` script, but you must first create an `ndkpath.txt` with your path to NDK. You should have at least NDK r23, as Clang 12 features are required. +In order to build, the recommended way is to use [QPM](https://github.com/QuestPackageManager/QPM.CLI). Run `qpm restore` and `qpm qmod zip` to create `beatsaber-hook.qmod`, which can be installed by any quest mod installer. This library now also statically links against [capstone](https://github.com/aquynh/capstone) in order to perform _better_ instruction parsing. This CAN be removed, but note that many features will be broken or otherwise damaged. -There are a bunch of defines that can control the built code a bit, TODO add them all here as a list. (for now just read the locations of `#ifdef` or `#ifndef`) - -## Running tests - -The repository contains a test shared library built during the normal build process: `build/libtest-beatsaber-hook.so`. - -Copy the built `.so` into the scotland2 early mods folder inside the repo for development testing (create the folder if it doesn't exist) e.g - `adb push build/libtest-beatsaber-hook.so /libtest-beatsaber-hook.so` +It also includes [rapidjson](https://github.com/Tencent/rapidjson). -Example device paths you might need to adapt: -- `/sdcard/ModData//Modloader/early_mods/libtest-beatsaber-hook.so` +There are a few defines that can control the built code: -Verifying the test library loads +- `UNITY_2019`/`UNITY_2021`/`UNITY_6`: Sets the target unity version. Exactly one should be defined; currently set in `CMakeLists.txt`. +- `HAS_CODEGEN`: Assumes a codegen-like library is present that provides headers for all C# types. +- `BS_HOOK_MATCH_UNSAFE`: Disables checks for method size and existence in `MAKE_HOOK_MATCH`. +- `SUPPRESS_MACRO_LOGS`: Disables a number of primarily error log messages. +- `PERSISTENT_DIR`: Override the base directory for mod data storage. +- `CONFIG_PATH_FORMAT`: Override the directory for mod config files. +- `NO_EVENT_CALLBACK_INVOKE_SAFETY`: Disables safety for events being added to a `basic_event_callback` during an invoke. +- `EXCEPTION_MESSAGE_SIZE`: Maximum size for il2cpp exception message formatting. -- Start the game / modloader on the device after the `.so` is in the mods folder. -- Use `adb logcat` to watch logs and confirm the test library or the beatsaber-hook initialization messages appear. A useful pattern is: - - - `adb logcat -c` - - `adb logcat -v time | grep -i "libtest-beatsaber-hook\|beatsaber-hook\|scotland2"` +## Running tests -Look for log lines emitted by the test library (it will usually print a startup message) or for messages from the modloader indicating the `.so` was loaded. +Tests are automatically compiled but not run. To run the tests, create and install the qmod by running `qpm qmod zip --import .\mod.tests.json test-beatsaber-hook`, then inspect the log. Note that tests failing will not necessarily cause the game to crash, and in fact rarely should. ## Acknowledgements diff --git a/make-tests.ps1 b/make-tests.ps1 deleted file mode 100644 index f8bc44de..00000000 --- a/make-tests.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -Move-Item ./mod.template.json mod.template.main.json -Move-Item ./mod.template.tests.json ./mod.template.json - -# generate zip to use as test lib qmod -qpm qmod zip --exclude_libs libbeatsaber-hook.so - -# create mod.json with only regular bshook as dependency -if($LASTEXITCODE -eq 0) { - qpm qmod manifest --exclude_libs libbeatsaber-hook.so - - $modJson = Get-Content ./mod.json -Raw | ConvertFrom-Json - $id = $modJson.id - $version = $modJson.version - $modJson.id = "test-$id" - $modJson.dependencies = @(@{"id" = "$id"; "version" = "^$version"}) - $modJson | ConvertTo-Json | Out-File ./mod.json - - # replace mod.json with modified version - Compress-Archive -Path mod.json -Update -DestinationPath "$id.qmod" - Move-Item "$id.qmod" "$id-tests.qmod" -Force - Remove-Item mod.json -} - -Move-Item ./mod.template.json mod.template.tests.json -Move-Item ./mod.template.main.json ./mod.template.json - -# generate regular bshook zip -if($LASTEXITCODE -eq 0) { - qpm qmod zip -} \ No newline at end of file diff --git a/mod.template.tests.json b/mod.tests.json similarity index 72% rename from mod.template.tests.json rename to mod.tests.json index 9f9c696a..a44cc152 100644 --- a/mod.template.tests.json +++ b/mod.tests.json @@ -1,9 +1,9 @@ { "_QPVersion": "0.1.1", - "name": "${mod_name} (Tests)", - "id": "${mod_id}", + "name": "BSHook Tests", + "id": "test-beatsaber-hook", "author": "sc2ad", - "version": "${version}", + "version": "0.1.0", "description": "Tests modding utilities for unity il2cpp games", "modloader": "Scotland2", "dependencies": [], diff --git a/qpm.shared.json b/qpm.shared.json index 8a5d6a26..770ae338 100644 --- a/qpm.shared.json +++ b/qpm.shared.json @@ -158,15 +158,15 @@ { "dependency": { "id": "scotland2", - "versionRange": "=0.1.6", + "versionRange": "=0.1.7", "additionalData": { - "soLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/libsl2.so", - "debugSoLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/debug_libsl2.so", + "soLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.7/libsl2.so", + "debugSoLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.7/debug_libsl2.so", "overrideSoName": "libsl2.so", - "branchName": "version/v0_1_6" + "branchName": "version/v0_1_7" } }, - "version": "0.1.6" + "version": "0.1.7" } ] } \ No newline at end of file diff --git a/shared/utils/alphanum.hpp b/shared/alphanum.hpp similarity index 100% rename from shared/utils/alphanum.hpp rename to shared/alphanum.hpp diff --git a/shared/api.hpp b/shared/api.hpp new file mode 100644 index 00000000..0d28792a --- /dev/null +++ b/shared/api.hpp @@ -0,0 +1,451 @@ +#pragma once + +#include "exceptions.hpp" +#include "utils.hpp" + +#pragma pack(push) + +#include "il2cpp-api-types.h" +#include "il2cpp-class-internals.h" +#include "il2cpp-metadata.h" +#include "il2cpp-object-internals.h" +#include "il2cpp-tabledefs.h" +#include "vm/GlobalMetadataFileInternals.h" + +#include +#include +#include +#include + +typedef std::vector Il2CppAssemblyVector; + +#define API_FUNC(rt, name, ...) \ + extern rt (*name) __VA_ARGS__; + +// A (once) class which contains all available il2cpp functions +// Created by zoller27osu +namespace i2c::functions { +// These methods autogenerated by Sc2ad: +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(int, init, (char const* domain_name)); + API_FUNC(int, init_utf16, (Il2CppChar const* domain_name)); +#else + API_FUNC(void, init, (char const* domain_name)); + API_FUNC(void, init_utf16, (Il2CppChar const* domain_name)); +#endif + API_FUNC(void, shutdown, ()); + API_FUNC(void, set_config_dir, (char const* config_path)); + API_FUNC(void, set_data_dir, (char const* data_path)); + API_FUNC(void, set_temp_dir, (char const* temp_path)); + API_FUNC(void, set_commandline_arguments, (int argc, char const* const argv[], char const* basedir)); + API_FUNC(void, set_commandline_arguments_utf16, (int argc, Il2CppChar const* const argv[], char const* basedir)); + API_FUNC(void, set_config_utf16, (Il2CppChar const* executablePath)); + API_FUNC(void, set_config, (char const* executablePath)); + API_FUNC(void, set_memory_callbacks, (Il2CppMemoryCallbacks * callbacks)); + API_FUNC(Il2CppImage const*, get_corlib, ()); + API_FUNC(void, add_internal_call, (char const* name, Il2CppMethodPointer method)); + API_FUNC(Il2CppMethodPointer, resolve_icall, (char const* name)); + API_FUNC(void*, alloc, (size_t size)); + API_FUNC(void, free, (void* ptr)); + API_FUNC(Il2CppClass*, array_class_get, (Il2CppClass * element_class, uint32_t rank)); + API_FUNC(uint32_t, array_length, (Il2CppArray * array)); + API_FUNC(uint32_t, array_get_byte_length, (Il2CppArray * array)); + API_FUNC(Il2CppArray*, array_new, (Il2CppClass * elementTypeInfo, il2cpp_array_size_t length)); + API_FUNC(Il2CppArray*, array_new_specific, (Il2CppClass * arrayTypeInfo, il2cpp_array_size_t length)); + API_FUNC(Il2CppArray*, array_new_full, (Il2CppClass * array_class, il2cpp_array_size_t* lengths, il2cpp_array_size_t* lower_bounds)); + API_FUNC(Il2CppClass*, bounded_array_class_get, (Il2CppClass * element_class, uint32_t rank, bool bounded)); + API_FUNC(int, array_element_size, (Il2CppClass const* array_class)); + API_FUNC(Il2CppImage const*, assembly_get_image, (Il2CppAssembly const* assembly)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(void, class_for_each, (void (*klassReportFunc)(Il2CppClass* klass, void* userData), void* userData)); +#endif + API_FUNC(Il2CppType const*, class_enum_basetype, (Il2CppClass * klass)); + API_FUNC(bool, class_is_generic, (Il2CppClass const* klass)); + API_FUNC(bool, class_is_inflated, (Il2CppClass const* klass)); + API_FUNC(bool, class_is_assignable_from, (Il2CppClass * klass, Il2CppClass* oklass)); + API_FUNC(bool, class_is_subclass_of, (Il2CppClass * klass, Il2CppClass* klassc, bool check_interfaces)); + API_FUNC(bool, class_has_parent, (Il2CppClass * klass, Il2CppClass* klassc)); + API_FUNC(Il2CppClass*, class_from_il2cpp_type, (Il2CppType const* type)); + API_FUNC(Il2CppClass*, class_from_name, (Il2CppImage const* image, char const* namespaze, char const* name)); + API_FUNC(Il2CppClass*, class_from_system_type, (Il2CppReflectionType * type)); + API_FUNC(Il2CppClass*, class_get_element_class, (Il2CppClass * klass)); + API_FUNC(EventInfo const*, class_get_events, (Il2CppClass * klass, void** iter)); + API_FUNC(FieldInfo*, class_get_fields, (Il2CppClass * klass, void** iter)); + API_FUNC(Il2CppClass*, class_get_nested_types, (Il2CppClass * klass, void** iter)); + API_FUNC(Il2CppClass*, class_get_interfaces, (Il2CppClass * klass, void** iter)); + API_FUNC(PropertyInfo const*, class_get_properties, (Il2CppClass * klass, void** iter)); + API_FUNC(PropertyInfo const*, class_get_property_from_name, (Il2CppClass * klass, char const* name)); + API_FUNC(FieldInfo*, class_get_field_from_name, (Il2CppClass * klass, char const* name)); + API_FUNC(MethodInfo const*, class_get_methods, (Il2CppClass * klass, void** iter)); + API_FUNC(MethodInfo const*, class_get_method_from_name, (Il2CppClass const* klass, char const* name, int argsCount)); + API_FUNC(char const*, class_get_name, (Il2CppClass const* klass)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(void, type_get_name_chunked, (Il2CppType const* type, void (*chunkReportFunc)(void* data, void* userData), void* userData)); +#endif + API_FUNC(char const*, class_get_namespace, (Il2CppClass const* klass)); + API_FUNC(Il2CppClass*, class_get_parent, (Il2CppClass * klass)); + API_FUNC(Il2CppClass*, class_get_declaring_type, (Il2CppClass const* klass)); + API_FUNC(int32_t, class_instance_size, (Il2CppClass * klass)); + API_FUNC(size_t, class_num_fields, (Il2CppClass const* enumKlass)); + API_FUNC(bool, class_is_valuetype, (Il2CppClass const* klass)); + API_FUNC(int32_t, class_value_size, (Il2CppClass * klass, uint32_t* align)); + API_FUNC(bool, class_is_blittable, (Il2CppClass const* klass)); + API_FUNC(int, class_get_flags, (Il2CppClass const* klass)); + API_FUNC(bool, class_is_abstract, (Il2CppClass const* klass)); + API_FUNC(bool, class_is_interface, (Il2CppClass const* klass)); + API_FUNC(int, class_array_element_size, (Il2CppClass const* klass)); + API_FUNC(Il2CppClass*, class_from_type, (Il2CppType const* type)); + API_FUNC(Il2CppType const*, class_get_type, (Il2CppClass * klass)); + API_FUNC(uint32_t, class_get_type_token, (Il2CppClass * klass)); + API_FUNC(bool, class_has_attribute, (Il2CppClass * klass, Il2CppClass* attr_class)); + API_FUNC(bool, class_has_references, (Il2CppClass * klass)); + API_FUNC(bool, class_is_enum, (Il2CppClass const* klass)); + API_FUNC(Il2CppImage const*, class_get_image, (Il2CppClass * klass)); + API_FUNC(char const*, class_get_assemblyname, (Il2CppClass const* klass)); + API_FUNC(int, class_get_rank, (Il2CppClass const* klass)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(uint32_t, class_get_data_size, (Il2CppClass const* klass)); + API_FUNC(void*, class_get_static_field_data, (Il2CppClass const* klass)); +#endif +#if defined(UNITY_2019) || defined(UNITY_2021) + API_FUNC(size_t, class_get_bitmap_size, (Il2CppClass const* klass)); + API_FUNC(void, class_get_bitmap, (Il2CppClass * klass, size_t* bitmap)); +#endif + API_FUNC(bool, stats_dump_to_file, (char const* path)); + API_FUNC(uint64_t, stats_get_value, (Il2CppStat stat)); + API_FUNC(Il2CppDomain*, domain_get, ()); + API_FUNC(Il2CppAssembly const*, domain_assembly_open, (Il2CppDomain * domain, char const* name)); + API_FUNC(Il2CppAssembly const**, domain_get_assemblies, (Il2CppDomain const* domain, size_t* size)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(void, raise_exception, (Il2CppException*) ); +#endif + API_FUNC(Il2CppException*, exception_from_name_msg, (Il2CppImage const* image, char const* name_space, char const* name, char const* msg)); + API_FUNC(Il2CppException*, get_exception_argument_null, (char const* arg)); + API_FUNC(void, format_exception, (Il2CppException const* ex, char* message, int message_size)); + API_FUNC(void, format_stack_trace, (Il2CppException const* ex, char* output, int output_size)); + API_FUNC(void, unhandled_exception, (Il2CppException*) ); + API_FUNC(int, field_get_flags, (FieldInfo * field)); + API_FUNC(char const*, field_get_name, (FieldInfo * field)); + API_FUNC(Il2CppClass*, field_get_parent, (FieldInfo * field)); + API_FUNC(size_t, field_get_offset, (FieldInfo * field)); + API_FUNC(Il2CppType const*, field_get_type, (FieldInfo * field)); + API_FUNC(void, field_get_value, (Il2CppObject * obj, FieldInfo* field, void* value)); + API_FUNC(Il2CppObject*, field_get_value_object, (FieldInfo * field, Il2CppObject* obj)); + API_FUNC(bool, field_has_attribute, (FieldInfo * field, Il2CppClass* attr_class)); + API_FUNC(void, field_set_value, (Il2CppObject * obj, FieldInfo* field, void* value)); + 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)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(bool, field_is_literal, (FieldInfo * field)); +#endif + API_FUNC(void, gc_collect, (int maxGenerations)); + API_FUNC(int32_t, gc_collect_a_little, ()); + API_FUNC(void, gc_disable, ()); + API_FUNC(void, gc_enable, ()); + API_FUNC(bool, gc_is_disabled, ()); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + 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, ()); +#endif + 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)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + 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**))); + 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 + API_FUNC(uint32_t, gchandle_new, (Il2CppObject * obj, bool pinned)); + 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)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + 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, ()); + API_FUNC(uint32_t, offset_of_array_length_in_array_object_header, ()); + API_FUNC(uint32_t, offset_of_array_bounds_in_array_object_header, ()); + API_FUNC(uint32_t, allocation_granularity, ()); +#endif +#if defined(UNITY_2021) || defined(UNITY_6) + API_FUNC( + void*, + 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, unity_liveness_finalize, (void* state)); + API_FUNC(void, unity_liveness_free_struct, (void* state)); +#else + 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 + API_FUNC(void, unity_liveness_calculation_from_root, (Il2CppObject * root, void* state)); + API_FUNC(void, unity_liveness_calculation_from_statics, (void* state)); + API_FUNC(Il2CppType const*, method_get_return_type, (MethodInfo const* method)); + API_FUNC(Il2CppClass*, method_get_declaring_type, (MethodInfo const* method)); + API_FUNC(char const*, method_get_name, (MethodInfo const* method)); + API_FUNC(MethodInfo const*, method_get_from_reflection, (Il2CppReflectionMethod const* method)); + API_FUNC(Il2CppReflectionMethod*, method_get_object, (MethodInfo const* method, Il2CppClass* refclass)); + API_FUNC(bool, method_is_generic, (MethodInfo const* method)); + API_FUNC(bool, method_is_inflated, (MethodInfo const* method)); + API_FUNC(bool, method_is_instance, (MethodInfo const* method)); + API_FUNC(uint32_t, method_get_param_count, (MethodInfo const* method)); + API_FUNC(Il2CppType const*, method_get_param, (MethodInfo const* method, uint32_t index)); + API_FUNC(Il2CppClass*, method_get_class, (MethodInfo const* method)); + API_FUNC(bool, method_has_attribute, (MethodInfo const* method, Il2CppClass* attr_class)); + API_FUNC(uint32_t, method_get_flags, (MethodInfo const* method, uint32_t* iflags)); + API_FUNC(uint32_t, method_get_token, (MethodInfo const* method)); + API_FUNC(char const*, method_get_param_name, (MethodInfo const* method, uint32_t index)); + + // ONLY IF THE PROFILER EXISTS FOR UNITY_2019 + API_FUNC(void, profiler_install, (Il2CppProfiler * prof, Il2CppProfileFunc shutdown_callback)); + API_FUNC(void, profiler_set_events, (Il2CppProfileFlags events)); + API_FUNC(void, profiler_install_enter_leave, (Il2CppProfileMethodFunc enter, Il2CppProfileMethodFunc fleave)); + API_FUNC(void, profiler_install_allocation, (Il2CppProfileAllocFunc callback)); + API_FUNC(void, profiler_install_gc, (Il2CppProfileGCFunc callback, Il2CppProfileGCResizeFunc heap_resize_callback)); + API_FUNC(void, profiler_install_fileio, (Il2CppProfileFileIOFunc callback)); + API_FUNC(void, profiler_install_thread, (Il2CppProfileThreadFunc start, Il2CppProfileThreadFunc end)); + + API_FUNC(uint32_t, property_get_flags, (PropertyInfo const* prop)); + API_FUNC(MethodInfo const*, property_get_get_method, (PropertyInfo const* prop)); + API_FUNC(MethodInfo const*, property_get_set_method, (PropertyInfo const* prop)); + API_FUNC(char const*, property_get_name, (PropertyInfo const* prop)); + API_FUNC(Il2CppClass*, property_get_parent, (PropertyInfo const* prop)); + API_FUNC(Il2CppClass*, object_get_class, (Il2CppObject * obj)); + API_FUNC(uint32_t, object_get_size, (Il2CppObject * obj)); + API_FUNC(MethodInfo const*, object_get_virtual_method, (Il2CppObject * obj, MethodInfo const* method)); + API_FUNC(Il2CppObject*, object_new, (Il2CppClass const* klass)); + // Always returns (void*, (obj + 1) + API_FUNC(void*, object_unbox, (Il2CppObject * obj)); + // If klass is not a ValueType, returns (Il2CppObject*, (*data), else boxes + API_FUNC(Il2CppObject*, value_box, (Il2CppClass * klass, void* data)); + API_FUNC(void, monitor_enter, (Il2CppObject * obj)); + API_FUNC(bool, monitor_try_enter, (Il2CppObject * obj, uint32_t timeout)); + API_FUNC(void, monitor_exit, (Il2CppObject * obj)); + API_FUNC(void, monitor_pulse, (Il2CppObject * obj)); + API_FUNC(void, monitor_pulse_all, (Il2CppObject * obj)); + API_FUNC(void, monitor_wait, (Il2CppObject * obj)); + API_FUNC(bool, monitor_try_wait, (Il2CppObject * obj, uint32_t timeout)); + API_FUNC(Il2CppObject*, runtime_invoke, (MethodInfo const* method, void* obj, void** params, Il2CppException** exc)); + API_FUNC( + Il2CppObject*, + runtime_invoke_convert_args, + (MethodInfo const* method, void* obj, Il2CppObject** params, int paramCount, Il2CppException** exc) + ); + API_FUNC(void, runtime_class_init, (Il2CppClass * klass)); + API_FUNC(void, runtime_object_init, (Il2CppObject * obj)); + API_FUNC(void, runtime_object_init_exception, (Il2CppObject * obj, Il2CppException** exc)); + API_FUNC(void, runtime_unhandled_exception_policy_set, (Il2CppRuntimeUnhandledExceptionPolicy value)); + API_FUNC(int32_t, string_length, (Il2CppString * str)); + API_FUNC(Il2CppChar*, string_chars, (Il2CppString * str)); + API_FUNC(Il2CppString*, string_new, (char const* str)); + API_FUNC(Il2CppString*, string_new_len, (char const* str, uint32_t length)); + API_FUNC(Il2CppString*, string_new_utf16, (Il2CppChar const* text, int32_t len)); + API_FUNC(Il2CppString*, string_new_wrapper, (char const* str)); + API_FUNC(Il2CppString*, string_intern, (Il2CppString * str)); + API_FUNC(Il2CppString*, string_is_interned, (Il2CppString * str)); + API_FUNC(Il2CppThread*, thread_current, ()); + API_FUNC(Il2CppThread*, thread_attach, (Il2CppDomain * domain)); + API_FUNC(void, thread_detach, (Il2CppThread * thread)); +#if defined(UNITY_2019) || defined(UNITY_2021) + API_FUNC(Il2CppThread**, thread_get_all_attached_threads, (size_t* size)); +#endif + API_FUNC(bool, is_vm_thread, (Il2CppThread * thread)); + API_FUNC(void, current_thread_walk_frame_stack, (Il2CppFrameWalkFunc func, void* user_data)); + API_FUNC(void, thread_walk_frame_stack, (Il2CppThread * thread, Il2CppFrameWalkFunc func, void* user_data)); + API_FUNC(bool, current_thread_get_top_frame, (Il2CppStackFrameInfo * frame)); + API_FUNC(bool, thread_get_top_frame, (Il2CppThread * thread, Il2CppStackFrameInfo* frame)); + API_FUNC(bool, current_thread_get_frame_at, (int32_t offset, Il2CppStackFrameInfo* frame)); + 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)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(void, override_stack_backtrace, (Il2CppBacktraceFunc stackBacktraceFunc)); +#endif + API_FUNC(Il2CppObject*, type_get_object, (Il2CppType const* type)); + API_FUNC(int, type_get_type, (Il2CppType const* type)); + API_FUNC(Il2CppClass*, type_get_class_or_element_class, (Il2CppType const* type)); + API_FUNC(char*, type_get_name, (Il2CppType const* type)); + API_FUNC(bool, type_is_byref, (Il2CppType const* type)); + API_FUNC(uint32_t, type_get_attrs, (Il2CppType const* type)); + API_FUNC(bool, type_equals, (Il2CppType const* type, Il2CppType const* otherType)); + API_FUNC(char*, type_get_assembly_qualified_name, (Il2CppType const* type)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(bool, type_is_static, (Il2CppType const* type)); + API_FUNC(bool, type_is_pointer_type, (Il2CppType const* type)); +#endif + API_FUNC(Il2CppAssembly const*, image_get_assembly, (Il2CppImage const* image)); + API_FUNC(char const*, image_get_name, (Il2CppImage const* image)); + API_FUNC(char const*, image_get_filename, (Il2CppImage const* image)); + API_FUNC(MethodInfo const*, image_get_entry_point, (Il2CppImage const* image)); + API_FUNC(size_t, image_get_class_count, (Il2CppImage const* image)); + API_FUNC(Il2CppClass const*, image_get_class, (Il2CppImage const* image, size_t index)); + API_FUNC(Il2CppManagedMemorySnapshot*, capture_memory_snapshot, ()); + API_FUNC(void, free_captured_memory_snapshot, (Il2CppManagedMemorySnapshot * snapshot)); + API_FUNC(void, set_find_plugin_callback, (Il2CppSetFindPlugInCallback method)); + API_FUNC(void, register_log_callback, (Il2CppLogCallback method)); + API_FUNC(void, debugger_set_agent_options, (char const* options)); + API_FUNC(bool, is_debugger_attached, ()); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(void, register_debugger_agent_transport, (Il2CppDebuggerTransport * debuggerTransport)); + API_FUNC(bool, debug_get_method_info, (MethodInfo const*, Il2CppMethodDebugInfo* methodDebugInfo)); +#endif + API_FUNC(void, unity_install_unitytls_interface, (void const* unitytlsInterfaceStruct)); + API_FUNC(Il2CppCustomAttrInfo*, custom_attrs_from_class, (Il2CppClass * klass)); + API_FUNC(Il2CppCustomAttrInfo*, custom_attrs_from_method, (MethodInfo const* method)); + API_FUNC(Il2CppObject*, custom_attrs_get_attr, (Il2CppCustomAttrInfo * ainfo, Il2CppClass* attr_klass)); + 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)); +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(void, class_set_userdata, (Il2CppClass * klass, void* userdata)); + API_FUNC(int, class_get_userdata_offset, ()); +#endif + + // MANUALLY DEFINED CONST DEFINITIONS + API_FUNC(Il2CppType const*, class_get_type_const, (Il2CppClass const* klass)); + API_FUNC(char const*, class_get_name_const, (Il2CppClass const* klass)); + API_FUNC(Il2CppClass*, type_get_class, (Il2CppType * type)); + + // SELECT NON-API LIBIL2CPP FUNCTIONS: + API_FUNC(bool, Class_Init, (Il2CppClass * klass)); + + API_FUNC(Il2CppClass*, MetadataCache_GetTypeInfoFromHandle, (Il2CppMetadataTypeHandle index)); + API_FUNC(Il2CppClass*, MetadataCache_GetTypeInfoFromTypeIndex, (TypeIndex index)); + + API_FUNC(Il2CppClass*, GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex, (TypeDefinitionIndex index)); + API_FUNC(Il2CppClass*, GlobalMetadata_GetTypeInfoFromHandle, (Il2CppMetadataTypeHandle index)); + +#if defined(UNITY_2019) || defined(UNITY_2021) || defined(UNITY_6) + API_FUNC(std::string, _Type_GetName_, (Il2CppType const* type, Il2CppTypeNameFormat format)); +#else + API_FUNC(gnu_string, _Type_GetName_, (Il2CppType const* type, Il2CppTypeNameFormat format)); +#endif + API_FUNC(void, GC_free, (void* addr)); + + API_FUNC(void, GarbageCollector_SetWriteBarrier, (void** ptr)); + API_FUNC(void*, GarbageCollector_AllocateFixed, (size_t sz, void* descr)); + + API_FUNC(Il2CppClass*, Class_FromIl2CppType, (Il2CppType * typ)); + API_FUNC(Il2CppClass*, Class_GetPtrClass, (Il2CppClass * elementClass)); + API_FUNC(Il2CppClass*, GenericClass_GetClass, (Il2CppGenericClass * gclass)); + API_FUNC(Il2CppClass*, GenericClass_CreateClass, (Il2CppGenericClass * gclass, bool throwOnError)); +#if defined(UNITY_2019) || defined(UNITY_2021) + API_FUNC(Il2CppAssemblyVector*, Assembly_GetAllAssemblies, ()); +#endif + extern bool has_gc_funcs; + + // You must i2c::functions::free the char* when you are done with it + extern char* Type_GetName(Il2CppType const* type, Il2CppTypeNameFormat format); + extern void const* s_GlobalMetadata; + extern Il2CppGlobalMetadataHeader const* s_GlobalMetadataHeader; + extern Il2CppMetadataRegistration const* s_Il2CppMetadataRegistration; + + extern Il2CppDefaults const* defaults; + extern void find_il2cpp_defaults(Paper::LoggerContext const& logger); + +#if !defined(UNITY_2019) && !defined(UNITY_2021) + extern Il2CppAssemblyVector* s_Assemblies; + extern void find_s_Assemblies(Paper::LoggerContext const& logger); +#endif + + // must be done on-demand because the pointers aren't necessarily correct at the time of il2cpp_functions::Init + void CheckS_GlobalMetadata(); + + // COPIES OF FREQUENTLY INLINED NON-API LIBIL2CPP FUNCTIONS: + char const* MetadataCache_GetStringFromIndex(StringIndex index); + Il2CppTypeDefinition const* MetadataCache_GetTypeDefinitionFromIndex(TypeDefinitionIndex index); + TypeDefinitionIndex MetadataCache_GetExportedTypeFromIndex(TypeDefinitionIndex index); + Il2CppGenericContainer const* MetadataCache_GetGenericContainerFromIndex(GenericContainerIndex index); + Il2CppGenericParameter const* MetadataCache_GetGenericParameterFromIndex(GenericParameterIndex index); + Il2CppClass* MetadataCache_GetNestedTypeFromIndex(NestedTypeIndex index); + TypeDefinitionIndex MetadataCache_GetIndexForTypeDefinition(Il2CppClass const* typeDefinition); + TypeDefinitionIndex MetadataCache_GetIndexForTypeDefinition(Il2CppTypeDefinition const* typeDefinition); + GenericParameterIndex MetadataCache_GetGenericParameterIndexFromParameter(Il2CppMetadataGenericParameterHandle handle); + Il2CppTypeDefinition const* MetadataCache_GetTypeDefinition(Il2CppClass* klass); + GenericParameterIndex MetadataCache_GetGenericContainerIndex(Il2CppClass* klass); + +#if !defined(UNITY_2019) && !defined(UNITY_2021) + Il2CppAssemblyVector* Assembly_GetAllAssemblies(); +#endif + + // Whether all of the il2cpp functions have been initialized or not + extern bool initialized; + // Initializes all of the IL2CPP functions via dlopen and dlsym for use. + void initialize() noexcept; +} + +#undef API_FUNC + +// Okay, so because we know that GC isn't overwriting heap (since it just calls the same shared calloc/malloc impls) +// we are confident that a new operator here isn't necessary or useful. +// We can safely say that the heap is shared properly, abeit without references. +// If references ARE desired, use one of the gc allocation functions in here expicitly. + +namespace i2c { + /// @brief Returns an allocated instance of the provided size that will not be written over by future GC allocations and holds references. + /// You MUST use the gc_free_specific function defined here to destroy it. + /// This function fallsback to calloc if no GC_Alloc or GC_Free implementations are found via xref/sigscan. + /// @param sz The size to allocate an instance of. + /// @return The allocated instance. + [[nodiscard]] void* gc_alloc_specific(size_t sz) noexcept; + + /// @brief Deletes the provided allocated instance from the gc_alloc_specific function defined here. + /// Other pointers will cause undefined behavior. + /// This function will call GC_free if there is both a GC_Alloc and GC_Free implementation available, free otherwise. + /// @param sz The pointer to free explicitly. + /// @return The allocated instance. + void gc_free_specific(void* ptr) noexcept; + + /// @brief Reallocation implementation is equivalent to: alloc + free + /// @param ptr The pointer to resize. + /// @param new_size The new size of the memory. + /// @return The resized instance. + [[nodiscard]] void* gc_realloc_specific(void* ptr, size_t new_size) noexcept; + + /// @brief EXTREMEMLY UNSAFE ALLOCATION! THIS SHOULD BE AVOIDED UNLESS YOU KNOW WHAT YOU ARE DOING! + /// This function allocates a GC-able object of the size provided by manipulating an existing Il2CppClass' instance_size. + /// This is VERY DANGEROUS (and NOT THREAD SAFE!) and may cause all sorts of race conditions. Use at your own risk. + /// @param size The size to allocate the unsafe object with. + /// @return The returned GC-allocated instance. + [[deprecated("DO NOT USE")]] void* __allocate_unsafe(std::size_t size) noexcept; + + /// @brief Resolves the provided icall, throwing an i2c::trace_exception with backtrace information if failed. + /// Does NOT cache the resolved method pointer. + /// Also does NOT perform any type checking of parameters, so make sure you check your parameters and return types! + /// @tparam R The return type of the function to resolve + /// @tparam TArgs The arguments of the function to resolve + /// @param name The name of the icall to resolve + /// @return The resolved function pointer, will always be valid or throws an i2c::trace_exceptionon. + template + auto resolve_icall(std::string_view name) { + using T = std::conditional_t>, function_ptr_t>; + functions::initialize(); + if (auto out = reinterpret_cast>(functions::resolve_icall(name.data()))) { + return static_cast(out); + } + return result_or_throw(fmt::format("Failed to resolve_icall for: {}!", name.data())); + } +} + +#pragma pack(pop) diff --git a/shared/arrayw.hpp b/shared/arrayw.hpp new file mode 100644 index 00000000..67882da1 --- /dev/null +++ b/shared/arrayw.hpp @@ -0,0 +1,255 @@ +#pragma once + +#include "exceptions.hpp" +#include "types.hpp" +#include "utils.hpp" + +/// @brief An Array wrapper type that is responsible for holding an (ideally valid) pointer to an array on the GC heap. +/// Allows for C++ array semantics. Ex, [], begin(), end(), etc... +template +struct ArrayW { + using ptr = Array*; + using const_ptr = Array const*; + + using value = T; + using const_value = T const; + using pointer = T*; + using const_pointer = T const*; + using reference = T&; + using const_reference = T const&; + + using iterator = pointer; + using const_iterator = const_pointer; + + /// @brief Default constructor wraps a nullptr array + constexpr ArrayW() noexcept : val(nullptr) {} + /// @brief Constructs an ArrayW that wraps a null value + constexpr ArrayW(std::nullptr_t nptr) noexcept : val(nptr) {} + /// @brief Create an ArrayW from an arbitrary pointer + constexpr ArrayW(void* inst) noexcept : val(static_cast(inst)) {} + /// @brief Create an ArrayW from a pointer + constexpr ArrayW(ptr inst) noexcept : val(inst) {} + +#ifdef HAS_CODEGEN + constexpr ArrayW(System::Array* inst) noexcept : val(static_cast(static_cast(inst))) {} +#endif + + // Empty with size + ArrayW(il2cpp_array_size_t size) { + i2c::functions::initialize(); + val = reinterpret_cast(i2c::functions::array_new(i2c::class_of(), size)); + } + // From initializer list + template + requires(std::is_convertible_v) + ArrayW(std::initializer_list vals) : ArrayW(vals.size()) { + std::copy(vals.begin(), vals.end(), begin()); + } + // From container + template + requires(std::is_convertible_v) + ArrayW(i2c::view vals) : ArrayW(vals.size()) { + std::copy(vals.begin(), vals.end(), begin()); + } + // Required because C++ cannot deduce the type inside a parameter if it also has to construct that parameter + ArrayW(i2c::view vals) : ArrayW(vals.size()) { std::copy(vals.begin(), vals.end(), begin()); } + template + requires(std::is_convertible_v) + ArrayW(std::vector vals) : ArrayW(i2c::view{vals}) {} + + constexpr ArrayW(ArrayW const&) noexcept = default; + constexpr ArrayW(ArrayW&&) noexcept = default; + + constexpr ArrayW& operator=(ArrayW const&) noexcept = default; + constexpr ArrayW& operator=(ArrayW&&) noexcept = default; + + constexpr ArrayW& operator=(ptr rhs) { + this->val = rhs; + return *this; + } + template + requires(std::is_convertible_v) + constexpr ArrayW& operator=(ArrayW rhs) { + return operator=(static_cast(rhs.val)); + } + + constexpr void* convert() const noexcept { return const_cast(static_cast(val)); } + constexpr bool operator==(ArrayW const&) const noexcept = default; + constexpr bool operator==(std::nullptr_t) const noexcept { return !val; } + + operator std::span() { return ref_to(); } + operator std::span const() const { return ref_to(); } + + operator ptr() noexcept { return val; } + operator const_ptr() const noexcept { return val; } + + ptr operator->() noexcept { return val; } + const_ptr operator->() const noexcept { return val; } + + operator bool() noexcept { return val != nullptr; } + operator bool() const noexcept { return val != nullptr; } + + [[nodiscard]] il2cpp_array_size_t size() const noexcept { return val->max_length; } + bool empty() const noexcept { return size() == 0; } + + void assert_bounds(il2cpp_array_size_t i) const { + if (i < 0 || i >= size()) { + throw i2c::trace_exception(fmt::format("{} is out of bounds for array of length: {}", i, size())); + } + } + + iterator begin() { return val->_values; } + const_iterator begin() const { return val->_values; } + iterator end() { return val->_values + size(); } + const_iterator end() const { return val->_values + size(); } + + auto rbegin() { return std::reverse_iterator(end()); } + auto rbegin() const { return std::reverse_iterator(end()); } + auto rend() { return std::reverse_iterator(begin()); } + auto rend() const { return std::reverse_iterator(begin()); } + + reference operator[](il2cpp_array_size_t i) noexcept { return val->_values[i]; } + const_reference operator[](il2cpp_array_size_t i) const noexcept { return val->_values[i]; } + + /// @brief Get a given index, performs bound checking and throws i2c::trace_exception on failure. + /// @param i The index to get. + /// @return The reference to the item. + reference at(il2cpp_array_size_t i) { + assert_bounds(i); + return (*this)[i]; + } + /// @brief Get a given index, performs bound checking and throws i2c::trace_exception on failure. + /// @param i The index to get. + /// @return The const reference to the item. + const_reference at(il2cpp_array_size_t i) const { + assert_bounds(i); + return (*this)[i]; + } + + /// @brief Tries to get a given index, performs bound checking and returns a std::nullopt on failure. + /// @param i The index to get. + /// @return The reference_wrapper to the item, mostly considered to be a T&. + std::optional> try_get(il2cpp_array_size_t i) noexcept { + if (i < 0 || i >= size()) { + return std::nullopt; + } + return (*this)[i]; + } + /// @brief Tries to get a given index, performs bound checking and returns a std::nullopt on failure. + /// @param i The index to get. + /// @return The reference_wrapper to the item, mostly considered to be a const T&. + std::optional> try_get(il2cpp_array_size_t i) const noexcept { + if (i < 0 || i >= size()) { + return std::nullopt; + } + return (*this)[i]; + } + + iterator find(const_reference item) { return std::find(begin(), end(), item); } + const_iterator find(const_reference item) const { return std::find(begin(), end(), item); } + + auto rfind(const_reference item) { return std::find(rbegin(), rend(), item); } + auto rfind(const_reference item) const { return std::find(rbegin(), rend(), item); } + + iterator find_if(auto&& pred) { return std::find_if(begin(), end(), pred); } + const_iterator find_if(auto&& pred) const { return std::find_if(begin(), end(), pred); } + + auto rfind_if(auto&& pred) { return std::find_if(rbegin(), rend(), pred); } + auto rfind_if(auto&& pred) const { return std::find_if(rbegin(), rend(), pred); } + + reference front() { return (*this)[0]; } + const_reference front() const { return (*this)[0]; } + reference front(auto&& pred) { return *find_if(pred); } + const_reference front(auto&& pred) const { return *find_if(pred); } + + template + requires(std::is_default_constructible_v && std::is_copy_constructible_v) + value front_or_default(TArgs&&... args) const { + auto itr = begin(); + if constexpr (sizeof...(TArgs) > 0) { + itr = find_if(std::forward(args)...); + } + if (itr == end()) { + return {}; + } + return *itr; + } + + reference back() { return (*this)[size() - 1]; } + const_reference back() const { return (*this)[size() - 1]; } + reference back(auto&& pred) { return *rfind_if(pred); } + const_reference back(auto&& pred) const { return *rfind_if(pred); } + + template + requires(std::is_default_constructible_v && std::is_copy_constructible_v) + value back_or_default(TArgs&&... args) const { + auto itr = rbegin(); + if constexpr (sizeof...(TArgs) > 0) { + itr = rfind_if(std::forward(args)...); + } + if (itr == rend()) { + return {}; + } + return *itr; + } + + bool contains(const_reference item) const { return find(item) != end(); } + + void copy_to(std::span destination, il2cpp_array_size_t index = 0) const { + if (index + size() > destination.size()) { + throw i2c::trace_exception("Destination span is too short for copy"); + } + std::copy_n(begin(), size(), std::next(destination.begin(), index)); + } + + il2cpp_array_size_t index_of(const_reference item) const { + auto itr = find(item); + if (itr == end()) { + return -1; + } + return std::distance(begin(), itr); + } + + /// @brief Provides a reference span of the held data within this array. The span should NOT outlive this instance. + /// @return The created span. + std::span ref_to() { return {val->_values, size()}; } + /// @brief Provides a reference span of the held data within this array. The span should NOT outlive this instance. + /// @return The created span. + std::span const ref_to() const { return {val->_values, size()}; } + +#ifdef HAS_CODEGEN + explicit constexpr operator ::System::Collections::ICollection*() noexcept { return static_cast<::System::Collections::ICollection*>(convert()); } + explicit constexpr operator ::System::Collections::IEnumerable*() noexcept { return static_cast<::System::Collections::IEnumerable*>(convert()); } + explicit constexpr operator ::System::Collections::IList*() noexcept { return static_cast<::System::Collections::IList*>(convert()); } + explicit constexpr operator ::System::Collections::IStructuralComparable*() noexcept { + return static_cast<::System::Collections::IStructuralComparable*>(convert()); + } + explicit constexpr operator ::System::Collections::IStructuralEquatable*() noexcept { + return static_cast<::System::Collections::IStructuralEquatable*>(convert()); + } + explicit constexpr operator ::System::ICloneable*() noexcept { return static_cast<::System::ICloneable*>(convert()); } +#endif + + private: + ptr val; +}; + +template +struct i2c::type_check::no_arg_class> { + static inline Il2CppClass* get() { return no_arg_class*>::get(); } +}; +MARK_GEN_REF_T(ArrayW); + +static_assert(sizeof(ArrayW) == sizeof(void*)); +static_assert(i2c::type_check::wrapper_ref_type>); + +template +struct fmt::is_range, Char> : std::false_type {}; + +template +inline std::string format_as(ArrayW array) { + if (!array) { + return fmt::format("ArrayW<{}>(null)", i2c::type_name()); + } + return fmt::format("{}", fmt::join(array.begin(), array.end(), ", ")); +} diff --git a/shared/binary.hpp b/shared/binary.hpp new file mode 100644 index 00000000..62ed1539 --- /dev/null +++ b/shared/binary.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +namespace i2c::binary { + uintptr_t get_base(void* pc); + ptrdiff_t as_offset(void* pc); + + // Attempts to print what is stored at the given pointer. + // For a given pointer, it will scan 4 void*'s worth of bytes at the location pointed to. + // For each void* of bytes, it will print the raw bytes and interpretations of the bytes as ints and char*s. + // When the bytes look like a valid pointer, it will attempt to follow that pointer, increasing the indentation. + // It will not follow pointers that it has already analyzed as a result of the current call. + void analyze_bytes(void const* ptr); + + // Dumps the 'before' bytes before and 'after' bytes after the given pointer to log + void dump(int before, int after, void* ptr); + + uintptr_t get_real_offset(void const* offset); + uintptr_t base_addr(char const* soname); + + // Only wildcard is ? and ?? - both are handled the same way. They will skip exactly 1 byte (2 hex digits) + uintptr_t find_pattern(uintptr_t dw_addr, char const* pattern, uintptr_t dw_search_len); + // Same as find_pattern but will continue scanning to make sure your pattern is sufficiently specific. + // Each candidate will be logged. label should describe what you're looking for, like "Class::Init". + // Sets "multiple" iff multiple matches are found, and outputs a log warning message. + // Returns the first match, if any. + uintptr_t find_unique_pattern(bool& multiple, uintptr_t dw_addr, char const* pattern, uintptr_t dw_search_len, char const* label = 0); + + /// @brief Attempts to match the pattern provided with all regions of mapped read memory with the file provided + uintptr_t mapped_file_unique_pattern(bool& multiple, char const* pattern, char const* file, char const* label = 0); + + /// @brief Attempts to match the pattern provided with all regions of mapped read memory with the libil2cpp.so + uintptr_t libil2cpp_unique_pattern(bool& multiple, char const* pattern, char const* label = 0); + + /// @brief Attempts to match the pattern provided with all regions of mapped read memory with the libunity.so + uintptr_t libunity_unique_pattern(bool& multiple, char const* pattern, char const* label = 0); + + /// @brief Get the size of the libil2cpp.so file + uintptr_t get_libil2cpp_size(); + + /// @brief Get the build id from a file + std::optional get_build_id(std::string_view filename); +} diff --git a/shared/byref.hpp b/shared/byref.hpp new file mode 100644 index 00000000..a2f24c1c --- /dev/null +++ b/shared/byref.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "types.hpp" + +/// @brief Represents a byref parameter that wraps a reference. +/// This is REQUIRED for codegen invokes, as run_method can't tell the difference between a reference parameter and a byref on constexpr time. +template +requires(!std::is_reference_v) +struct by_ref { + constexpr by_ref(T& val) noexcept : ref(&val) {} + explicit constexpr by_ref(void* val) noexcept : ref(reinterpret_cast(val)) {} + + constexpr void* convert() const noexcept { return reinterpret_cast(ref); } + + constexpr T& operator*() noexcept { return *ref; } + constexpr T const& operator*() const noexcept { return *ref; } + constexpr T* operator->() noexcept { return ref; } + constexpr T const* operator->() const noexcept { return ref; } + + by_ref& operator=(T& other) { + ref = &other; + return *this; + } + + T* ref; +}; + +// We do not need a no_arg_class specialization for by_ref, since it will never get to that point. +template +struct BS_HOOK_HIDDEN ::i2c::type_check::no_arg_type> { + static inline Il2CppType const* get() { return &no_arg_class::get()->this_arg; } +}; +MARK_GEN_REF_T(by_ref); diff --git a/shared/callback.hpp b/shared/callback.hpp new file mode 100644 index 00000000..100a24bd --- /dev/null +++ b/shared/callback.hpp @@ -0,0 +1,212 @@ +#pragma once + +#include +#include +#include + +namespace detail { + template