diff --git a/.github/actions/canary-ndk/action.yml b/.github/actions/canary-ndk/action.yml new file mode 100644 index 0000000..81f9b3a --- /dev/null +++ b/.github/actions/canary-ndk/action.yml @@ -0,0 +1,35 @@ +name: "Setup canary ndk" +description: "Sets up canary ndk" +outputs: + ndk-path: + value: ${{ steps.path.outputs.path }} + description: "Output path of the ndk" + cache-hit: + value: ${{ steps.cache.outputs.cache-hit }} + description: "Whether a cache hit occurred for the ndk" +runs: + using: "composite" + steps: + - name: NDK cache + id: cache + uses: actions/cache@v3 + with: + path: ${HOME}/android-ndk-r27-canary/ + key: ${{ runner.os }}-ndk-r27-canary + + - name: Download canary ndk + if: ${{ !steps.cache.outputs.cache-hit }} + env: + CANARY_URL: https://github.com/QuestPackageManager/ndk-canary-archive/releases/download/27.0.1/android-ndk-10883340-linux-x86_64.zip + run: wget ${CANARY_URL} -O ${HOME}/ndk.zip + shell: bash + + - name: Unzip ndk + if: ${{ !steps.cache.outputs.cache-hit }} + run: 7z x "${HOME}/ndk.zip" -o"${HOME}/" + shell: bash + + - name: Set output + id: path + shell: bash + run: echo "path=${HOME}/android-ndk-r27-canary" >> ${GITHUB_OUTPUT} diff --git a/.github/workflows/build-ndk.yml b/.github/workflows/build-ndk.yml index 4f383d8..225c325 100644 --- a/.github/workflows/build-ndk.yml +++ b/.github/workflows/build-ndk.yml @@ -1,15 +1,18 @@ name: NDK build -env: - module_id: custom-types - qmodName: CustomTypes - cache-name: customtypes_cache - on: workflow_dispatch: push: + branches-ignore: + - "version-*" pull_request: - branches: [master] + branches-ignore: + - "version-*" + +env: + module_id: custom-types + qmodName: CustomTypes + cache-name: customtypes_cache jobs: build: @@ -24,9 +27,13 @@ jobs: - uses: seanmiddleditch/gha-setup-ninja@v3 + - name: Setup canary NDK + id: setup-ndk + uses: ./.github/actions/canary-ndk + - name: Create ndkpath.txt run: | - echo "$ANDROID_NDK_LATEST_HOME" > ${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 3af32ee..fcc1310 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,17 +23,20 @@ jobs: - uses: seanmiddleditch/gha-setup-ninja@v3 + - name: Setup canary NDK + id: setup-ndk + uses: ./.github/actions/canary-ndk + - name: Create ndkpath.txt run: | - echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt + echo ${{ steps.setup-ndk.outputs.ndk-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 @@ -47,8 +50,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 @@ -76,13 +79,12 @@ jobs: pwsh -Command ./createqmod.ps1 ${{env.qmodName}} - name: Rename debug file - run: mv "./build/debug/${{ steps.libname.outputs.NAME }}" "./build/debug/debug_${{ steps.libname.outputs.NAME }}" + 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: - name: ${{ github.event.inputs.release_msg }} tag_name: ${{ github.event.inputs.version }} files: | ./build/${{ steps.libname.outputs.NAME }} diff --git a/qpm.json b/qpm.json index b51e321..3bf1ec3 100644 --- a/qpm.json +++ b/qpm.json @@ -4,17 +4,20 @@ "info": { "name": "custom-types", "id": "custom-types", - "version": "0.4.6", + "version": "0.16.0", "url": "https://github.com/sc2ad/Il2CppQuestTypePatching", "additionalData": { "overrideSoName": "libcustom-types.so", - "cmake": true + "cmake": true, + "compileOptions": { + "cppFlags": [ "-Wno-invalid-offsetof" ] + } } }, "dependencies": [ { "id": "beatsaber-hook", - "versionRange": "^4.0.0", + "versionRange": "^5.0.1", "additionalData": { "extraFiles": [ "src/inline-hook" diff --git a/qpm.shared.json b/qpm.shared.json index 9ae2cbf..4227d91 100644 --- a/qpm.shared.json +++ b/qpm.shared.json @@ -5,24 +5,22 @@ "info": { "name": "custom-types", "id": "custom-types", - "version": "0.4.6", + "version": "0.16.0", "url": "https://github.com/sc2ad/Il2CppQuestTypePatching", "additionalData": { "overrideSoName": "libcustom-types.so", + "compileOptions": { + "cppFlags": [ + "-Wno-invalid-offsetof" + ] + }, "cmake": true } }, - "workspace": { - "scripts": { - "build": [ - "pwsh ./build.ps1" - ] - } - }, "dependencies": [ { "id": "beatsaber-hook", - "versionRange": "^4.0.0", + "versionRange": "^5.0.1", "additionalData": { "extraFiles": [ "src/inline-hook" @@ -36,43 +34,47 @@ "private": true } } - ] + ], + "workspace": { + "scripts": { + "build": [ + "pwsh ./build.ps1" + ] + } + } }, "restoredDependencies": [ { "dependency": { "id": "libil2cpp", - "versionRange": "=0.2.3", + "versionRange": "=0.3.1", "additionalData": { "headersOnly": true } }, - "version": "0.2.3" + "version": "0.3.1" }, { "dependency": { "id": "beatsaber-hook", - "versionRange": "=4.0.1", + "versionRange": "=5.0.1", "additionalData": { - "soLink": "https://github.com/sc2ad/beatsaber-hook/releases/download/v4.0.1/libbeatsaber-hook_4_0_1.so", - "debugSoLink": "https://github.com/sc2ad/beatsaber-hook/releases/download/v4.0.1/debug_libbeatsaber-hook_4_0_1.so", - "branchName": "version/v4_0_1" + "soLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v5.0.1/libbeatsaber-hook_5_0_1.so", + "debugSoLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v5.0.1/debug_libbeatsaber-hook_5_0_1.so", + "branchName": "version/v5_0_1" } }, - "version": "4.0.1" + "version": "5.0.1" }, { "dependency": { "id": "scotland2", - "versionRange": "=0.1.2", + "versionRange": "=0.1.3", "additionalData": { - "soLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.2/libsl2.so", - "debugSoLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.2/debug_libsl2.so", - "overrideSoName": "libsl2.so", - "branchName": "version/v0_1_2" + "overrideSoName": "libsl2.so" } }, - "version": "0.1.2" + "version": "0.1.3" }, { "dependency": { diff --git a/shared/coroutine.hpp b/shared/coroutine.hpp index 370f118..9c84179 100644 --- a/shared/coroutine.hpp +++ b/shared/coroutine.hpp @@ -29,7 +29,7 @@ namespace custom_types::Helpers { #include #include "macros.hpp" -#ifdef HAS_CODEGEN +#if __has_include("System/Collections/IEnumerator.hpp") #include "System/Collections/IEnumerator.hpp" namespace custom_types::Helpers { using enumeratorT = System::Collections::IEnumerator*; @@ -46,6 +46,7 @@ namespace custom_types::Helpers { namespace custom_types::Helpers { struct Wrapper { enumeratorT instance; + constexpr Wrapper() : instance(nullptr) {} constexpr Wrapper(enumeratorT value) : instance(value) {} }; static_assert(sizeof(Wrapper) == sizeof(enumeratorT)); @@ -57,6 +58,8 @@ namespace custom_types::Helpers { using Coroutine = generator; using CoroFuncType = std::function; } +MARK_GEN_REF_PTR_T(custom_types::Helpers::generator); +MARK_REF_PTR_T(custom_types::Helpers::Coroutine*); // Coroutine* mapped to void* template<> struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class { @@ -73,6 +76,7 @@ struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_classvoid_class); } }; +MARK_REF_PTR_T(custom_types::Helpers::CoroFuncType*); namespace custom_types::Helpers { /// @brief A helper type for creating custom coroutines C# from C++. @@ -186,15 +190,11 @@ namespace custom_types::Helpers { } } -DECLARE_CLASS_INTERFACES(custom_types::Helpers, ResetableCoroutine, "System", "Object", sizeof(Il2CppObject), +DECLARE_CLASS_INTERFACES(custom_types::Helpers, ResetableCoroutine, "System", "Object", sizeof(Il2CppObject), (il2cpp_utils::GetClassFromName("System.Collections", "IEnumerator")), private: custom_types::Helpers::CoroFuncType coroCreator; custom_types::Helpers::Wrapper current; - // Explicitly delete copy constructor - ResetableCoroutine(const ResetableCoroutine& other) = delete; - // Explicitly delete move constructor - ResetableCoroutine(ResetableCoroutine&& other) = delete; // These fields exist as C# fields for semantic purposes only DECLARE_INSTANCE_FIELD(custom_types::Helpers::Coroutine*, currentCoro); DECLARE_INSTANCE_FIELD(bool, valid); @@ -206,16 +206,12 @@ DECLARE_CLASS_INTERFACES(custom_types::Helpers, ResetableCoroutine, "System", "O DECLARE_DTOR(Finalize); ) -DECLARE_CLASS_INTERFACES(custom_types::Helpers, StandardCoroutine, "System", "Object", sizeof(Il2CppObject), +DECLARE_CLASS_INTERFACES(custom_types::Helpers, StandardCoroutine, "System", "Object", sizeof(Il2CppObject), (il2cpp_utils::GetClassFromName("System.Collections", "IEnumerator")), struct CoroutineNotResettable : std::runtime_error { CoroutineNotResettable() : std::runtime_error("StandardCoroutine is not resettable!") {} }; custom_types::Helpers::Wrapper current; - // Explicitly delete copy constructor - StandardCoroutine(const StandardCoroutine& other) = delete; - // Explicitly delete move constructor - StandardCoroutine(StandardCoroutine&& other) = delete; // These fields exist as C# fields for semantic purposes only DECLARE_INSTANCE_FIELD(custom_types::Helpers::Coroutine*, currentCoro); DECLARE_INSTANCE_FIELD(bool, valid); @@ -260,7 +256,7 @@ namespace custom_types::Helpers { constexpr T* operator->() noexcept { return ptr; } - + private: T* ptr; }; @@ -272,7 +268,7 @@ namespace custom_types::Helpers { struct CoroutineHelper { private: static void EnsureCoroutines(); - + public: /// @brief Creates a new StandardCoroutine from the provided Coroutine instance, which is immediately rendered invalid. /// This function will throw a ::custom_types::Helpers::CoroutineAllocationFailed exception on failure. diff --git a/shared/delegate.hpp b/shared/delegate.hpp index 7fd7a08..e54fdd5 100644 --- a/shared/delegate.hpp +++ b/shared/delegate.hpp @@ -8,10 +8,10 @@ namespace custom_types { int get_delegate_count(); -inline void setup_for_delegate(MethodInfo* info) { +inline void setup_for_delegate([[maybe_unused]] MethodInfo* info) { // The method in question actually isn't quite fit for being a proper delegate // So, here we will set it just to make sure it does what we want. - info->is_marshaled_from_native = true; + info->indirect_call_via_invokers = true; // TODO: Support virtual invokes some time in the distant, distant future. // m->slot = kInvalidIl2CppMethodSlot; // m->invoker_method = parent_invoke->invoker_method; @@ -126,11 +126,11 @@ struct DelegateWrapperStatic : Il2CppObject { const Il2CppType* returnType() const override { return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); } - std::vector params() const override { - int32_t counter = 1; + std::vector params() const override { il2cpp_functions::Init(); - return {ParameterInfo{"inst", 0, static_cast(-1), ::il2cpp_functions::class_get_type(___TypeRegistration::klass_ptr)}, - (ParameterInfo{"param", counter++, static_cast(-1), ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get())})... + return { + ::il2cpp_functions::class_get_type(___TypeRegistration::klass_ptr), + (il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()))... }; } uint8_t params_size() const override { @@ -169,7 +169,7 @@ struct DelegateWrapperStatic : Il2CppObject { ::il2cpp_functions::Init(); return &il2cpp_functions::defaults->void_class->byval_arg; } - std::vector params() const override { + std::vector params() const override { return {}; } uint8_t params_size() const override { @@ -338,11 +338,11 @@ struct DelegateWrapperInstance : Il2CppObject { ::il2cpp_functions::Init(); return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); } - std::vector params() const override { - int32_t counter = 1; + std::vector params() const override { il2cpp_functions::Init(); - return {ParameterInfo{"inst", 0, static_cast(-1), ::il2cpp_functions::class_get_type(___TypeRegistration::klass_ptr)}, - (ParameterInfo{"param", counter++, static_cast(-1), ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get())})... + return { + ::il2cpp_functions::class_get_type(___TypeRegistration::klass_ptr), + (::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()))... }; } uint8_t params_size() const override { @@ -406,7 +406,7 @@ struct DelegateWrapperInstance : Il2CppObject { ::il2cpp_functions::Init(); return &il2cpp_functions::defaults->void_class->byval_arg; } - std::vector params() const override { + std::vector params() const override { return {}; } uint8_t params_size() const override { diff --git a/shared/logging.hpp b/shared/logging.hpp index d85258f..947af09 100644 --- a/shared/logging.hpp +++ b/shared/logging.hpp @@ -52,7 +52,7 @@ namespace custom_types { /// @brief Logs the provided ParameterInfo* /// @param info The ParameterInfo* to log all fields on. - void logParam(const ParameterInfo* info); + void logParam(const Il2CppType* info, int index); /// @brief Logs the provided MethodInfo* /// @param info The MethodInfo* to log all fields on. @@ -61,4 +61,4 @@ namespace custom_types { /// @brief Logs the provided Il2CppClass* /// @param klass The Il2CppClass* to log all fields on. void logAll(const Il2CppClass* klass); -} \ No newline at end of file +} diff --git a/shared/macros.hpp b/shared/macros.hpp index c0d8fd2..925dd86 100644 --- a/shared/macros.hpp +++ b/shared/macros.hpp @@ -60,12 +60,15 @@ namespace namespaze_ { \ class name_; \ } \ +MARK_REF_PTR_T(namespaze_::name_);\ namespace namespaze_ { \ class name_ { \ using ___TargetType = name_; \ constexpr static auto ___Base__Size = baseSize; \ friend ::custom_types::Register; \ public: \ + constexpr static bool __IL2CPP_IS_VALUE_TYPE = typeEnum_ != Il2CppTypeEnum::IL2CPP_TYPE_CLASS;\ + static const int __IL2CPP_REFERENCE_TYPE_SIZE;\ struct ___TypeRegistration : ::custom_types::TypeRegistration { \ ___TypeRegistration() { \ ::custom_types::Register::AddType(this); \ @@ -152,9 +155,14 @@ namespace namespaze_ { \ } \ }; \ uint8_t _baseFields[baseSize]; \ + protected: \ + name_() {}; \ public: \ + name_(name_&&) = delete;\ + name_(name_ const&) = delete;\ __VA_ARGS__ \ }; \ + inline constexpr const int name_::__IL2CPP_REFERENCE_TYPE_SIZE = sizeof(name_);\ } \ template<> \ struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<::namespaze_::name_*> { \ @@ -172,12 +180,15 @@ struct ::il2cpp_utils::il2cpp_type_check::need_box<::namespaze_::name_> { \ namespace namespaze_ { \ class name_; \ } \ +MARK_REF_PTR_T(namespaze_::name_);\ namespace namespaze_ { \ class name_ : public baseT { \ using ___TargetType = name_; \ constexpr static auto ___Base__Size = sizeof(baseT); \ friend ::custom_types::Register; \ public: \ + constexpr static bool __IL2CPP_IS_VALUE_TYPE = typeEnum_ != Il2CppTypeEnum::IL2CPP_TYPE_CLASS;\ + static const int __IL2CPP_REFERENCE_TYPE_SIZE;\ struct ___TypeRegistration : ::custom_types::TypeRegistration { \ ___TypeRegistration() { \ ::custom_types::Register::AddType(this); \ @@ -230,7 +241,9 @@ namespace namespaze_ { \ return dllName_; \ } \ Il2CppClass* baseType() const override { \ - return ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get(); \ + auto klass = ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get();\ + if (!klass->initialized) il2cpp_functions::Class_Init(klass);\ + return klass;\ } \ std::vector const interfaces() const override { \ return interfaces_; \ @@ -263,9 +276,14 @@ namespace namespaze_ { \ return instance; \ } \ }; \ + protected: \ + name_() {}; \ public: \ + name_(name_&&) = delete;\ + name_(name_ const&) = delete;\ __VA_ARGS__ \ }; \ + inline constexpr const int name_::__IL2CPP_REFERENCE_TYPE_SIZE = sizeof(name_);\ } \ template<> \ struct ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<::namespaze_::name_*> { \ @@ -394,13 +412,33 @@ bool namespaze::name::___TypeRegistration::init = false; // TODO: Add a way of declaring abstract/interface types. // This requires messing with method slots even more than I do right now. -#ifdef DECLARE_INSTANCE_FIELD_DEFAULT -#error "DECLARE_INSTANCE_FIELD_DEFAULT is already defined! Undefine it before including macros.hpp!" +// if we compile with declspec, use that to wrap field accesses automatically, +// this then requires the backing fields to have a different name, this achieves that +#ifdef BACKING_FIELD_NAME +#error "BACKING_FIELD_NAME is already defined! Undefine it before including macros.hpp!" #endif -// Declares a field with type, name, value. -// Fields declared like this must also be registered via REGISTER_FIELD within the REGISTER_TYPE function. -// Fields like this are ONLY initialized when the C++ constructor is called. See the INVOKE_CTOR macro for more info. -#define DECLARE_INSTANCE_FIELD_DEFAULT(type_, name_, value) \ + +#if __has_declspec_attribute(property) && !defined(CT_NO_DECLSPEC_PROPS) +#define BACKING_FIELD_NAME(name_) ___backing_field_##name_ +#else +#define BACKING_FIELD_NAME(name_) name_ +#endif + +#ifdef BACKING_FIELD_OFFSET_OF +#error "BACKING_FIELD_OFFSET_OF is already defined! Undefine it before including macros.hpp!" +#endif + +#if __has_declspec_attribute(property) && !defined(CT_NO_DECLSPEC_PROPS) +#define BACKING_FIELD_OFFSET_OF(name_) offsetof(___TargetType, ___backing_field_##name_) +#else +#define BACKING_FIELD_OFFSET_OF(name_) offsetof(___TargetType, name_) +#endif + +#ifdef DEFINE_INSTANCE_FIELD_REGISTRATOR +#error "DEFINE_INSTANCE_FIELD_REGISTRATOR is already defined! Undefine it before including macros.hpp!" +#endif + +#define DEFINE_INSTANCE_FIELD_REGISTRATOR(type_, name_, flags_)\ private: \ struct ___FieldRegistrator_##name_ : ::custom_types::FieldRegistrator { \ ___FieldRegistrator_##name_() { \ @@ -411,22 +449,58 @@ struct ___FieldRegistrator_##name_ : ::custom_types::FieldRegistrator { \ } \ const Il2CppType* type() const override { \ ::il2cpp_functions::Init(); \ - return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); \ + auto klass = ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get();\ + if (!klass->initialized) il2cpp_functions::Class_Init(klass);\ + return ::il2cpp_functions::class_get_type(klass); \ } \ constexpr uint16_t fieldAttributes() const override { \ - return FIELD_ATTRIBUTE_PUBLIC; \ + return flags_; \ } \ constexpr size_t size() const override { \ return sizeof(type_); \ } \ int32_t offset() const override { \ - return offsetof(___TargetType, name_); \ + return BACKING_FIELD_OFFSET_OF(name_); \ } \ }; \ -static inline ___FieldRegistrator_##name_ ___##name_##_FieldRegistrator; \ -public: \ -type_ name_ = value +static inline ___FieldRegistrator_##name_ ___##name_##_FieldRegistrator +#ifdef DEFINE_INSTANCE_FIELD_ACCESSORS +#error "DEFINE_INSTANCE_FIELD_ACCESSORS is already defined! Undefine it before including macros.hpp!" +#endif + +#define DEFINE_INSTANCE_FIELD_ACCESSORS(type_, name_, visibility_) \ +protected: \ +static inline custom_types::field_accessor ___##name_##_FieldAccessor; \ +visibility_: \ +inline type_& __get_##name_() noexcept { return ___##name_##_FieldAccessor.read(this, ___##name_##_FieldRegistrator.offset()); } \ +inline type_ const& __get_##name_() const noexcept { return ___##name_##_FieldAccessor.read(this, ___##name_##_FieldRegistrator.offset()); } \ +inline void __set_##name_(type_ v) { ___##name_##_FieldAccessor.write(this, ___##name_##_FieldRegistrator.offset(), std::forward(v)); } + +// if we have no property support, we should not emit code that tries to be one. instead just emit nothing. +// earlier we defined a special way to have backing fields exist, so you will always be able to access the fields with this->fieldname +#ifdef DECLARE_INSTANCE_CPP_PROPERTY +#error "DECLARE_INSTANCE_CPP_PROPERTY is already defined! Undefine it before including macros.hpp!" +#endif + +#if __has_declspec_attribute(property) && !defined(CT_NO_DECLSPEC_PROPS) +#define DECLARE_INSTANCE_CPP_PROPERTY(type_, name_) __declspec(property(get=__get_##name_,put=__set_##name_)) type_ name_ +#else +#define DECLARE_INSTANCE_CPP_PROPERTY(type_, name_) +#endif + +#ifdef DECLARE_INSTANCE_FIELD_DEFAULT +#error "DECLARE_INSTANCE_FIELD_DEFAULT is already defined! Undefine it before including macros.hpp!" +#endif +// Declares a field with type, name, value. +// Fields declared like this must also be registered via REGISTER_FIELD within the REGISTER_TYPE function. +// Fields like this are ONLY initialized when the C++ constructor is called. See the INVOKE_CTOR macro for more info. +#define DECLARE_INSTANCE_FIELD_DEFAULT(type_, name_, value) \ +DEFINE_INSTANCE_FIELD_REGISTRATOR(type_, name_, FIELD_ATTRIBUTE_PUBLIC);\ +DEFINE_INSTANCE_FIELD_ACCESSORS(type_, name_, public);\ +public: \ +DECLARE_INSTANCE_CPP_PROPERTY(type_, name_);\ +type_ BACKING_FIELD_NAME(name_) = value #ifdef DECLARE_INSTANCE_FIELD_PRIVATE_DEFAULT #error "DECLARE_INSTANCE_FIELD_PRIVATE_DEFAULT is already defined! Undefine it before including macros.hpp!" @@ -435,93 +509,33 @@ type_ name_ = value // Fields declared like this must also be registered via REGISTER_FIELD within the REGISTER_TYPE function. // Fields like this are ONLY initialized when the C++ constructor is called. See the INVOKE_CTOR macro for more info. #define DECLARE_INSTANCE_FIELD_PRIVATE_DEFAULT(type_, name_, value) \ +DEFINE_INSTANCE_FIELD_REGISTRATOR(type_, name_, FIELD_ATTRIBUTE_PRIVATE);\ +DEFINE_INSTANCE_FIELD_ACCESSORS(type_, name_, private);\ private: \ -struct ___FieldRegistrator_##name_ : ::custom_types::FieldRegistrator { \ - ___FieldRegistrator_##name_() { \ - ___TargetType::___TypeRegistration::addField(this); \ - } \ - constexpr const char* name() const override { \ - return #name_; \ - } \ - const Il2CppType* type() const override { \ - ::il2cpp_functions::Init(); \ - return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); \ - } \ - constexpr uint16_t fieldAttributes() const override { \ - return FIELD_ATTRIBUTE_PRIVATE; \ - } \ - constexpr size_t size() const override { \ - return sizeof(type_); \ - } \ - int32_t offset() const override { \ - return offsetof(___TargetType, name_); \ - } \ -}; \ -static inline ___FieldRegistrator_##name_ ___##name_##_FieldRegistrator; \ -private: \ -type_ name_ = value +DECLARE_INSTANCE_CPP_PROPERTY(type_, name_);\ +type_ BACKING_FIELD_NAME(name_) = value #ifdef DECLARE_INSTANCE_FIELD #error "DECLARE_INSTANCE_FIELD is already defined! Undefine it before including macros.hpp!" #endif // Declare a field with type, name. #define DECLARE_INSTANCE_FIELD(type_, name_) \ -private: \ -struct ___FieldRegistrator_##name_ : ::custom_types::FieldRegistrator { \ - ___FieldRegistrator_##name_() { \ - ___TargetType::___TypeRegistration::addField(this); \ - } \ - constexpr const char* name() const override { \ - return #name_; \ - } \ - const Il2CppType* type() const override { \ - ::il2cpp_functions::Init(); \ - return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); \ - } \ - constexpr uint16_t fieldAttributes() const override { \ - return FIELD_ATTRIBUTE_PUBLIC; \ - } \ - constexpr size_t size() const override { \ - return sizeof(type_); \ - } \ - int32_t offset() const override { \ - return offsetof(___TargetType, name_); \ - } \ -}; \ -static inline ___FieldRegistrator_##name_ ___##name_##_FieldRegistrator; \ +DEFINE_INSTANCE_FIELD_REGISTRATOR(type_, name_, FIELD_ATTRIBUTE_PUBLIC);\ +DEFINE_INSTANCE_FIELD_ACCESSORS(type_, name_, public);\ public: \ -type_ name_ +DECLARE_INSTANCE_CPP_PROPERTY(type_, name_);\ +type_ BACKING_FIELD_NAME(name_) #ifdef DECLARE_INSTANCE_FIELD_PRIVATE #error "DECLARE_INSTANCE_FIELD_PRIVATE is already defined! Undefine it before including macros.hpp!" #endif // Declare a field with type, name. #define DECLARE_INSTANCE_FIELD_PRIVATE(type_, name_) \ +DEFINE_INSTANCE_FIELD_REGISTRATOR(type_, name_, FIELD_ATTRIBUTE_PRIVATE);\ +DEFINE_INSTANCE_FIELD_ACCESSORS(type_, name_, private);\ private: \ -struct ___FieldRegistrator_##name_ : ::custom_types::FieldRegistrator { \ - ___FieldRegistrator_##name_() { \ - ___TargetType::___TypeRegistration::addField(this); \ - } \ - constexpr const char* name() const override { \ - return #name_; \ - } \ - const Il2CppType* type() const override { \ - ::il2cpp_functions::Init(); \ - return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); \ - } \ - constexpr uint16_t fieldAttributes() const override { \ - return FIELD_ATTRIBUTE_PRIVATE; \ - } \ - constexpr size_t size() const override { \ - return sizeof(type_); \ - } \ - int32_t offset() const override { \ - return offsetof(___TargetType, name_); \ - } \ -}; \ -static inline ___FieldRegistrator_##name_ ___##name_##_FieldRegistrator; \ -private: \ -type_ name_ +DECLARE_INSTANCE_CPP_PROPERTY(type_, name_);\ +type_ BACKING_FIELD_NAME(name_) #ifdef DECLARE_STATIC_FIELD #error "DECLARE_STATIC_FIELD is already defined! Undefine it before including macros.hpp!" @@ -590,10 +604,10 @@ struct ___MethodRegistrator_##name_ : ::custom_types::Method il2cpp_functions::Init(); \ return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); \ } \ - std::vector params() const override { \ + std::vector params() const override { \ int32_t counter = 0; \ il2cpp_functions::Init(); \ - return {(ParameterInfo{"param", counter++, static_cast(-1), ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get())})...}; \ + return {(::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()))...}; \ } \ uint8_t params_size() const override { \ return sizeof...(TArgs); \ @@ -657,6 +671,14 @@ public: \ ret name(__VA_ARGS__); \ ___CREATE_INSTANCE_METHOD(name, #name, (overridingMethodInfo->flags & ~METHOD_ATTRIBUTE_ABSTRACT) | METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_HIDE_BY_SIG, overridingMethodInfo) +#ifdef DECLARE_OVERRIDE_METHOD_MATCH +#error "DECLARE_OVERRIDE_METHOD_MATCH is already defined! Undefine it before including macros.hpp!" +#endif +// Declare an overriding method with: return type, name, method it is implementing, parameters... +// This macro matches the DECLARE_OVERRIDE_METHOD macro except it matches the method you pass in with the il2cpp type check. +#define DECLARE_OVERRIDE_METHOD_MATCH(ret, name, overridingMethod, ...) \ +DECLARE_OVERRIDE_METHOD(ret, name, il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() __VA_OPT__(, __VA_ARGS__)) + #ifdef DECLARE_DTOR #error "DECLARE_DTOR is already defined! Undefine it before including macros.hpp!" #endif @@ -756,10 +778,10 @@ struct ___MethodRegistrator_##name_ : ::custom_types::MethodReg il2cpp_functions::Init(); \ return ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()); \ } \ - std::vector params() const override { \ + std::vector params() const override { \ int32_t counter = 0; \ il2cpp_functions::Init(); \ - return {(ParameterInfo{"param", counter++, static_cast(-1), ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get())})...}; \ + return {(::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()))...}; \ } \ uint8_t params_size() const override { \ return sizeof...(TArgs); \ diff --git a/shared/register.hpp b/shared/register.hpp index f0bf100..76e4822 100644 --- a/shared/register.hpp +++ b/shared/register.hpp @@ -7,7 +7,7 @@ #include #include "logging.hpp" #include "types.hpp" - +#include "beatsaber-hook/shared/utils/typedefs.h" template <> struct std::hash> { @@ -151,4 +151,4 @@ class Register { // dangling references. } }; -} // namespace custom_types \ No newline at end of file +} // namespace custom_types diff --git a/shared/types.hpp b/shared/types.hpp index c37cd04..400e406 100644 --- a/shared/types.hpp +++ b/shared/types.hpp @@ -4,6 +4,7 @@ #include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" #include "beatsaber-hook/shared/utils/il2cpp-utils.hpp" #include "beatsaber-hook/shared/utils/typedefs.h" +#include "beatsaber-hook/shared/utils/size-concepts.hpp" #include #include #include @@ -46,7 +47,7 @@ namespace custom_types { virtual int flags() const = 0; virtual const MethodInfo* virtualMethod() const = 0; virtual const Il2CppType* returnType() const = 0; - virtual std::vector params() const = 0; + virtual std::vector params() const = 0; virtual uint8_t params_size() const = 0; virtual Il2CppMethodPointer methodPointer() const = 0; virtual InvokerMethod invoker() const = 0; @@ -67,7 +68,7 @@ namespace custom_types { info->slot = kInvalidIl2CppMethodSlot; auto ps = params(); info->parameters_count = ps.size(); - auto* paramList = reinterpret_cast(calloc(ps.size(), sizeof(ParameterInfo))); + auto* paramList = reinterpret_cast(calloc(ps.size(), sizeof(Il2CppType*))); for (uint8_t pi = 0; pi < info->parameters_count; pi++) { paramList[pi] = ps[pi]; } @@ -157,8 +158,8 @@ namespace custom_types { // 1 or more parameters template struct parameter_converter { - static inline std::vector get() { - std::vector params; + static inline std::vector get() { + std::vector params; auto& info = params.emplace_back(); il2cpp_functions::Init(); const Il2CppType* type = ::il2cpp_functions::class_get_type(::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class

