From 28c91f762ed44d391e069976ae6b7525542e3aae Mon Sep 17 00:00:00 2001 From: ConfiG Date: Mon, 15 Jan 2024 00:42:04 +0300 Subject: [PATCH 1/8] refactor Hook/Patch --- loader/include/Geode/loader/Hook.hpp | 174 ++++++++++++------------- loader/include/Geode/loader/Mod.hpp | 67 +++++----- loader/include/Geode/modify/Modify.hpp | 7 +- loader/include/Geode/utils/general.hpp | 7 + loader/src/loader/Hook.cpp | 62 ++++----- loader/src/loader/HookImpl.cpp | 170 ++++++++++++------------ loader/src/loader/HookImpl.hpp | 50 ++++--- loader/src/loader/LoaderImpl.cpp | 31 +---- loader/src/loader/LoaderImpl.hpp | 4 +- loader/src/loader/Mod.cpp | 32 ++--- loader/src/loader/ModImpl.cpp | 147 ++++++++++----------- loader/src/loader/ModImpl.hpp | 16 ++- loader/src/loader/ModPatch.cpp | 25 ++++ loader/src/loader/ModPatch.hpp | 22 ++++ loader/src/loader/Patch.cpp | 73 +++++------ loader/src/loader/PatchImpl.cpp | 69 ++++++++++ loader/src/loader/PatchImpl.hpp | 31 +++++ 17 files changed, 546 insertions(+), 441 deletions(-) create mode 100644 loader/src/loader/ModPatch.cpp create mode 100644 loader/src/loader/ModPatch.hpp create mode 100644 loader/src/loader/PatchImpl.cpp create mode 100644 loader/src/loader/PatchImpl.hpp diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp index eb11ddfc..da6e42e6 100644 --- a/loader/include/Geode/loader/Hook.hpp +++ b/loader/include/Geode/loader/Hook.hpp @@ -4,7 +4,7 @@ #include "../utils/general.hpp" #include #include "Tulip.hpp" -#include +#include #include #include @@ -16,21 +16,17 @@ namespace geode { private: class Impl; std::shared_ptr m_impl; - Hook(std::shared_ptr&& impl); + explicit Hook(std::shared_ptr&& impl); ~Hook(); friend class Mod; friend class Loader; - Result<> enable(); - Result<> disable(); - public: /** * Create a hook at an address. The hook is enabled immediately. By * default, the hook is placed at the end of the detour list; however, * this can be controlled using metadata settings. - * @param owner The mod that owns this hook; must be provided * @param address The address to hook * @param detour The detour to run when the hook is hit. The detour's * calling convention should be cdecl @@ -39,10 +35,9 @@ namespace geode { * @param handlerMetadata Metadata for the hook handler * @param hookMetadata Metadata for the hook itself * @returns The created hook, or an error. Make sure to add the created - * hook to the mod that owns it using mod->addHook! + * hook to the mod that owns it using mod->claimHook! */ static Hook* create( - Mod* owner, void* address, void* detour, std::string const& displayName, @@ -50,9 +45,8 @@ namespace geode { tulip::hook::HookMetadata const& hookMetadata ); - template + template static Hook* create( - Mod* owner, void* address, DetourType detour, std::string const& displayName, @@ -64,7 +58,6 @@ namespace geode { .m_abstract = tulip::hook::AbstractFunction::from(detour) }; return Hook::create( - owner, address, reinterpret_cast(detour), displayName, @@ -77,135 +70,130 @@ namespace geode { Hook operator=(Hook const&) = delete; /** - * Get the address of the function hooked. - * @returns Address + * Get the owner of this hook. + * @returns Pointer to the owner's Mod handle. */ - uintptr_t getAddress() const; - - /** - * Get the display name of the function hooked. - * @returns Display name - */ - std::string_view getDisplayName() const; + [[nodiscard]] Mod* getOwner() const; /** * Get whether the hook is enabled or not. * @returns True if enabled, false if not. */ - bool isEnabled() const; + [[nodiscard]] bool isEnabled() const; - /** - * Get the owner of this hook. - * @returns Pointer to the owner's Mod handle. - */ - Mod* getOwner() const; + Result<> enable(); - /** - * Get info about the hook as JSON - * @note For IPC - */ - matjson::Value getRuntimeInfo() const; - - /** - * Get the metadata of the hook. - * @returns Hook metadata - */ - tulip::hook::HookMetadata getHookMetadata() const; - - /** - * Set the metadata of the hook. - * @param metadata Hook metadata - */ - void setHookMetadata(tulip::hook::HookMetadata const& metadata); - - /** - * Get the priority of the hook. - * @returns Priority - */ - int32_t getPriority() const; - - /** - * Set the priority of the hook. - * @param priority Priority - */ - void setPriority(int32_t priority); + Result<> disable(); /** * Get whether the hook should be auto enabled or not. * @returns Auto enable */ - bool getAutoEnable() const; + [[nodiscard]] bool getAutoEnable() const; /** * Set whether the hook should be auto enabled or not. * @param autoEnable Auto enable */ void setAutoEnable(bool autoEnable); + + /** + * Get the address of the function hooked. + * @returns Address + */ + [[nodiscard]] uintptr_t getAddress() const; + + /** + * Get the display name of the function hooked. + * @returns Display name + */ + [[nodiscard]] std::string_view getDisplayName() const; + + /** + * Get info about the hook as JSON + * @note For IPC + */ + [[nodiscard]] matjson::Value getRuntimeInfo() const; + + /** + * Get the metadata of the hook. + * @returns Hook metadata + */ + [[nodiscard]] tulip::hook::HookMetadata getHookMetadata() const; + + /** + * Set the metadata of the hook. + * @param metadata Hook metadata + */ + void setHookMetadata(tulip::hook::HookMetadata const& metadata); + + /** + * Get the priority of the hook. + * @returns Priority + */ + [[nodiscard]] int32_t getPriority() const; + + /** + * Set the priority of the hook. + * @param priority Priority + */ + void setPriority(int32_t priority); }; class GEODE_DLL Patch final { - // Change to private in 2.0.0 - protected: - Mod* m_owner; - void* m_address; - ByteVector m_original; - ByteVector m_patch; - bool m_applied; - bool m_autoEnable; - - // Only allow friend classes to create - // patches. Whatever method created the - // patches should take care of populating - // m_owner, m_address, m_original and - // m_patch. - Patch(); + private: + class Impl; + std::shared_ptr m_impl; + explicit Patch(std::shared_ptr&& impl); ~Patch(); - // no copying - Patch(Patch const&) = delete; - Patch operator=(Patch const&) = delete; - friend class Mod; friend class Loader; public: - /** - * Get the address of the patch. - * @returns Address - */ - uintptr_t getAddress() const; + static Patch* create(void* address, const ByteVector& patch); - /** - * Get whether the patch is applied or not. - * @returns True if applied, false if not. - */ - bool isApplied() const; - - bool apply(); - bool restore(); + Patch(Patch const&) = delete; + Patch operator=(Patch const&) = delete; /** * Get the owner of this patch. * @returns Pointer to the owner's Mod handle. */ - Mod* getOwner() const; + [[nodiscard]] Mod* getOwner() const; /** - * Get info about the patch as JSON - * @note For IPC + * Get whether the patch is enabled or not. + * @returns True if enabled, false if not. */ - matjson::Value getRuntimeInfo() const; + [[nodiscard]] bool isEnabled() const; + + Result<> enable(); + + Result<> disable(); /** * Get whether the patch should be auto enabled or not. * @returns Auto enable */ - bool getAutoEnable() const; + [[nodiscard]] bool getAutoEnable() const; /** * Set whether the patch should be auto enabled or not. * @param autoEnable Auto enable */ void setAutoEnable(bool autoEnable); + + /** + * Get the address of the patch. + * @returns Address + */ + [[nodiscard]] uintptr_t getAddress() const; + + /** + * Get info about the patch as JSON + * @note For IPC + */ + [[nodiscard]] matjson::Value getRuntimeInfo() const; }; } diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index a87b1f67..82320363 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -229,12 +229,6 @@ namespace geode { return sharedMod<>; } - /** - * Get all hooks owned by this Mod - * @returns Vector of hooks - */ - std::vector getHooks() const; - /** * Create a hook at an address. Call the original * function by calling the original function – @@ -250,38 +244,36 @@ namespace geode { * Hook pointer, errorful result with info on * error */ - template - Result addHook( + template + Result hook( void* address, DetourType detour, std::string const& displayName = "", tulip::hook::TulipConvention convention = tulip::hook::TulipConvention::Default, tulip::hook::HookMetadata const& hookMetadata = tulip::hook::HookMetadata() ) { - auto hook = Hook::create(this, address, detour, displayName, convention, hookMetadata); - return this->addHook(hook); + auto hook = Hook::create(address, detour, displayName, convention, hookMetadata); + GEODE_UNWRAP(this->claimHook(hook)); + return Ok(hook); } - Result addHook(Hook* hook); + Result hook( + void* address, void* detour, std::string const& displayName, + tulip::hook::HandlerMetadata const& handlerMetadata, + tulip::hook::HookMetadata const& hookMetadata + ) { + auto hook = Hook::create(address, detour, displayName, handlerMetadata, hookMetadata); + GEODE_UNWRAP(this->claimHook(hook)); + return Ok(hook); + } + + Result<> claimHook(Hook* hook); + + Result<> disownHook(Hook* hook); /** - * Enable a hook owned by this Mod - * @returns Successful result on success, - * errorful result with info on error + * Get all hooks owned by this Mod + * @returns Vector of hooks */ - Result<> enableHook(Hook* hook); - - /** - * Disable a hook owned by this Mod - * @returns Successful result on success, - * errorful result with info on error - */ - Result<> disableHook(Hook* hook); - - /** - * Remove a hook owned by this Mod - * @returns Successful result on success, - * errorful result with info on error - */ - Result<> removeHook(Hook* hook); + [[nodiscard]] std::vector getHooks() const; /** * Write a patch at an address @@ -290,14 +282,21 @@ namespace geode { * @returns Successful result on success, * errorful result with info on error */ - Result patch(void* address, ByteVector const& data); + Result patch(void* address, ByteVector const& data) { + auto patch = Patch::create(address, data); + GEODE_UNWRAP(this->claimPatch(patch)); + return Ok(patch); + } + + Result<> claimPatch(Patch* patch); + + Result<> disownPatch(Patch* patch); /** - * Remove a patch owned by this Mod - * @returns Successful result on success, - * errorful result with info on error + * Get all patches owned by this Mod + * @returns Vector of patches */ - Result<> unpatch(Patch* patch); + [[nodiscard]] std::vector getPatches() const; /** * Enable this mod diff --git a/loader/include/Geode/modify/Modify.hpp b/loader/include/Geode/modify/Modify.hpp index 2cd0591d..504d2988 100644 --- a/loader/include/Geode/modify/Modify.hpp +++ b/loader/include/Geode/modify/Modify.hpp @@ -21,7 +21,6 @@ break; \ } \ auto hook = Hook::create( \ - Mod::get(), \ reinterpret_cast(address), \ AsStaticFunction_##FunctionName_< \ Derived, \ @@ -38,7 +37,6 @@ if constexpr (HasConstructor) { \ static auto address = AddressInline_; \ auto hook = Hook::create( \ - Mod::get(), \ reinterpret_cast(address), \ AsStaticFunction_##constructor< \ Derived, \ @@ -55,7 +53,6 @@ if constexpr (HasDestructor) { \ static auto address = AddressInline_; \ auto hook = Hook::create( \ - Mod::get(), \ reinterpret_cast(address), \ AsStaticFunction_##destructor::func(&Derived::destructor))>::value, \ #ClassName_ "::" #ClassName_, \ @@ -98,9 +95,9 @@ namespace geode::modifier { test->ModifyDerived::apply(); ModifyDerived::Derived::onModify(*this); for (auto& [uuid, hook] : m_hooks) { - auto res = Mod::get()->addHook(hook); + auto res = Mod::get()->claimHook(hook); if (!res) { - log::error("Failed to add hook {}: {}", hook->getDisplayName(), res.error()); + log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.error()); } } } diff --git a/loader/include/Geode/utils/general.hpp b/loader/include/Geode/utils/general.hpp index b4209af2..fac47094 100644 --- a/loader/include/Geode/utils/general.hpp +++ b/loader/include/Geode/utils/general.hpp @@ -123,6 +123,13 @@ namespace geode { } } +template<> +struct matjson::Serialize { + static matjson::Value to_json(geode::ByteVector const& bytes) { + return matjson::Array(bytes.begin(), bytes.end()); + } +}; + namespace geode::utils::clipboard { GEODE_DLL bool write(std::string const& data); GEODE_DLL std::string read(); diff --git a/loader/src/loader/Hook.cpp b/loader/src/loader/Hook.cpp index c314f53a..7dc5ffb3 100644 --- a/loader/src/loader/Hook.cpp +++ b/loader/src/loader/Hook.cpp @@ -1,30 +1,43 @@ #include -#include -#include -#include -#include -#include -#include "ModImpl.hpp" #include "HookImpl.hpp" using namespace geode::prelude; Hook::Hook(std::shared_ptr&& impl) : m_impl(std::move(impl)) {} -Hook::~Hook() {} +Hook::~Hook() = default; -// These classes (Hook and Patch) are nasty using new and delete, change them in 2.0.0 Hook* Hook::create( - Mod* owner, void* address, void* detour, std::string const& displayName, tulip::hook::HandlerMetadata const& handlerMetadata, tulip::hook::HookMetadata const& hookMetadata ) { - auto impl = std::make_shared( - address, detour, displayName, handlerMetadata, hookMetadata, owner - ); - return new Hook(std::move(impl)); + return Impl::create(address, detour, displayName, handlerMetadata, hookMetadata); +} + +Mod* Hook::getOwner() const { + return m_impl->getOwner(); +} + +bool Hook::isEnabled() const { + return m_impl->isEnabled(); +} + +Result<> Hook::enable() { + return m_impl->enable(); +} + +Result<> Hook::disable() { + return m_impl->disable(); +} + +bool Hook::getAutoEnable() const { + return m_impl->getAutoEnable(); +} + +void Hook::setAutoEnable(bool autoEnable) { + return m_impl->setAutoEnable(autoEnable); } uintptr_t Hook::getAddress() const { @@ -35,14 +48,6 @@ std::string_view Hook::getDisplayName() const { return m_impl->getDisplayName(); } -bool Hook::isEnabled() const { - return m_impl->isEnabled(); -} - -Mod* Hook::getOwner() const { - return m_impl->getOwner(); -} - matjson::Value Hook::getRuntimeInfo() const { return m_impl->getRuntimeInfo(); } @@ -62,18 +67,3 @@ int32_t Hook::getPriority() const { void Hook::setPriority(int32_t priority) { return m_impl->setPriority(priority); } - -bool Hook::getAutoEnable() const { - return m_impl->getAutoEnable(); -} - -void Hook::setAutoEnable(bool autoEnable) { - return m_impl->setAutoEnable(autoEnable); -} - -Result<> Hook::enable() { - return m_impl->enable(); -} -Result<> Hook::disable() { - return m_impl->disable(); -} \ No newline at end of file diff --git a/loader/src/loader/HookImpl.cpp b/loader/src/loader/HookImpl.cpp index 49ee5495..e11bf1be 100644 --- a/loader/src/loader/HookImpl.cpp +++ b/loader/src/loader/HookImpl.cpp @@ -1,15 +1,19 @@ #include "HookImpl.hpp" + +#include #include "LoaderImpl.hpp" -Hook::Impl::Impl(void* address, void* detour, std::string const& displayName, tulip::hook::HandlerMetadata const& handlerMetadata, tulip::hook::HookMetadata const& hookMetadata, Mod* owner) : +Hook::Impl::Impl( + void* address, + void* detour, + std::string displayName, + tulip::hook::HandlerMetadata handlerMetadata, + tulip::hook::HookMetadata const& hookMetadata) : m_address(address), m_detour(detour), - m_displayName(displayName), - m_handlerMetadata(handlerMetadata), - m_hookMetadata(hookMetadata), - m_owner(owner), - m_enabled(false), - m_autoEnable(true) {} + m_displayName(std::move(displayName)), + m_handlerMetadata(std::move(handlerMetadata)), + m_hookMetadata(hookMetadata) {} Hook::Impl::~Impl() { if (m_enabled) { auto res = this->disable(); @@ -17,20 +21,82 @@ Hook::Impl::~Impl() { log::error("Failed to disable hook: {}", res.unwrapErr()); } } + if (m_owner) { + auto res = m_owner->disownHook(m_self); + if (!res) { + log::error("Failed to disown hook: {}", res.unwrapErr()); + } + } +} + +Hook* Hook::Impl::create( + void* address, + void* detour, + std::string const& displayName, + tulip::hook::HandlerMetadata const& handlerMetadata, + tulip::hook::HookMetadata const& hookMetadata +) { + auto impl = std::make_shared( + address, detour, displayName, handlerMetadata, hookMetadata + ); + auto hook = new Hook(std::move(impl)); + impl->m_self = hook; + return hook; +} + +Result<> Hook::Impl::enable() { + if (m_enabled) { + return Ok(); + } + + // During a transition between updates when it's important to get a + // non-functional version that compiles, address 0x9999999 is used to mark + // functions not yet RE'd but that would prevent compilation + if ((uintptr_t)m_address == (geode::base::get() + 0x9999999)) { + if (m_owner) { + log::warn( + "Hook {} for {} uses placeholder address, refusing to hook", + m_displayName, m_owner->getID() + ); + } + else { + log::warn("Hook {} uses placeholder address, refusing to hook", m_displayName); + } + return Ok(); + } + + GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getOrCreateHandler(m_address, m_handlerMetadata)); + m_handle = tulip::hook::createHook(handler, m_detour, m_hookMetadata); + m_enabled = true; + + if (m_owner) { + log::debug("Enabled {} hook at {} for {}", m_displayName, m_address, m_owner->getID()); + } + else { + log::debug("Enabled {} hook at {}", m_displayName, m_address); + } + + return Ok(); +} + +Result<> Hook::Impl::disable() { + if (!m_enabled) + return Ok(); + GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address)); + tulip::hook::removeHook(handler, m_handle); + m_enabled = false; + log::debug("Disabled {} hook", m_displayName); + return Ok(); } uintptr_t Hook::Impl::getAddress() const { return reinterpret_cast(m_address); } + std::string_view Hook::Impl::getDisplayName() const { return m_displayName; } -bool Hook::Impl::isEnabled() const { - return m_enabled; -} -Mod* Hook::Impl::getOwner() const { - return m_owner; -} + matjson::Value Hook::Impl::getRuntimeInfo() const { auto json = matjson::Object(); json["address"] = std::to_string(reinterpret_cast(m_address)); @@ -39,92 +105,34 @@ matjson::Value Hook::Impl::getRuntimeInfo() const { json["enabled"] = m_enabled; return json; } + tulip::hook::HookMetadata Hook::Impl::getHookMetadata() const { return m_hookMetadata; } -Result<> Hook::Impl::enable() { - if (m_enabled) { - return Ok(); - } - - // During a transition between updates when it's important to get a - // non-functional version that compiles, address 0x9999999 is used to mark - // functions not yet RE'd but that would prevent compilation - if ((uintptr_t)m_address == (geode::base::get() + 0x9999999)) { - if (m_owner) { - log::debug( - "Hook {} for {} uses placeholder address, refusing to hook", - m_displayName, m_owner->getID() - ); - } - else { - log::debug("Hook {} uses placeholder address, refusing to hook", m_displayName); - } - return Ok(); - } - - if (!LoaderImpl::get()->hasHandler(m_address)) { - GEODE_UNWRAP(LoaderImpl::get()->createHandler(m_address, m_handlerMetadata)); - } - GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address)); - - m_handle = tulip::hook::createHook(handler, m_detour, m_hookMetadata); - if (m_owner) { - log::debug("Enabled {} hook at {} for {}", m_displayName, m_address, m_owner->getID()); - } - else { - log::debug("Enabled {} hook at {}", m_displayName, m_address); - } - - m_enabled = true; - return Ok(); -} - -Result<> Hook::Impl::disable() { - if (!m_enabled) - return Ok(); - - GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address)); - - tulip::hook::removeHook(handler, m_handle); - - log::debug("Disabled {} hook", m_displayName); - - m_enabled = false; - return Ok(); -} - -Result<> Hook::Impl::updateMetadata() { - if (m_enabled) { - GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address)); - - tulip::hook::updateHookMetadata(handler, m_handle, m_hookMetadata); - } - return Ok(); -} void Hook::Impl::setHookMetadata(tulip::hook::HookMetadata const& metadata) { m_hookMetadata = metadata; - auto res = this->updateMetadata(); + auto res = this->updateHookMetadata(); if (!res) { log::error("Failed to update hook metadata: {}", res.unwrapErr()); } } + int32_t Hook::Impl::getPriority() const { return m_hookMetadata.m_priority; } + void Hook::Impl::setPriority(int32_t priority) { m_hookMetadata.m_priority = priority; - auto res = this->updateMetadata(); + auto res = this->updateHookMetadata(); if (!res) { log::error("Failed to update hook priority: {}", res.unwrapErr()); } } -bool Hook::Impl::getAutoEnable() const { - return m_autoEnable; -} - -void Hook::Impl::setAutoEnable(bool autoEnable) { - m_autoEnable = autoEnable; +Result<> Hook::Impl::updateHookMetadata() { + if (!m_enabled) return Ok(); + GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address)); + tulip::hook::updateHookMetadata(handler, m_handle, m_hookMetadata); + return Ok(); } diff --git a/loader/src/loader/HookImpl.hpp b/loader/src/loader/HookImpl.hpp index aa309055..e9258ac0 100644 --- a/loader/src/loader/HookImpl.hpp +++ b/loader/src/loader/HookImpl.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -5,47 +7,59 @@ #include #include #include "ModImpl.hpp" +#include "ModPatch.hpp" using namespace geode::prelude; -class Hook::Impl { +class Hook::Impl final : ModPatch { public: Impl( void* address, void* detour, - std::string const& displayName, - tulip::hook::HandlerMetadata const& handlerMetadata, - tulip::hook::HookMetadata const& hookMetadata, - Mod* owner + std::string displayName, + tulip::hook::HandlerMetadata handlerMetadata, + tulip::hook::HookMetadata const& hookMetadata ); ~Impl(); - + static Hook* create( + void* address, + void* detour, + std::string const& displayName, + tulip::hook::HandlerMetadata const& handlerMetadata, + tulip::hook::HookMetadata const& hookMetadata + ); + + template + static Hook* create( + void* address, + DetourType detour, + std::string const& displayName, + tulip::hook::TulipConvention convention, + tulip::hook::HookMetadata const& hookMetadata + ); + + Hook* m_self = nullptr; void* m_address; void* m_detour; std::string m_displayName; tulip::hook::HandlerMetadata m_handlerMetadata; tulip::hook::HookMetadata m_hookMetadata; - Mod* m_owner; - tulip::hook::HookHandle m_handle; - bool m_enabled = false; - bool m_autoEnable = true; + tulip::hook::HookHandle m_handle = 0; - - // Used by Mod Result<> enable(); Result<> disable(); - Result<> updateMetadata(); uintptr_t getAddress() const; std::string_view getDisplayName() const; - bool isEnabled() const; - Mod* getOwner() const; matjson::Value getRuntimeInfo() const; tulip::hook::HookMetadata getHookMetadata() const; void setHookMetadata(tulip::hook::HookMetadata const& metadata); int32_t getPriority() const; void setPriority(int32_t priority); - bool getAutoEnable() const; - void setAutoEnable(bool autoEnable); -}; \ No newline at end of file + + Result<> updateHookMetadata(); + + friend class Hook; + friend class Mod; +}; diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp index d97b676e..5a14e0ce 100644 --- a/loader/src/loader/LoaderImpl.cpp +++ b/loader/src/loader/LoaderImpl.cpp @@ -681,7 +681,7 @@ bool Loader::Impl::loadHooks() { m_readyToHook = true; bool hadErrors = false; for (auto const& [hook, mod] : m_uninitializedHooks) { - auto res = mod->addHook(hook); + auto res = hook->enable(); if (!res) { log::logImpl(Severity::Error, mod, "{}", res.unwrapErr()); hadErrors = true; @@ -728,35 +728,18 @@ void Loader::Impl::releaseNextMod() { m_nextModLock.unlock(); } -Result<> Loader::Impl::createHandler(void* address, tulip::hook::HandlerMetadata const& metadata) { - if (m_handlerHandles.count(address)) { - return Err("Handler already exists at address"); - } - - GEODE_UNWRAP_INTO(auto handle, tulip::hook::createHandler(address, metadata)); - m_handlerHandles[address] = handle; - return Ok(); -} - -bool Loader::Impl::hasHandler(void* address) { - return m_handlerHandles.count(address) > 0; -} - Result Loader::Impl::getHandler(void* address) { if (!m_handlerHandles.count(address)) { return Err("Handler does not exist at address"); } - return Ok(m_handlerHandles[address]); } -Result<> Loader::Impl::removeHandler(void* address) { - if (!m_handlerHandles.count(address)) { - return Err("Handler does not exist at address"); +Result Loader::Impl::getOrCreateHandler(void* address, tulip::hook::HandlerMetadata const& metadata) { + if (m_handlerHandles.count(address)) { + return Ok(m_handlerHandles[address]); } - - auto handle = m_handlerHandles[address]; - GEODE_UNWRAP(tulip::hook::removeHandler(handle)); - m_handlerHandles.erase(address); - return Ok(); + GEODE_UNWRAP_INTO(auto handle, tulip::hook::createHandler(address, metadata)); + m_handlerHandles[address] = handle; + return Ok(handle); } diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp index f3810aa4..94caad7b 100644 --- a/loader/src/loader/LoaderImpl.hpp +++ b/loader/src/loader/LoaderImpl.hpp @@ -69,10 +69,8 @@ namespace geode { std::unordered_map m_handlerHandles; - Result<> createHandler(void* address, tulip::hook::HandlerMetadata const& metadata); - bool hasHandler(void* address); Result getHandler(void* address); - Result<> removeHandler(void* address); + Result getOrCreateHandler(void* address, tulip::hook::HandlerMetadata const& metadata); bool loadHooks(); diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index c202555e..1617d537 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -121,32 +121,28 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptrregisterCustomSetting(key, std::move(value)); } +Result<> Mod::claimHook(Hook* hook) { + return m_impl->claimHook(hook); +} + +Result<> Mod::disownHook(Hook* hook) { + return m_impl->disownHook(hook); +} + std::vector Mod::getHooks() const { return m_impl->getHooks(); } -Result Mod::addHook(Hook* hook) { - return m_impl->addHook(hook); +Result<> Mod::claimPatch(Patch* patch) { + return m_impl->claimPatch(patch); } -Result<> Mod::enableHook(Hook* hook) { - return m_impl->enableHook(hook); +Result<> Mod::disownPatch(Patch* patch) { + return m_impl->disownPatch(patch); } -Result<> Mod::disableHook(Hook* hook) { - return m_impl->disableHook(hook); -} - -Result<> Mod::removeHook(Hook* hook) { - return m_impl->removeHook(hook); -} - -Result Mod::patch(void* address, ByteVector const& data) { - return m_impl->patch(address, data); -} - -Result<> Mod::unpatch(Patch* patch) { - return m_impl->unpatch(patch); +std::vector Mod::getPatches() const { + return m_impl->getPatches(); } Result<> Mod::enable() { diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index 9d5db133..6b87660d 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -1,6 +1,8 @@ #include "ModImpl.hpp" #include "LoaderImpl.hpp" #include "ModMetadataImpl.hpp" +#include "HookImpl.hpp" +#include "PatchImpl.hpp" #include "about.hpp" #include "console.hpp" @@ -133,6 +135,10 @@ std::vector Mod::Impl::getHooks() const { return m_hooks; } +std::vector Mod::Impl::getPatches() const { + return m_patches; +} + // Settings and saved values Result<> Mod::Impl::loadData() { @@ -328,32 +334,6 @@ Result<> Mod::Impl::loadBinary() { LoaderImpl::get()->releaseNextMod(); - for (auto const& hook : m_hooks) { - if (!hook) { - log::warn("Hook is null in mod \"{}\"", m_metadata.getName()); - continue; - } - if (hook->getAutoEnable()) { - auto res = this->enableHook(hook); - if (!res) { - log::error("Can't enable hook {} for mod {}: {}", hook->getDisplayName(), m_metadata.getID(), res.unwrapErr()); - } - } - } - - for (auto const& patch : m_patches) { - if (!patch) { - log::warn("Patch is null in mod \"{}\"", m_metadata.getName()); - continue; - } - if (patch->getAutoEnable()) { - if (!patch->apply()) { - log::warn("Unable to apply patch at {}", patch->getAddress()); - continue; - } - } - } - m_enabled = true; ModStateEvent(m_self, ModEventType::Loaded).post(); @@ -461,79 +441,90 @@ bool Mod::Impl::depends(std::string_view const id) const { // Hooks -Result<> Mod::Impl::enableHook(Hook* hook) { - auto res = hook->enable(); - if (!res) { - log::error("Can't enable hook {} for mod {}: {}", hook->getDisplayName(), m_metadata.getID(), res.unwrapErr()); +Result<> Mod::Impl::claimHook(Hook* hook) { + auto res1 = hook->m_impl->setOwner(m_self); + if (!res1) { + return Err("Cannot claim hook: {}", res1.unwrapErr()); } + m_hooks.push_back(hook); - return res; -} - -Result<> Mod::Impl::disableHook(Hook* hook) { - return hook->disable(); -} - -Result Mod::Impl::addHook(Hook* hook) { - if (!ranges::contains(m_hooks, [&](auto const& h) { return h == hook; })) - m_hooks.push_back(hook); + if (!this->isEnabled() || !hook->getAutoEnable()) + return Ok(); if (!LoaderImpl::get()->isReadyToHook()) { LoaderImpl::get()->addUninitializedHook(hook, m_self); - return Ok(hook); + return Ok(); } - if (!this->isEnabled() || !hook->getAutoEnable()) - return Ok(hook); - - auto res = this->enableHook(hook); - if (!res) { - delete hook; - return Err("Can't create hook: " + res.unwrapErr()); + auto res2 = hook->enable(); + if (!res2) { + return Err("Cannot enable hook: {}", res2.unwrapErr()); } - return Ok(hook); + return Ok(); } -Result<> Mod::Impl::removeHook(Hook* hook) { - auto res = this->disableHook(hook); - if (res) { - ranges::remove(m_hooks, hook); - delete hook; +Result<> Mod::Impl::disownHook(Hook* hook) { + if (hook->getOwner() != m_self) { + return Err("Cannot disown hook not owned by this mod"); } - return res; + + auto res1 = hook->m_impl->setOwner(nullptr); + if (!res1) { + return Err("Cannot disown hook: {}", res1.unwrapErr()); + } + m_hooks.erase(std::find(m_hooks.begin(), m_hooks.end(), hook)); + + if (!this->isEnabled() || !hook->getAutoEnable()) + return Ok(); + + auto res2 = hook->disable(); + if (!res2) { + return Err("Cannot disable hook: {}", res2.unwrapErr()); + } + + return Ok(); } // Patches -// TODO: replace this with a safe one -static ByteVector readMemory(void* address, size_t amount) { - ByteVector ret; - for (size_t i = 0; i < amount; i++) { - ret.push_back(*reinterpret_cast(reinterpret_cast(address) + i)); +Result<> Mod::Impl::claimPatch(Patch* patch) { + auto res1 = patch->m_impl->setOwner(m_self); + if (!res1) { + return Err("Cannot claim patch: {}", res1.unwrapErr()); } - return ret; + m_patches.push_back(patch); + + if (!this->isEnabled() || !patch->getAutoEnable()) + return Ok(); + + auto res2 = patch->enable(); + if (!res2) { + return Err("Cannot enable patch: {}", res2.unwrapErr()); + } + + return Ok(); } -Result Mod::Impl::patch(void* address, ByteVector const& data) { - auto p = new Patch; - p->m_address = address; - p->m_original = readMemory(address, data.size()); - p->m_owner = m_self; - p->m_patch = data; - if (this->isEnabled() && !p->apply()) { - delete p; - return Err("Unable to enable patch at " + std::to_string(reinterpret_cast(address))); +Result<> Mod::Impl::disownPatch(Patch* patch) { + if (patch->getOwner() != m_self) { + return Err("Cannot disown patch not owned by this mod"); + } + + auto res1 = patch->m_impl->setOwner(nullptr); + if (!res1) { + return Err("Cannot disown patch: {}", res1.unwrapErr()); + } + m_patches.erase(std::find(m_patches.begin(), m_patches.end(), patch)); + + if (!this->isEnabled() || !patch->getAutoEnable()) + return Ok(); + + auto res2 = patch->disable(); + if (!res2) { + return Err("Cannot disable patch: {}", res2.unwrapErr()); } - m_patches.push_back(p); - return Ok(p); -} -Result<> Mod::Impl::unpatch(Patch* patch) { - if (!patch->restore()) - return Err("Unable to restore patch at " + std::to_string(patch->getAddress())); - ranges::remove(m_patches, patch); - delete patch; return Ok(); } diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp index e7b7a0a2..4378aea5 100644 --- a/loader/src/loader/ModImpl.hpp +++ b/loader/src/loader/ModImpl.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "ModPatch.hpp" namespace geode { class Mod::Impl { @@ -112,13 +113,14 @@ namespace geode { SettingValue* getSetting(std::string_view const key) const; void registerCustomSetting(std::string_view const key, std::unique_ptr value); - std::vector getHooks() const; - Result addHook(Hook* hook); - Result<> enableHook(Hook* hook); - Result<> disableHook(Hook* hook); - Result<> removeHook(Hook* hook); - Result patch(void* address, ByteVector const& data); - Result<> unpatch(Patch* patch); + Result<> claimHook(Hook* hook); + Result<> disownHook(Hook* hook); + [[nodiscard]] std::vector getHooks() const; + + Result<> claimPatch(Patch* patch); + Result<> disownPatch(Patch* patch); + [[nodiscard]] std::vector getPatches() const; + Result<> enable(); Result<> disable(); Result<> uninstall(bool deleteSaveData = false); diff --git a/loader/src/loader/ModPatch.cpp b/loader/src/loader/ModPatch.cpp new file mode 100644 index 00000000..fd6627f1 --- /dev/null +++ b/loader/src/loader/ModPatch.cpp @@ -0,0 +1,25 @@ +#include "ModPatch.hpp" + +Mod* ModPatch::getOwner() const { + return m_owner; +} + +Result<> ModPatch::setOwner(geode::Mod* mod) { + if (mod && m_owner) { + return Err("Cannot directly replace owner of an already owned mod"); + } + m_owner = mod; + return Ok(); +} + +bool ModPatch::isEnabled() const { + return m_enabled; +} + +bool ModPatch::getAutoEnable() const { + return m_autoEnable; +} + +void ModPatch::setAutoEnable(bool autoEnable) { + m_autoEnable = autoEnable; +} diff --git a/loader/src/loader/ModPatch.hpp b/loader/src/loader/ModPatch.hpp new file mode 100644 index 00000000..65ff2af4 --- /dev/null +++ b/loader/src/loader/ModPatch.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +using namespace geode::prelude; + +class ModPatch { +public: + Mod* m_owner = nullptr; + bool m_enabled = false; + bool m_autoEnable = true; + + [[nodiscard]] Mod* getOwner() const; + Result<> setOwner(Mod* mod); + + [[nodiscard]] bool isEnabled() const; + virtual Result<> enable() = 0; + virtual Result<> disable() = 0; + + [[nodiscard]] bool getAutoEnable() const; + void setAutoEnable(bool autoEnable); +}; diff --git a/loader/src/loader/Patch.cpp b/loader/src/loader/Patch.cpp index c6199ff2..88f4b602 100644 --- a/loader/src/loader/Patch.cpp +++ b/loader/src/loader/Patch.cpp @@ -1,58 +1,43 @@ #include -#include +#include "PatchImpl.hpp" using namespace geode::prelude; -bool Patch::apply() { - bool res = bool(tulip::hook::writeMemory(m_address, m_patch.data(), m_patch.size())); - if (res) - m_applied = true; - return res; -} +Patch::Patch(std::shared_ptr&& impl) : m_impl(std::move(impl)) {} +Patch::~Patch() = default; -bool Patch::restore() { - bool res = bool(tulip::hook::writeMemory(m_address, m_original.data(), m_original.size())); - if (res) - m_applied = false; - return res; -} - -Patch::Patch() : m_owner(nullptr), m_address(nullptr), m_applied(false), m_autoEnable(true) {} - -void Patch::setAutoEnable(bool autoEnable) { - m_autoEnable = autoEnable; -} - -bool Patch::getAutoEnable() const { - return m_autoEnable; -} - -uintptr_t Patch::getAddress() const { - return reinterpret_cast(m_address); -} - -bool Patch::isApplied() const { - return m_applied; +Patch* Patch::create(void* address, const ByteVector& patch) { + return Impl::create(address, patch); } Mod* Patch::getOwner() const { - return m_owner; + return m_impl->getOwner(); } -Patch::~Patch() {} +bool Patch::isEnabled() const { + return m_impl->isEnabled(); +} -template <> -struct matjson::Serialize { - static matjson::Value to_json(ByteVector const& bytes) { - return matjson::Array(bytes.begin(), bytes.end()); - } -}; +Result<> Patch::enable() { + return m_impl->enable(); +} + +Result<> Patch::disable() { + return m_impl->disable(); +} + +bool Patch::getAutoEnable() const { + return m_impl->getAutoEnable(); +} + +void Patch::setAutoEnable(bool autoEnable) { + return m_impl->setAutoEnable(autoEnable); +} + +uintptr_t Patch::getAddress() const { + return m_impl->getAddress(); +} matjson::Value Patch::getRuntimeInfo() const { - auto json = matjson::Object(); - json["address"] = std::to_string(reinterpret_cast(m_address)); - json["original"] = m_original; - json["patch"] = m_patch; - json["applied"] = m_applied; - return json; + return m_impl->getRuntimeInfo(); } diff --git a/loader/src/loader/PatchImpl.cpp b/loader/src/loader/PatchImpl.cpp new file mode 100644 index 00000000..dfe511ae --- /dev/null +++ b/loader/src/loader/PatchImpl.cpp @@ -0,0 +1,69 @@ +#include "PatchImpl.hpp" + +#include +#include "LoaderImpl.hpp" + +Patch::Impl::Impl(void* address, ByteVector original, ByteVector patch) : + m_address(address), + m_original(std::move(original)), + m_patch(std::move(patch)) {} +Patch::Impl::~Impl() { + if (m_enabled) { + auto res = this->disable(); + if (!res) { + log::error("Failed to disable patch: {}", res.unwrapErr()); + } + } + if (m_owner) { + auto res = m_owner->disownPatch(m_self); + if (!res) { + log::error("Failed to disown patch: {}", res.unwrapErr()); + } + } +} + +// TODO: replace this with a safe one +static ByteVector readMemory(void* address, size_t amount) { + ByteVector ret; + for (size_t i = 0; i < amount; i++) { + ret.push_back(*reinterpret_cast(reinterpret_cast(address) + i)); + } + return ret; +} + +Patch* Patch::Impl::create(void* address, const geode::ByteVector& patch) { + auto impl = std::make_shared( + address, readMemory(address, patch.size()), patch + ); + auto p = new Patch(std::move(impl)); + impl->m_self = p; + return p; +} + +Result<> Patch::Impl::enable() { + // TODO: add overlap checking + auto res = tulip::hook::writeMemory(m_address, m_patch.data(), m_patch.size()); + if (!res) return Err(res.unwrapErr()); + m_enabled = true; + return Ok(); +} + +Result<> Patch::Impl::disable() { + auto res = tulip::hook::writeMemory(m_address, m_original.data(), m_original.size()); + if (!res) return Err(res.unwrapErr()); + m_enabled = false; + return Ok(); +} + +uintptr_t Patch::Impl::getAddress() const { + return reinterpret_cast(m_address); +} + +matjson::Value Patch::Impl::getRuntimeInfo() const { + auto json = matjson::Object(); + json["address"] = std::to_string(reinterpret_cast(m_address)); + json["original"] = m_original; + json["patch"] = m_patch; + json["enabled"] = m_enabled; + return json; +} diff --git a/loader/src/loader/PatchImpl.hpp b/loader/src/loader/PatchImpl.hpp new file mode 100644 index 00000000..522284f6 --- /dev/null +++ b/loader/src/loader/PatchImpl.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include "ModImpl.hpp" +#include "ModPatch.hpp" + +using namespace geode::prelude; + +class Patch::Impl final : ModPatch { +public: + Impl(void* address, ByteVector original, ByteVector patch); + ~Impl(); + + static Patch* create(void* address, const ByteVector& patch); + + Patch* m_self = nullptr; + void* m_address; + ByteVector m_original; + ByteVector m_patch; + + Result<> enable(); + Result<> disable(); + + uintptr_t getAddress() const; + matjson::Value getRuntimeInfo() const; + + friend class Patch; + friend class Mod; +}; From 7631b045e1d84ed8209a28bd0a374005239f72b6 Mon Sep 17 00:00:00 2001 From: ConfiG Date: Mon, 15 Jan 2024 00:47:02 +0300 Subject: [PATCH 2/8] fix android --- loader/src/hooks/DynamicCastFix.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/loader/src/hooks/DynamicCastFix.cpp b/loader/src/hooks/DynamicCastFix.cpp index 9119b796..6494f0fb 100644 --- a/loader/src/hooks/DynamicCastFix.cpp +++ b/loader/src/hooks/DynamicCastFix.cpp @@ -13,10 +13,10 @@ $execute { reinterpret_cast(base::get() + 0x603948), toByteArray(&cast::typeinfoCastInternal) ); #elif defined(GEODE_IS_ANDROID32) - (void)Mod::get()->addHook(reinterpret_cast(base::get() + (0x720348 - 0x10000) + 1), &cast::typeinfoCastInternal, "__dynamic_cast"); + (void)Mod::get()->hook(reinterpret_cast(base::get() + (0x720348 - 0x10000) + 1), &cast::typeinfoCastInternal, "__dynamic_cast"); #elif defined(GEODE_IS_ANDROID64) - (void)Mod::get()->addHook(reinterpret_cast(base::get() + (0xd6cb8c - 0x100000)), &cast::typeinfoCastInternal, "__dynamic_cast"); + (void)Mod::get()->hook(reinterpret_cast(base::get() + (0xd6cb8c - 0x100000)), &cast::typeinfoCastInternal, "__dynamic_cast"); #endif -} \ No newline at end of file +} From ab436bd7c398d87f92060dc1f8d1483e07626d48 Mon Sep 17 00:00:00 2001 From: ConfiG Date: Mon, 15 Jan 2024 00:55:18 +0300 Subject: [PATCH 3/8] fix crash yayy --- loader/src/loader/Hook.cpp | 2 +- loader/src/loader/HookImpl.cpp | 4 +--- loader/src/loader/Patch.cpp | 2 +- loader/src/loader/PatchImpl.cpp | 4 +--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/loader/src/loader/Hook.cpp b/loader/src/loader/Hook.cpp index 7dc5ffb3..376b231f 100644 --- a/loader/src/loader/Hook.cpp +++ b/loader/src/loader/Hook.cpp @@ -3,7 +3,7 @@ using namespace geode::prelude; -Hook::Hook(std::shared_ptr&& impl) : m_impl(std::move(impl)) {} +Hook::Hook(std::shared_ptr&& impl) : m_impl(std::move(impl)) { m_impl->m_self = this; } Hook::~Hook() = default; Hook* Hook::create( diff --git a/loader/src/loader/HookImpl.cpp b/loader/src/loader/HookImpl.cpp index e11bf1be..2fab7464 100644 --- a/loader/src/loader/HookImpl.cpp +++ b/loader/src/loader/HookImpl.cpp @@ -39,9 +39,7 @@ Hook* Hook::Impl::create( auto impl = std::make_shared( address, detour, displayName, handlerMetadata, hookMetadata ); - auto hook = new Hook(std::move(impl)); - impl->m_self = hook; - return hook; + return new Hook(std::move(impl)); } Result<> Hook::Impl::enable() { diff --git a/loader/src/loader/Patch.cpp b/loader/src/loader/Patch.cpp index 88f4b602..180d7368 100644 --- a/loader/src/loader/Patch.cpp +++ b/loader/src/loader/Patch.cpp @@ -3,7 +3,7 @@ using namespace geode::prelude; -Patch::Patch(std::shared_ptr&& impl) : m_impl(std::move(impl)) {} +Patch::Patch(std::shared_ptr&& impl) : m_impl(std::move(impl)) { m_impl->m_self = this; } Patch::~Patch() = default; Patch* Patch::create(void* address, const ByteVector& patch) { diff --git a/loader/src/loader/PatchImpl.cpp b/loader/src/loader/PatchImpl.cpp index dfe511ae..db571b98 100644 --- a/loader/src/loader/PatchImpl.cpp +++ b/loader/src/loader/PatchImpl.cpp @@ -35,9 +35,7 @@ Patch* Patch::Impl::create(void* address, const geode::ByteVector& patch) { auto impl = std::make_shared( address, readMemory(address, patch.size()), patch ); - auto p = new Patch(std::move(impl)); - impl->m_self = p; - return p; + return new Patch(std::move(impl)); } Result<> Patch::Impl::enable() { From 165f05fcb41c0cd2f727e8782665fb7b47e98b90 Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:57:43 +0300 Subject: [PATCH 4/8] funny shared_ptr implementation --- loader/include/Geode/loader/Hook.hpp | 12 ++++--- loader/include/Geode/loader/Mod.hpp | 16 ++++----- loader/include/Geode/modify/Modify.hpp | 13 +++++-- loader/include/Geode/utils/ObjcHook.hpp | 4 +-- loader/src/loader/Hook.cpp | 2 +- loader/src/loader/HookImpl.cpp | 6 ++-- loader/src/loader/HookImpl.hpp | 4 +-- loader/src/loader/Mod.cpp | 8 ++--- loader/src/loader/ModImpl.cpp | 48 ++++++++++++++++--------- loader/src/loader/ModImpl.hpp | 8 ++--- loader/src/loader/Patch.cpp | 2 +- loader/src/loader/PatchImpl.cpp | 6 ++-- loader/src/loader/PatchImpl.hpp | 2 +- 13 files changed, 80 insertions(+), 51 deletions(-) diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp index da6e42e6..1f79e9c6 100644 --- a/loader/include/Geode/loader/Hook.hpp +++ b/loader/include/Geode/loader/Hook.hpp @@ -16,13 +16,14 @@ namespace geode { private: class Impl; std::shared_ptr m_impl; - explicit Hook(std::shared_ptr&& impl); ~Hook(); friend class Mod; friend class Loader; public: + explicit Hook(std::shared_ptr&& impl); + /** * Create a hook at an address. The hook is enabled immediately. By * default, the hook is placed at the end of the detour list; however, @@ -37,7 +38,7 @@ namespace geode { * @returns The created hook, or an error. Make sure to add the created * hook to the mod that owns it using mod->claimHook! */ - static Hook* create( + static std::shared_ptr create( void* address, void* detour, std::string const& displayName, @@ -46,7 +47,7 @@ namespace geode { ); template - static Hook* create( + static std::shared_ptr create( void* address, DetourType detour, std::string const& displayName, @@ -144,14 +145,15 @@ namespace geode { private: class Impl; std::shared_ptr m_impl; - explicit Patch(std::shared_ptr&& impl); ~Patch(); friend class Mod; friend class Loader; public: - static Patch* create(void* address, const ByteVector& patch); + explicit Patch(std::shared_ptr&& impl); + + static std::shared_ptr create(void* address, const ByteVector& patch); Patch(Patch const&) = delete; Patch operator=(Patch const&) = delete; diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index 82320363..14aa1959 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -251,8 +251,8 @@ namespace geode { tulip::hook::HookMetadata const& hookMetadata = tulip::hook::HookMetadata() ) { auto hook = Hook::create(address, detour, displayName, convention, hookMetadata); - GEODE_UNWRAP(this->claimHook(hook)); - return Ok(hook); + GEODE_UNWRAP_INTO(auto ptr, this->claimHook(std::move(hook))); + return Ok(ptr); } Result hook( @@ -261,11 +261,11 @@ namespace geode { tulip::hook::HookMetadata const& hookMetadata ) { auto hook = Hook::create(address, detour, displayName, handlerMetadata, hookMetadata); - GEODE_UNWRAP(this->claimHook(hook)); - return Ok(hook); + GEODE_UNWRAP_INTO(auto ptr, this->claimHook(std::move(hook))); + return Ok(ptr); } - Result<> claimHook(Hook* hook); + Result claimHook(std::shared_ptr&& hook); Result<> disownHook(Hook* hook); @@ -284,11 +284,11 @@ namespace geode { */ Result patch(void* address, ByteVector const& data) { auto patch = Patch::create(address, data); - GEODE_UNWRAP(this->claimPatch(patch)); - return Ok(patch); + GEODE_UNWRAP_INTO(auto ptr, this->claimPatch(std::move(patch))); + return Ok(ptr); } - Result<> claimPatch(Patch* patch); + Result claimPatch(std::shared_ptr&& patch); Result<> disownPatch(Patch* patch); diff --git a/loader/include/Geode/modify/Modify.hpp b/loader/include/Geode/modify/Modify.hpp index 504d2988..32e27459 100644 --- a/loader/include/Geode/modify/Modify.hpp +++ b/loader/include/Geode/modify/Modify.hpp @@ -70,13 +70,13 @@ namespace geode::modifier { template class ModifyBase { public: - std::map m_hooks; + std::map> m_hooks; Result getHook(std::string const& name) { if (m_hooks.find(name) == m_hooks.end()) { return Err("Hook not in this modify"); } - return Ok(m_hooks[name]); + return Ok(m_hooks[name].get()); } Result<> setHookPriority(std::string const& name, int32_t priority) { @@ -94,11 +94,18 @@ namespace geode::modifier { auto test = static_cast(this); test->ModifyDerived::apply(); ModifyDerived::Derived::onModify(*this); + std::vector added; for (auto& [uuid, hook] : m_hooks) { - auto res = Mod::get()->claimHook(hook); + auto res = Mod::get()->claimHook(std::move(hook)); if (!res) { log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.error()); } + else { + added.push_back(uuid); + } + } + for (auto& uuid : added) { + m_hooks.erase(uuid); } } diff --git a/loader/include/Geode/utils/ObjcHook.hpp b/loader/include/Geode/utils/ObjcHook.hpp index 7e156777..ec52ae4f 100644 --- a/loader/include/Geode/utils/ObjcHook.hpp +++ b/loader/include/Geode/utils/ObjcHook.hpp @@ -35,7 +35,7 @@ namespace geode { * @returns The created hook, or an error. */ template - static Result create(std::string const& className, std::string const& selectorName, Func function, tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) { + static Result> create(std::string const& className, std::string const& selectorName, Func function, tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) { GEODE_UNWRAP_INTO(auto imp, geode::hook::getObjcMethodImp(className, selectorName)); return Ok(Hook::create( @@ -59,7 +59,7 @@ namespace geode { * @returns The created hook, or an error. */ template - static Result create(std::string const& className, std::string const& selectorName, Func function, void(*empty)(), tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) { + static Result> create(std::string const& className, std::string const& selectorName, Func function, void(*empty)(), tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) { GEODE_UNWRAP(geode::hook::addObjcMethod(className, selectorName, (void*)empty)); return ObjcHook::create(className, selectorName, function, metadata); diff --git a/loader/src/loader/Hook.cpp b/loader/src/loader/Hook.cpp index 376b231f..ae0d6239 100644 --- a/loader/src/loader/Hook.cpp +++ b/loader/src/loader/Hook.cpp @@ -6,7 +6,7 @@ using namespace geode::prelude; Hook::Hook(std::shared_ptr&& impl) : m_impl(std::move(impl)) { m_impl->m_self = this; } Hook::~Hook() = default; -Hook* Hook::create( +std::shared_ptr Hook::create( void* address, void* detour, std::string const& displayName, diff --git a/loader/src/loader/HookImpl.cpp b/loader/src/loader/HookImpl.cpp index 2fab7464..558a1756 100644 --- a/loader/src/loader/HookImpl.cpp +++ b/loader/src/loader/HookImpl.cpp @@ -29,7 +29,7 @@ Hook::Impl::~Impl() { } } -Hook* Hook::Impl::create( +std::shared_ptr Hook::Impl::create( void* address, void* detour, std::string const& displayName, @@ -39,7 +39,9 @@ Hook* Hook::Impl::create( auto impl = std::make_shared( address, detour, displayName, handlerMetadata, hookMetadata ); - return new Hook(std::move(impl)); + return std::shared_ptr(new Hook(std::move(impl)), [](Hook* hook) { + delete hook; + }); } Result<> Hook::Impl::enable() { diff --git a/loader/src/loader/HookImpl.hpp b/loader/src/loader/HookImpl.hpp index e9258ac0..3dbf8cdd 100644 --- a/loader/src/loader/HookImpl.hpp +++ b/loader/src/loader/HookImpl.hpp @@ -22,7 +22,7 @@ public: ); ~Impl(); - static Hook* create( + static std::shared_ptr create( void* address, void* detour, std::string const& displayName, @@ -31,7 +31,7 @@ public: ); template - static Hook* create( + static std::shared_ptr create( void* address, DetourType detour, std::string const& displayName, diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index 1617d537..0e93d85b 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -121,8 +121,8 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptrregisterCustomSetting(key, std::move(value)); } -Result<> Mod::claimHook(Hook* hook) { - return m_impl->claimHook(hook); +Result Mod::claimHook(std::shared_ptr&& hook) { + return m_impl->claimHook(std::move(hook)); } Result<> Mod::disownHook(Hook* hook) { @@ -133,8 +133,8 @@ std::vector Mod::getHooks() const { return m_impl->getHooks(); } -Result<> Mod::claimPatch(Patch* patch) { - return m_impl->claimPatch(patch); +Result Mod::claimPatch(std::shared_ptr&& patch) { + return m_impl->claimPatch(std::move(patch)); } Result<> Mod::disownPatch(Patch* patch) { diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index 6b87660d..363e2438 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -132,11 +132,19 @@ bool Mod::Impl::needsEarlyLoad() const { } std::vector Mod::Impl::getHooks() const { - return m_hooks; + std::vector ret; + for (auto& hook : m_hooks) { + ret.push_back(hook.get()); + } + return ret; } std::vector Mod::Impl::getPatches() const { - return m_patches; + std::vector ret; + for (auto& patch : m_patches) { + ret.push_back(patch.get()); + } + return ret; } // Settings and saved values @@ -441,27 +449,30 @@ bool Mod::Impl::depends(std::string_view const id) const { // Hooks -Result<> Mod::Impl::claimHook(Hook* hook) { +Result Mod::Impl::claimHook(std::shared_ptr&& hook) { auto res1 = hook->m_impl->setOwner(m_self); if (!res1) { return Err("Cannot claim hook: {}", res1.unwrapErr()); } - m_hooks.push_back(hook); + auto ptr = hook.get(); + m_hooks.push_back(std::move(hook)); if (!this->isEnabled() || !hook->getAutoEnable()) - return Ok(); + return Ok(ptr); if (!LoaderImpl::get()->isReadyToHook()) { - LoaderImpl::get()->addUninitializedHook(hook, m_self); - return Ok(); + LoaderImpl::get()->addUninitializedHook(ptr, m_self); + return Ok(ptr); } - auto res2 = hook->enable(); + auto res2 = ptr->enable(); if (!res2) { return Err("Cannot enable hook: {}", res2.unwrapErr()); } - return Ok(); + + + return Ok(ptr); } Result<> Mod::Impl::disownHook(Hook* hook) { @@ -473,7 +484,9 @@ Result<> Mod::Impl::disownHook(Hook* hook) { if (!res1) { return Err("Cannot disown hook: {}", res1.unwrapErr()); } - m_hooks.erase(std::find(m_hooks.begin(), m_hooks.end(), hook)); + m_hooks.erase(std::find_if(m_hooks.begin(), m_hooks.end(), [&](auto& a) { + return a.get() == hook; + })); if (!this->isEnabled() || !hook->getAutoEnable()) return Ok(); @@ -488,22 +501,23 @@ Result<> Mod::Impl::disownHook(Hook* hook) { // Patches -Result<> Mod::Impl::claimPatch(Patch* patch) { +Result Mod::Impl::claimPatch(std::shared_ptr&& patch) { auto res1 = patch->m_impl->setOwner(m_self); if (!res1) { return Err("Cannot claim patch: {}", res1.unwrapErr()); } - m_patches.push_back(patch); + auto ptr = patch.get(); + m_patches.push_back(std::move(patch)); if (!this->isEnabled() || !patch->getAutoEnable()) - return Ok(); + return Ok(ptr); - auto res2 = patch->enable(); + auto res2 = ptr->enable(); if (!res2) { return Err("Cannot enable patch: {}", res2.unwrapErr()); } - return Ok(); + return Ok(ptr); } Result<> Mod::Impl::disownPatch(Patch* patch) { @@ -515,7 +529,9 @@ Result<> Mod::Impl::disownPatch(Patch* patch) { if (!res1) { return Err("Cannot disown patch: {}", res1.unwrapErr()); } - m_patches.erase(std::find(m_patches.begin(), m_patches.end(), patch)); + m_patches.erase(std::find_if(m_patches.begin(), m_patches.end(), [&](auto& a) { + return a.get() == patch; + })); if (!this->isEnabled() || !patch->getAutoEnable()) return Ok(); diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp index 4378aea5..335b054f 100644 --- a/loader/src/loader/ModImpl.hpp +++ b/loader/src/loader/ModImpl.hpp @@ -18,11 +18,11 @@ namespace geode { /** * Hooks owned by this mod */ - std::vector m_hooks; + std::vector> m_hooks; /** * Patches owned by this mod */ - std::vector m_patches; + std::vector> m_patches; /** * Whether the mod is enabled or not */ @@ -113,11 +113,11 @@ namespace geode { SettingValue* getSetting(std::string_view const key) const; void registerCustomSetting(std::string_view const key, std::unique_ptr value); - Result<> claimHook(Hook* hook); + Result claimHook(std::shared_ptr&& hook); Result<> disownHook(Hook* hook); [[nodiscard]] std::vector getHooks() const; - Result<> claimPatch(Patch* patch); + Result claimPatch(std::shared_ptr&& patch); Result<> disownPatch(Patch* patch); [[nodiscard]] std::vector getPatches() const; diff --git a/loader/src/loader/Patch.cpp b/loader/src/loader/Patch.cpp index 180d7368..15471c95 100644 --- a/loader/src/loader/Patch.cpp +++ b/loader/src/loader/Patch.cpp @@ -6,7 +6,7 @@ using namespace geode::prelude; Patch::Patch(std::shared_ptr&& impl) : m_impl(std::move(impl)) { m_impl->m_self = this; } Patch::~Patch() = default; -Patch* Patch::create(void* address, const ByteVector& patch) { +std::shared_ptr Patch::create(void* address, const ByteVector& patch) { return Impl::create(address, patch); } diff --git a/loader/src/loader/PatchImpl.cpp b/loader/src/loader/PatchImpl.cpp index db571b98..d38defda 100644 --- a/loader/src/loader/PatchImpl.cpp +++ b/loader/src/loader/PatchImpl.cpp @@ -31,11 +31,13 @@ static ByteVector readMemory(void* address, size_t amount) { return ret; } -Patch* Patch::Impl::create(void* address, const geode::ByteVector& patch) { +std::shared_ptr Patch::Impl::create(void* address, const geode::ByteVector& patch) { auto impl = std::make_shared( address, readMemory(address, patch.size()), patch ); - return new Patch(std::move(impl)); + return std::shared_ptr(new Patch(std::move(impl)), [](Patch* patch) { + delete patch; + }); } Result<> Patch::Impl::enable() { diff --git a/loader/src/loader/PatchImpl.hpp b/loader/src/loader/PatchImpl.hpp index 522284f6..67ce4207 100644 --- a/loader/src/loader/PatchImpl.hpp +++ b/loader/src/loader/PatchImpl.hpp @@ -13,7 +13,7 @@ public: Impl(void* address, ByteVector original, ByteVector patch); ~Impl(); - static Patch* create(void* address, const ByteVector& patch); + static std::shared_ptr create(void* address, const ByteVector& patch); Patch* m_self = nullptr; void* m_address; From 69f8e15e216a86aaa841f1f3e2077f8ed12ff690 Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:04:49 +0300 Subject: [PATCH 5/8] refactor the code flow --- loader/src/loader/ModImpl.cpp | 75 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index 363e2438..f147d292 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -456,21 +456,21 @@ Result Mod::Impl::claimHook(std::shared_ptr&& hook) { } auto ptr = hook.get(); + if (this->isEnabled() && hook->getAutoEnable()) { + if (LoaderImpl::get()->isReadyToHook()) { + auto res2 = ptr->enable(); + if (!res2) { + m_hooks.pop_back(); + return Err("Cannot enable hook: {}", res2.unwrapErr()); + } + } + else { + LoaderImpl::get()->addUninitializedHook(ptr, m_self); + return Ok(ptr); + } + } + m_hooks.push_back(std::move(hook)); - if (!this->isEnabled() || !hook->getAutoEnable()) - return Ok(ptr); - - if (!LoaderImpl::get()->isReadyToHook()) { - LoaderImpl::get()->addUninitializedHook(ptr, m_self); - return Ok(ptr); - } - - auto res2 = ptr->enable(); - if (!res2) { - return Err("Cannot enable hook: {}", res2.unwrapErr()); - } - - return Ok(ptr); } @@ -484,18 +484,18 @@ Result<> Mod::Impl::disownHook(Hook* hook) { if (!res1) { return Err("Cannot disown hook: {}", res1.unwrapErr()); } + + if (this->isEnabled() && hook->getAutoEnable()) { + auto res2 = hook->disable(); + if (!res2) { + return Err("Cannot disable hook: {}", res2.unwrapErr()); + } + } + m_hooks.erase(std::find_if(m_hooks.begin(), m_hooks.end(), [&](auto& a) { return a.get() == hook; })); - if (!this->isEnabled() || !hook->getAutoEnable()) - return Ok(); - - auto res2 = hook->disable(); - if (!res2) { - return Err("Cannot disable hook: {}", res2.unwrapErr()); - } - return Ok(); } @@ -508,15 +508,16 @@ Result Mod::Impl::claimPatch(std::shared_ptr&& patch) { } auto ptr = patch.get(); - m_patches.push_back(std::move(patch)); - if (!this->isEnabled() || !patch->getAutoEnable()) - return Ok(ptr); - - auto res2 = ptr->enable(); - if (!res2) { - return Err("Cannot enable patch: {}", res2.unwrapErr()); + if (this->isEnabled() && patch->getAutoEnable()) { + auto res2 = ptr->enable(); + if (!res2) { + m_patches.pop_back(); + return Err("Cannot enable patch: {}", res2.unwrapErr()); + } } + m_patches.push_back(std::move(patch)); + return Ok(ptr); } @@ -529,18 +530,18 @@ Result<> Mod::Impl::disownPatch(Patch* patch) { if (!res1) { return Err("Cannot disown patch: {}", res1.unwrapErr()); } + + if (this->isEnabled() && patch->getAutoEnable()) { + auto res2 = patch->disable(); + if (!res2) { + return Err("Cannot disable patch: {}", res2.unwrapErr()); + } + } + m_patches.erase(std::find_if(m_patches.begin(), m_patches.end(), [&](auto& a) { return a.get() == patch; })); - if (!this->isEnabled() || !patch->getAutoEnable()) - return Ok(); - - auto res2 = patch->disable(); - if (!res2) { - return Err("Cannot disable patch: {}", res2.unwrapErr()); - } - return Ok(); } From a089f5474e61bfead44c16b46b4513e8076ffb44 Mon Sep 17 00:00:00 2001 From: ConfiG Date: Mon, 15 Jan 2024 18:07:37 +0300 Subject: [PATCH 6/8] patch overlap checking --- loader/src/loader/PatchImpl.cpp | 26 +++++++++++++++++++++++--- loader/src/loader/PatchImpl.hpp | 1 + 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/loader/src/loader/PatchImpl.cpp b/loader/src/loader/PatchImpl.cpp index d38defda..fccfbf8c 100644 --- a/loader/src/loader/PatchImpl.cpp +++ b/loader/src/loader/PatchImpl.cpp @@ -40,18 +40,38 @@ std::shared_ptr Patch::Impl::create(void* address, const geode::ByteVecto }); } +std::vector& Patch::Impl::allEnabled() { + static std::vector vec; + return vec; +} + Result<> Patch::Impl::enable() { - // TODO: add overlap checking + // TODO: this feels slow. can be faster + for (const auto& other : allEnabled()) { + auto thisMin = this->getAddress(); + auto thisMax = this->getAddress() + this->m_patch.size(); + auto otherMin = other->getAddress(); + auto otherMax = other->getAddress() + other->m_patch.size(); + bool intersects = !((otherMax < thisMin) || (thisMax < otherMin)); + if (!intersects) + continue; + return Err( + "Failed to enable patch: overlaps patch at {} from {}", + other->m_address, other->getOwner()->getID() + ); + } auto res = tulip::hook::writeMemory(m_address, m_patch.data(), m_patch.size()); - if (!res) return Err(res.unwrapErr()); + if (!res) return Err("Failed to enable patch: {}", res.unwrapErr()); m_enabled = true; + allEnabled().push_back(this); return Ok(); } Result<> Patch::Impl::disable() { auto res = tulip::hook::writeMemory(m_address, m_original.data(), m_original.size()); - if (!res) return Err(res.unwrapErr()); + if (!res) return Err("Failed to disable patch: {}", res.unwrapErr()); m_enabled = false; + allEnabled().erase(std::find(allEnabled().begin(), allEnabled().end(), this)); return Ok(); } diff --git a/loader/src/loader/PatchImpl.hpp b/loader/src/loader/PatchImpl.hpp index 67ce4207..d4d55a27 100644 --- a/loader/src/loader/PatchImpl.hpp +++ b/loader/src/loader/PatchImpl.hpp @@ -14,6 +14,7 @@ public: ~Impl(); static std::shared_ptr create(void* address, const ByteVector& patch); + static std::vector& allEnabled(); Patch* m_self = nullptr; void* m_address; From c89f68cb182a406d387ffbc3847710adc25d9add Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:15:57 +0300 Subject: [PATCH 7/8] move the ctors back to private --- loader/include/Geode/loader/Hook.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp index 1f79e9c6..9e55939e 100644 --- a/loader/include/Geode/loader/Hook.hpp +++ b/loader/include/Geode/loader/Hook.hpp @@ -16,13 +16,13 @@ namespace geode { private: class Impl; std::shared_ptr m_impl; + explicit Hook(std::shared_ptr&& impl); ~Hook(); friend class Mod; friend class Loader; public: - explicit Hook(std::shared_ptr&& impl); /** * Create a hook at an address. The hook is enabled immediately. By @@ -145,13 +145,13 @@ namespace geode { private: class Impl; std::shared_ptr m_impl; + explicit Patch(std::shared_ptr&& impl); ~Patch(); friend class Mod; friend class Loader; public: - explicit Patch(std::shared_ptr&& impl); static std::shared_ptr create(void* address, const ByteVector& patch); From 5268bc631b41736a5ec956c6761532508582412a Mon Sep 17 00:00:00 2001 From: matcool <26722564+matcool@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:20:48 -0300 Subject: [PATCH 8/8] add docs --- loader/include/Geode/loader/Hook.hpp | 10 ++++++---- loader/include/Geode/loader/Mod.hpp | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp index 9e55939e..bbf928b0 100644 --- a/loader/include/Geode/loader/Hook.hpp +++ b/loader/include/Geode/loader/Hook.hpp @@ -25,9 +25,11 @@ namespace geode { public: /** - * Create a hook at an address. The hook is enabled immediately. By - * default, the hook is placed at the end of the detour list; however, - * this can be controlled using metadata settings. + * Create a hook at an address. By default, the hook is disabled and + * placed at the end of the detour list; however, this can be + * controlled using metadata settings. + * After creating the hook object, we recommend you set its owner + * by calling `Mod::claimHook`, see its docs for more info. * @param address The address to hook * @param detour The detour to run when the hook is hit. The detour's * calling convention should be cdecl @@ -36,7 +38,7 @@ namespace geode { * @param handlerMetadata Metadata for the hook handler * @param hookMetadata Metadata for the hook itself * @returns The created hook, or an error. Make sure to add the created - * hook to the mod that owns it using mod->claimHook! + * hook to the mod that owns it using mod->claimHook(hook)! */ static std::shared_ptr create( void* address, diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index 14aa1959..73996585 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -265,8 +265,20 @@ namespace geode { return Ok(ptr); } + /** + * Claims an existing hook object, marking this mod as its owner. + * If the hook has "auto enable" set, this will enable the hook. + * @returns Returns a pointer to the hook, or an error if the + * hook already has an owner, or was unable to enable the hook. + */ Result claimHook(std::shared_ptr&& hook); + /** + * Disowns a hook which this mod owns, making this mod no longer its owner. + * If the hook has "auto enable" set, this will disable the hook. + * @returns Returns an error if this mod doesn't own the hook, or + * if disabling the hook failed. + */ Result<> disownHook(Hook* hook); /** @@ -288,8 +300,20 @@ namespace geode { return Ok(ptr); } + /** + * Claims an existing patch object, marking this mod as its owner. + * If the patch has "auto enable" set, this will enable the patch. + * @returns Returns a pointer to the patch, or an error if the + * patch already has an owner, or was unable to enable the patch. + */ Result claimPatch(std::shared_ptr&& patch); + /** + * Disowns a patch which this mod owns, making this mod no longer its owner. + * If the patch has "auto enable" set, this will disable the patch. + * @returns Returns an error if this mod doesn't own the patch, or + * if disabling the patch failed. + */ Result<> disownPatch(Patch* patch); /**