::get()); @@ -168,8 +169,7 @@ namespace custom_types { if (type == nullptr) { _logger().warning("Failed to get type of parameter!"); } - info.parameter_type = type; - info.token = -1; + info = type; for (const auto& q : parameter_converter::get()) { params.push_back(q); } @@ -180,8 +180,8 @@ namespace custom_types { // 0 parameters template struct parameter_converter { - static inline std::vector get() { - return std::vector(); + static inline std::vector get() { + return std::vector(); } }; @@ -191,11 +191,9 @@ namespace custom_types { static inline Q unpack_arg(void* arg, type_tag) { if constexpr (std::is_pointer_v) { return reinterpret_cast(arg); - } - else if constexpr (il2cpp_utils::has_il2cpp_conversion) { + } else if constexpr (il2cpp_utils::il2cpp_reference_type_wrapper) { return Q(arg); - } - else { + } else { return *reinterpret_cast(arg); } } @@ -220,36 +218,54 @@ namespace custom_types { return static_cast(il2cpp_functions::value_box(klass, static_cast(&thing))); } } + + template + static inline void pack_result_into(Q&& thing, void* retval) { + if constexpr (std::is_pointer_v) { + *static_cast(retval) = std::forward(thing); + } else if constexpr (il2cpp_utils::il2cpp_reference_type_wrapper) { + *static_cast(retval) = thing.convert(); + } else { + auto* klass = il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get(); + if (!klass) { + _logger().critical("Failed to get non-null Il2CppClass* during invoke of custom function!"); + return; + } + // the void* retval is a buffer created as being klass->instance_size - sizeof(Il2CppObject), see Runtime::InvokeWithThrow + auto sz = sizeof(std::decay_t); + std::memcpy(retval, &thing, sz); + } + } }; template struct invoker_creator { template - static void* instance_invoke(TRet(*func)(T*, TArgs...), T* self, void** args, std::index_sequence) { + static void instance_invoke(TRet(*func)(T*, TArgs...), T* self, void** args, std::index_sequence, void* retval) { IL2CPP_CATCH_HANDLER( if constexpr (std::is_same_v) { func(self, arg_helper::unpack_arg(args[Ns], type_tag{})... ); - return nullptr; } else { - return arg_helper::pack_result( + arg_helper::pack_result_into( func(self, arg_helper::unpack_arg(args[Ns], type_tag{})... - ) + ), + retval ); } ) } [[gnu::noinline]] - static void* invoke(Il2CppMethodPointer ptr, [[maybe_unused]] const MethodInfo* m, void* obj, void** args) { + static void invoke(Il2CppMethodPointer ptr, [[maybe_unused]] const MethodInfo* m, void* obj, void** args, void* retval) { // We also don't need to use anything from m so it is ignored. // Perhaps far in the future we will check attributes on it auto func = reinterpret_cast(ptr); T* self = static_cast(obj); auto seq = std::make_index_sequence(); - return instance_invoke(func, self, args, seq); + instance_invoke(func, self, args, seq, retval); } template [[gnu::noinline]] @@ -261,54 +277,57 @@ namespace custom_types { template struct invoker_creator { template - static void* static_invoke(TRet(*func)(TArgs...), void** args, std::index_sequence) { + static void static_invoke(TRet(*func)(TArgs...), void** args, std::index_sequence, void* retval) { IL2CPP_CATCH_HANDLER( if constexpr (std::is_same_v) { func( arg_helper::unpack_arg(args[Ns], type_tag{})... ); - return nullptr; } else { - return arg_helper::pack_result( + arg_helper::pack_result_into( func( arg_helper::unpack_arg(args[Ns], type_tag{})... - ) + ), + retval ); } ) } template - static void* static_invoke_method(TRet(*func)(TArgs..., const MethodInfo*), void** args, const MethodInfo* m, std::index_sequence) { + static void static_invoke_method(TRet(*func)(TArgs..., const MethodInfo*), void** args, const MethodInfo* m, std::index_sequence, void* retval) { IL2CPP_CATCH_HANDLER( if constexpr (std::is_same_v) { func( arg_helper::unpack_arg(args[Ns], type_tag{})..., m ); - return nullptr; } else { - return arg_helper::pack_result( + arg_helper::pack_result_into( func( arg_helper::unpack_arg(args[Ns], type_tag{})..., m - ) + ), + retval ); } ) } [[gnu::noinline]] - static void* invoke(Il2CppMethodPointer ptr, [[maybe_unused]] const MethodInfo* m, [[maybe_unused]] void* obj, void** args) { + static void invoke(Il2CppMethodPointer ptr, [[maybe_unused]] const MethodInfo* m, [[maybe_unused]] void* obj, void** args, void* retval) { // We also don't need to use anything from m so it is ignored. // Perhaps far in the future we will check attributes on it - auto func = reinterpret_cast(ptr); auto seq = std::make_index_sequence(); - return static_invoke(func, args, seq); + // post unity update delegates changed which use this invoke method + // they get passed a nullptr ptr arg, so if they do we just take the method pointer from the method info instead! + auto func = ptr ? reinterpret_cast(ptr) : reinterpret_cast(m->methodPointer); + + static_invoke(func, args, seq, retval); } [[gnu::noinline]] - static void* invoke_method(Il2CppMethodPointer ptr, const MethodInfo* m, [[maybe_unused]] void* obj, void** args) { + static void* invoke_method(Il2CppMethodPointer ptr, const MethodInfo* m, [[maybe_unused]] void* obj, void** args, void* retval) { auto func = reinterpret_cast(ptr); auto seq = std::make_index_sequence(); - return static_invoke_method(func, args, m, seq); + static_invoke_method(func, args, m, seq, retval); } }; } diff --git a/shared/util.hpp b/shared/util.hpp index 39341d7..26529d9 100644 --- a/shared/util.hpp +++ b/shared/util.hpp @@ -1,5 +1,7 @@ #pragma once #include +#include "beatsaber-hook/shared/utils/type-concepts.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" namespace custom_types { template @@ -30,4 +32,53 @@ namespace custom_types { std::vector ExtractClasses() { return {classof(TArgs)...}; } -} \ No newline at end of file + template + struct field_accessor { + inline constexpr T const* field_addr(const void* instance, std::size_t offset) const noexcept { + return static_cast(static_cast(static_cast(instance) + offset)); + } + + inline constexpr T* field_addr(void* instance, std::size_t offset) const noexcept { + return static_cast(static_cast(static_cast(instance) + offset)); + } + + inline constexpr T& read(void* instance, std::size_t offset) const noexcept { + return *field_addr(instance, offset); + } + + inline constexpr T const& read(void const* instance, std::size_t offset) const noexcept { + return *field_addr(instance, offset); + } + + inline constexpr void write(void* instance, std::size_t offset, T&& v) const noexcept { + *field_addr(instance, offset) = v; + } + }; + + template + struct field_accessor { + inline constexpr void* field_addr(void* instance, std::size_t offset) const noexcept { + return static_cast(static_cast(instance) + offset); + } + + inline constexpr void const* field_addr(void const* instance, std::size_t offset) const noexcept { + return static_cast(static_cast(instance) + offset); + } + + inline constexpr T& read(void* instance, std::size_t offset) const noexcept { + return *static_cast(field_addr(instance, offset)); + } + + inline constexpr T const& read(void const* instance, std::size_t offset) const noexcept { + return *static_cast(field_addr(instance, offset)); + } + + inline void write(void* instance, std::size_t offset, T&& v) const noexcept { + il2cpp_functions::gc_wbarrier_set_field( + static_cast(instance), + static_cast(field_addr(instance, offset)), + il2cpp_utils::il2cpp_reference_type_value(std::forward(v)) + ); + } + }; +} diff --git a/src/logging.cpp b/src/logging.cpp index 464fa8e..33d1064 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -96,13 +96,16 @@ void logImage(const Il2CppImage* img) { _logger().debug("name: %s", img->name ? img->name : "NULL"); _logger().debug("nameNoExt: %s", img->nameNoExt ? img->nameNoExt : "NULL"); logAsm(img->assembly); - _logger().debug("typeStart: %u", img->typeStart); + + // as seen in GlobalMetadata::GetImageMetadata + auto metadata = reinterpret_cast(img->metadataHandle); + _logger().debug("typeStart: %u", metadata->typeStart); _logger().debug("typeCount: %u", img->typeCount); - _logger().debug("exportedTypeStart: %u", img->exportedTypeStart); + _logger().debug("exportedTypeStart: %u", metadata->exportedTypeStart); _logger().debug("exportedTypeCount: %u", img->exportedTypeCount); - _logger().debug("customAttributeStart: %u", img->customAttributeStart); + _logger().debug("customAttributeStart: %u", metadata->customAttributeStart); _logger().debug("customAttributeCount: %u", img->customAttributeCount); - _logger().debug("entryPointIndex: %u", img->entryPointIndex); + _logger().debug("entryPointIndex: %u", metadata->entryPointIndex); _logger().debug("nameToClassHashTable: %p", img->nameToClassHashTable); logCodegen(img->codeGenModule, "codeGenModule"); _logger().debug("token: %u", img->token); @@ -112,7 +115,7 @@ void logImage(const Il2CppImage* img) { void logType(const Il2CppType* t, std::string_view s) { _logger().debug("0 ======================TYPE INFO FOR: %s======================", s.data()); - _logger().debug("klassIndex: %u", t->data.klassIndex); + _logger().debug("klassIndex: %u", t->data.__klassIndex); _logger().debug("attrs: 0x%x", t->attrs); _logger().debug("type: 0x%x", t->type); _logger().debug("num_mods: %u", t->num_mods); @@ -146,17 +149,20 @@ void logInterfaceOffset(const Il2CppRuntimeInterfaceOffsetPair* pair) { _logger().debug("0 ======================END INTERFACE OFFSET======================"); } -void logParam(const ParameterInfo* info) { - if (!info) { +void logParam(const Il2CppType* t, int index) { + if (!t) { _logger().debug("NULL PARAMETER INFO!"); return; } - _logger().debug("0 ======================PARAMETER INFO FOR: %p======================", info); - _logger().debug("name: %s", info->name ? info->name : "NULL"); - _logger().debug("position: %u", info->position); - _logger().debug("token: %u", info->token); - _logger().debug("parameter_type: %p", info->parameter_type); - _logger().debug("0 ======================END PARAMETER INFO======================"); + _logger().debug("0 ======================TYPE INFO FOR PARAM %d:======================", index); + + _logger().debug("klassIndex: %u", t->data.__klassIndex); + _logger().debug("attrs: 0x%x", t->attrs); + _logger().debug("type: 0x%x", t->type); + _logger().debug("num_mods: %u", t->num_mods); + _logger().debug("byref: %u", t->byref); + _logger().debug("pinned: %u", t->pinned); + _logger().debug("0 ======================END PARAM TYPE INFO======================"); } void logMethod(const MethodInfo* info) { @@ -173,14 +179,15 @@ void logMethod(const MethodInfo* info) { _logger().debug("return_type: %p", info->return_type); if (info->parameters) { for (int i = 0; i < info->parameters_count; i++) { - logParam(&info->parameters[i]); + logParam(info->parameters[i], i); } } else { _logger().debug("parameters: 0x0"); } + // _logger().debug("parameters: %p", info->parameters); - _logger().debug("methodDefinition: %p", info->methodDefinition); - _logger().debug("genericContainer: %p", info->genericContainer); + _logger().debug("methodMetadataHandle: %p", info->methodMetadataHandle); + _logger().debug("genericContainerHandle: %p", info->genericContainerHandle); _logger().debug("token: %u", info->token); _logger().debug("flags: 0x%x", info->flags); _logger().debug("iflags: 0x%x", info->iflags); @@ -189,7 +196,6 @@ void logMethod(const MethodInfo* info) { _logger().debug("is_generic: %u", info->is_generic); _logger().debug("is_inflated: %u", info->is_inflated); _logger().debug("wrapper_type: %u", info->wrapper_type); - _logger().debug("is_marshaled_from_native: %u", info->is_marshaled_from_native); _logger().debug("0 ======================END METHOD INFO======================"); } @@ -207,7 +213,7 @@ void logAll(const Il2CppClass* klass) { _logger().debug("declaringType: %p", klass->declaringType); _logger().debug("parent: %p", klass->parent); _logger().debug("generic_class: %p", klass->generic_class); - _logger().debug("typeDefinition: %p", klass->typeDefinition); + _logger().debug("typeMetadataHandle: %p", klass->typeMetadataHandle); _logger().debug("interopData: %p", klass->interopData); _logger().debug("klass: %p", klass->klass); logFields(klass); @@ -229,9 +235,9 @@ void logAll(const Il2CppClass* klass) { _logger().debug("unity_user_data: %p", klass->unity_user_data); _logger().debug("initializationExceptionGCHandle: %u", klass->initializationExceptionGCHandle); _logger().debug("cctor_started: %u", klass->cctor_started); - _logger().debug("cctor_finished: %u", klass->cctor_finished); + _logger().debug("cctor_finished_or_no_cctor: %u", klass->cctor_finished_or_no_cctor); _logger().debug("cctor_thread: %zu", klass->cctor_thread); - _logger().debug("genericContainerIndex: %u", klass->genericContainerIndex); + _logger().debug("genericContainerHandle: %p", klass->genericContainerHandle); _logger().debug("instance_size: %u", klass->instance_size); _logger().debug("actualSize: %u", klass->actualSize); _logger().debug("element_size: %u", klass->element_size); @@ -255,19 +261,20 @@ void logAll(const Il2CppClass* klass) { _logger().debug("naturalAligment: %u", klass->naturalAligment); _logger().debug("packingSize: %u", klass->packingSize); _logger().debug("initialized_and_no_error: %u", klass->initialized_and_no_error); - _logger().debug("valuetype: %u", klass->valuetype); + _logger().debug("nullabletype: %u", klass->nullabletype); _logger().debug("initialized: %u", klass->initialized); _logger().debug("enumtype: %u", klass->enumtype); _logger().debug("is_generic: %u", klass->is_generic); _logger().debug("has_references: %u", klass->has_references); _logger().debug("init_pending: %u", klass->init_pending); + _logger().debug("size_init_pending: %u", klass->size_init_pending); _logger().debug("size_inited: %u", klass->size_inited); _logger().debug("has_finalize: %u", klass->has_finalize); _logger().debug("has_cctor: %u", klass->has_cctor); _logger().debug("is_blittable: %u", klass->is_blittable); _logger().debug("is_import_or_windows_runtime: %u", klass->is_import_or_windows_runtime); _logger().debug("is_vtable_initialized: %u", klass->is_vtable_initialized); - _logger().debug("has_initialization_error: %u", klass->has_initialization_error); + _logger().debug("is_byref_like: %u", klass->is_byref_like); for (uint16_t i = 0; i < klass->interface_offsets_count; i++) { logInterfaceOffset(&klass->interfaceOffsets[i]); } @@ -276,4 +283,4 @@ void logAll(const Il2CppClass* klass) { } _logger().debug("0 ======================END CLASS INFO======================"); } -} // namespace custom_types \ No newline at end of file +} // namespace custom_types diff --git a/src/register.cpp b/src/register.cpp index 709af2c..d3a20ae 100644 --- a/src/register.cpp +++ b/src/register.cpp @@ -15,6 +15,11 @@ #endif +// checks whether the ty->data could be a pointer. technically could be UB if the address is low enough +bool MetadataHandleSet(const Il2CppType* ty) { + return ((uint64_t)ty->data.typeHandle >> 32); +} + template struct Hook_FromIl2CppTypeMain { constexpr static const char* name() { @@ -40,12 +45,13 @@ struct Hook_FromIl2CppTypeMain { logger.warning("FromIl2CppType was given a null Il2CppType*! Returning a null!"); return nullptr; } - bool shouldBeOurs = false; + // preliminary check, if the metadata handle is not set this could be ours + bool shouldBeOurs = !MetadataHandleSet(typ); // klassIndex is only meaningful for these types - if ((typ->type == IL2CPP_TYPE_CLASS || typ->type == IL2CPP_TYPE_VALUETYPE) && typ->data.klassIndex < 0) { + if (shouldBeOurs && (typ->type == IL2CPP_TYPE_CLASS || typ->type == IL2CPP_TYPE_VALUETYPE) && typ->data.__klassIndex < 0) { shouldBeOurs = true; // If the type matches our type - size_t idx = kTypeDefinitionIndexInvalid - typ->data.klassIndex; + size_t idx = kTypeDefinitionIndexInvalid - typ->data.__klassIndex; #ifndef NO_VERBOSE_LOGS logger.debug("Custom idx: %u for type: %p", idx, typ); #endif @@ -63,7 +69,7 @@ struct Hook_FromIl2CppTypeMain { // Otherwise, return orig auto klass = FromIl2CppType(args...); if (shouldBeOurs) { - logger.debug("Called with klassIndex %i which is not our custom type?!", typ->data.klassIndex); + logger.debug("Called with klassIndex %i which is not our custom type?!", typ->data.__klassIndex); il2cpp_utils::LogClass(logger, klass, false); } return klass; @@ -79,22 +85,25 @@ MAKE_HOOK(Class_Init, nullptr, bool, Il2CppClass* klass) { logger.warning("Called with a null Il2CppClass*! (Specifically: %p)", klass); SAFE_ABORT(); } + auto typ = klass->this_arg; - if ((typ.type == IL2CPP_TYPE_CLASS || typ.type == IL2CPP_TYPE_VALUETYPE) && typ.data.klassIndex < 0) { - // This is a custom class. Skip it. - auto idx = kTypeDefinitionIndexInvalid - typ.data.klassIndex; -#ifndef NO_VERBOSE_LOGS - logger.debug("custom idx: %u", idx); -#endif - return true; - } else { - return Class_Init(klass); + if (!MetadataHandleSet(&typ) && (typ.type == IL2CPP_TYPE_CLASS || typ.type == IL2CPP_TYPE_VALUETYPE) && typ.data.__klassIndex < 0) { + auto idx = kTypeDefinitionIndexInvalid - typ.data.__klassIndex; + if (idx < (int)::custom_types::Register::classes.size() && idx >= 0) { + // This is a custom class. Skip it. + #ifndef NO_VERBOSE_LOGS + logger.debug("custom idx: %u", idx); + #endif + return true; + } } + + return Class_Init(klass); } -MAKE_HOOK(MetadataCache_GetTypeInfoFromTypeDefinitionIndex, nullptr, Il2CppClass*, TypeDefinitionIndex index) { +MAKE_HOOK(GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex, nullptr, Il2CppClass*, TypeDefinitionIndex index) { if (index < 0) { - static auto logger = ::custom_types::_logger().WithContext("MetadataCache::GetTypeInfoFromTypeDefinitionIndex"); + static auto logger = ::custom_types::_logger().WithContext("GlobalMetadata::GetTypeInfoFromTypeDefinitionIndex"); // index is either invalid or one of ours size_t idx = kTypeDefinitionIndexInvalid - index; logger.debug("custom idx: %zu", idx); @@ -105,7 +114,7 @@ MAKE_HOOK(MetadataCache_GetTypeInfoFromTypeDefinitionIndex, nullptr, Il2CppClass } } // Otherwise, return orig - return MetadataCache_GetTypeInfoFromTypeDefinitionIndex(index); + return GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex(index); } MAKE_HOOK(GetScriptingClass, nullptr, Il2CppClass*, void* thisptr, char* assembly, char* namespaze, char* name) { @@ -506,10 +515,14 @@ const Il2CppImage* Register::createImage(std::string_view name) { img->nameNoExt = allocNameNoExt; img->dynamic = true; img->assembly = createAssembly(allocNameNoExt, img); - img->nameToClassHashTable = new Il2CppNameToTypeDefinitionIndexHashTable(); + img->nameToClassHashTable = new Il2CppNameToTypeHandleHashTable(); + auto metadata = new Il2CppImageGlobalMetadata(); + metadata->image = img; + img->metadataHandle = reinterpret_cast(metadata); // Types are pushed here on class creation // TODO: Avoid copying eventually - img->exportedTypeStart = 0; + metadata->typeStart = 0; + metadata->exportedTypeStart = 0; img->exportedTypeCount = 0; // Custom attribute start and count is used somewhere within unity // (which makes a call to: @@ -517,9 +530,9 @@ const Il2CppImage* Register::createImage(std::string_view name) { // required to not be undefined (though perhaps a -1 and a 0 would work just // as well here?) RGCTXes are also from codeGenModule, so that must also be // defined. - img->customAttributeStart = 0; + metadata->customAttributeStart = 0; img->customAttributeCount = 0; - img->entryPointIndex = 0; + metadata->entryPointIndex = 0; // TODO: Populate this in a more reasonable way // auto* codegen = new Il2CppCodeGenModule{Il2CppCodeGenModule{ // .moduleName = name.data(), @@ -545,11 +558,9 @@ void Register::EnsureHooks() { } else { Hooking::InstallHookDirect>(logger, (void*)il2cpp_functions::il2cpp_Class_FromIl2CppType); } - INSTALL_HOOK_DIRECT(logger, MetadataCache_GetTypeInfoFromTypeDefinitionIndex, (void*)il2cpp_functions::il2cpp_MetadataCache_GetTypeInfoFromTypeDefinitionIndex); + INSTALL_HOOK_DIRECT(logger, GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex, (void*)il2cpp_functions::il2cpp_GlobalMetadata_GetTypeInfoFromTypeDefinitionIndex); INSTALL_HOOK_DIRECT(logger, Class_Init, (void*)il2cpp_functions::il2cpp_Class_Init); - uintptr_t GetScriptingClassAddr = findPattern(baseAddr("libunity.so"), - "ff c3 01 d1 f9 63 03 a9 f7 5b 04 a9 f5 53 05 a9 f3 7b 06 " - "a9 57 d0 3b d5 e8 16 40 f9 f6 03 01 aa"); + uintptr_t GetScriptingClassAddr = findPattern(baseAddr("libunity.so"), "ff 43 02 d1 fa 23 00 f9 f9 63 05 a9 f7 5b 06 a9 f5 53 07 a9 f3 7b 08 a9 57 d0 3b d5 e8 16 40 f9 f6 03 01 aa"); INSTALL_HOOK_DIRECT(logger, GetScriptingClass, reinterpret_cast(GetScriptingClassAddr)); // { // // We need to do a tiny bit of xref tracing to find the bottom level @@ -615,4 +626,4 @@ void Register::EnsureHooks() { installed = true; } } -} // namespace custom_types \ No newline at end of file +} // namespace custom_types diff --git a/src/type-registration.cpp b/src/type-registration.cpp index 0fa090b..847b1c7 100644 --- a/src/type-registration.cpp +++ b/src/type-registration.cpp @@ -21,8 +21,8 @@ namespace custom_types { // TODO: Change this for value types and other type enums type->type = typeEnum(); // This should be a unique number, assigned when each new type is created. - type->data.klassIndex = Register::typeIdx--; - _logger().debug("Made new type: %p, idx: %i", type, type->data.klassIndex); + type->data.__klassIndex = Register::typeIdx--; + _logger().debug("Made new type: %p, idx: %i", type, type->data.__klassIndex); return type; } @@ -120,6 +120,43 @@ namespace custom_types { } return vtableSize; } + std::list collect_parent_ifs(Il2CppClass* intf); + std::list collect_parent_ifs(std::list intfs); + + std::list collect_parent_ifs(Il2CppClass* intf) { + if (!intf->initialized) il2cpp_functions::Class_Init(intf); + + auto ifs = std::list(intf->implementedInterfaces, intf->implementedInterfaces + intf->interfaces_count); + + if (intf->parent) { + auto parents = collect_parent_ifs(ifs); + ifs.insert(ifs.begin(), parents.begin(), parents.end()); + } + + return ifs; + } + + std::list collect_parent_ifs(std::list intfs) { + std::list ifs; + + for (auto intf : intfs) { + auto implemented = collect_parent_ifs(intf); + ifs.insert(ifs.begin(), implemented.begin(), implemented.end()); + } + + return ifs; + } + + std::list collect_parent_ifs(std::vector intfs) { + std::list ifs; + + for (auto intf : intfs) { + auto implemented = collect_parent_ifs(intf); + ifs.insert(ifs.begin(), implemented.begin(), implemented.end()); + } + + return ifs; + } void TypeRegistration::createClass() { // Check to see if we have already created our class. If we have, use that. @@ -156,7 +193,7 @@ namespace custom_types { auto img = Register::createImage(dllName()); k->image = img; // Add ourselves to our image hash table (for class_from_name) - img->nameToClassHashTable->insert(std::make_pair(std::make_pair(namespaze(), name()), type->data.klassIndex)); + img->nameToClassHashTable->insert(std::make_pair(std::make_pair(namespaze(), name()), type->data.typeHandle)); // Set name k->name = name(); k->namespaze = namespaze(); @@ -180,27 +217,46 @@ namespace custom_types { k->size_inited = 1; // Methods are set after processing methods auto intfs = interfaces(); - k->interfaces_count = intfs.size(); + auto all_intfs = collect_parent_ifs(intfs); + k->interfaces_count = all_intfs.size(); + + // if these don't match, we actually have an interface that was not implemented + if (intfs.size() != all_intfs.size()) { + // scan for any interfaces the modder might have missed, since on quest all parent interfaces have to be implemented for things to work properly + for (auto parent : all_intfs) { + if (!parent) continue; + if (!il2cpp_functions::class_is_interface(parent)) continue; + auto itr = std::find(intfs.begin(), intfs.end(), parent); + if (itr == intfs.end()) { + _logger().warning("Parent interface %s::%s did not appear in requested interfaces of type %s::%s, but was found in a recursive search!", parent->namespaze, parent->name, namespaze(), name()); + _logger().warning("This interface was subsequently added to the list of implemented interfaces, make sure to also implement parent interfaces!"); + _logger().warning("(In some cases this will never crash, but still cause weird behaviour)"); + } + } + } + // k->implementedInterfaces needs to be allocated as well - k->implementedInterfaces = reinterpret_cast(calloc(intfs.size(), sizeof(Il2CppClass*))); - for (size_t i = 0; i < intfs.size(); i++) { - k->implementedInterfaces[i] = intfs[i]; + k->implementedInterfaces = reinterpret_cast(calloc(all_intfs.size(), sizeof(Il2CppClass*))); + for (size_t i = 0; auto intf : all_intfs) { + k->implementedInterfaces[i++] = intf; } + // TODO: Figure out generic class (will also need to inflate it) k->generic_class = nullptr; - k->genericContainerIndex = kGenericContainerIndexInvalid; + k->genericContainerHandle = 0; k->genericRecursionDepth = 1; // Pretend that the class has already been initialized k->initialized = 1; k->initialized_and_no_error = 1; k->init_pending = 0; - k->has_initialization_error = 0; + k->size_init_pending = 0; // TypeDefinition unused, can set to nullptr - k->typeDefinition = nullptr; - if (type->type == Il2CppTypeEnum::IL2CPP_TYPE_VALUETYPE) { - k->valuetype = true; + k->typeMetadataHandle = nullptr; + if (type->type != Il2CppTypeEnum::IL2CPP_TYPE_VALUETYPE) { + k->nullabletype = true; } + k->is_byref_like = 0; // TODO: is this valid? k->token = -1; // TODO: See if this is always the case @@ -299,7 +355,8 @@ namespace custom_types { // This SHOULD be good enough for Finalize. if (info->virtualMethod()) { auto* k = info->virtualMethod()->klass; - return namespaze == k->namespaze && name == k->name && methodName == info->csharpName() && (paramCount >= 0 ? info->params_size() == paramCount : true); + // pure virtuals (abstract) have a vMethod->klass (& other data) that is null, but those can never be Finalize. therefore we just skip the rest of the checks if !k + return k && namespaze == k->namespaze && name == k->name && methodName == info->csharpName() && (paramCount >= 0 ? info->params_size() == paramCount : true); } return false; } @@ -368,6 +425,7 @@ namespace custom_types { for (auto m : methods) { auto* vMethod = m->virtualMethod(); if (vMethod != nullptr) { + logger.info("Handling override method %s", m->name()); bool set = false; if (vMethod->slot < 0) { logger.critical("Virtual data: %p has slot: %u which is invalid!", vMethod, vMethod->slot); @@ -397,7 +455,9 @@ namespace custom_types { // If it is, then we use that type's slot auto* b = baseT; while (b != nullptr) { - if (vMethod->klass == b) { + // pure virtuals (abstract) have a vMethod->klass (& other data) that is null + // TODO: verify whether this is correct behaviour! + if (vMethod->klass == b || !vMethod->klass) { logger.debug("Matching base type: %p for method: %p", b, vMethod); // We are implementing an abstract method from our abstract base. Use the virtual_data's slot exactly. logger.debug("Using base slot: %u for method: %p", vMethod->slot, m->get()); @@ -458,7 +518,7 @@ namespace custom_types { if (!set) { // We should be implementing the interface. // If we reach here, we don't implement or extend the virtual method we want to implement. - logger.critical("Method: %p needs virtual_data: %p which requires type: %p which does not exist!", m, vMethod, vMethod->klass); + logger.critical("Method: %s (%p) needs virtual_data: %p which requires type: %p which does not exist!", m->csharpName(), m, vMethod, vMethod->klass); logger.critical("Ensure all of your virtual methods' types are defined in the interfaces in DECLARE_CLASS_INTERFACES!"); SAFE_ABORT(); } @@ -532,7 +592,7 @@ namespace custom_types { } if (k->methods) { for (uint16_t i = 0; i < k->method_count; ++i) { - free(const_cast(k->methods[i]->parameters)); + free(const_cast(k->methods[i]->parameters)); delete k->methods[i]; } free(k->methods); @@ -549,4 +609,4 @@ namespace custom_types { } } } -} \ No newline at end of file +}