diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp index eb11ddfc..bbf928b0 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,20 @@ 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 + * 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 @@ -39,10 +38,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(hook)! */ - static Hook* create( - Mod* owner, + static std::shared_ptr create( void* address, void* detour, std::string const& displayName, @@ -50,9 +48,8 @@ namespace geode { tulip::hook::HookMetadata const& hookMetadata ); - template - static Hook* create( - Mod* owner, + template + static std::shared_ptr create( void* address, DetourType detour, std::string const& displayName, @@ -64,7 +61,6 @@ namespace geode { .m_abstract = tulip::hook::AbstractFunction::from(detour) }; return Hook::create( - owner, address, reinterpret_cast(detour), displayName, @@ -77,135 +73,131 @@ 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; - /** - * Get whether the patch is applied or not. - * @returns True if applied, false if not. - */ - bool isApplied() const; + static std::shared_ptr create(void* address, const ByteVector& patch); - 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..73996585 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,48 @@ 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_INTO(auto ptr, this->claimHook(std::move(hook))); + return Ok(ptr); } - 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_INTO(auto ptr, this->claimHook(std::move(hook))); + return Ok(ptr); + } /** - * Enable a hook owned by this Mod - * @returns Successful result on success, - * errorful result with info on error + * 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<> enableHook(Hook* hook); + Result claimHook(std::shared_ptr&& hook); /** - * Disable a hook owned by this Mod - * @returns Successful result on success, - * errorful result with info on error + * 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<> disableHook(Hook* hook); + Result<> disownHook(Hook* hook); /** - * Remove 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<> removeHook(Hook* hook); + [[nodiscard]] std::vector getHooks() const; /** * Write a patch at an address @@ -290,14 +294,33 @@ 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_INTO(auto ptr, this->claimPatch(std::move(patch))); + return Ok(ptr); + } /** - * Remove a patch owned by this Mod - * @returns Successful result on success, - * errorful result with info on error + * 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<> unpatch(Patch* 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); + + /** + * Get all patches owned by this Mod + * @returns Vector of patches + */ + [[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..32e27459 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_, \ @@ -73,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) { @@ -97,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()->addHook(hook); + auto res = Mod::get()->claimHook(std::move(hook)); if (!res) { - log::error("Failed to add hook {}: {}", hook->getDisplayName(), res.error()); + 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/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/hooks/DynamicCastFix.cpp b/loader/src/hooks/DynamicCastFix.cpp index 332a021f..658ca898 100644 --- a/loader/src/hooks/DynamicCastFix.cpp +++ b/loader/src/hooks/DynamicCastFix.cpp @@ -16,10 +16,8 @@ $execute { void* handle = dlopen("libcocos2dcpp.so", RTLD_LAZY | RTLD_NOLOAD); void* dynamicCastAddr = dlsym(handle, "__dynamic_cast"); - (void)Mod::get()->addHook(dynamicCastAddr, &cast::typeinfoCastInternal, "__dynamic_cast"); + (void)Mod::get()->hook(dynamicCastAddr, &cast::typeinfoCastInternal, "__dynamic_cast"); dlclose(handle); #endif - - -} \ No newline at end of file +} diff --git a/loader/src/loader/Hook.cpp b/loader/src/loader/Hook.cpp index c314f53a..ae0d6239 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(std::shared_ptr&& impl) : m_impl(std::move(impl)) { m_impl->m_self = this; } +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, +std::shared_ptr Hook::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, 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..558a1756 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()); + } + } +} + +std::shared_ptr 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 + ); + return std::shared_ptr(new Hook(std::move(impl)), [](Hook* hook) { + delete 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..3dbf8cdd 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 std::shared_ptr create( + void* address, + void* detour, + std::string const& displayName, + tulip::hook::HandlerMetadata const& handlerMetadata, + tulip::hook::HookMetadata const& hookMetadata + ); + + template + static std::shared_ptr 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 727f68a3..25ae6563 100644 --- a/loader/src/loader/LoaderImpl.cpp +++ b/loader/src/loader/LoaderImpl.cpp @@ -678,7 +678,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; @@ -725,35 +725,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 2f6ab2d0..ac70266f 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..0e93d85b 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(std::shared_ptr&& hook) { + return m_impl->claimHook(std::move(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(std::shared_ptr&& patch) { + return m_impl->claimPatch(std::move(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 5ad862ff..ae11499e 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" @@ -135,7 +137,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 { + std::vector ret; + for (auto& patch : m_patches) { + ret.push_back(patch.get()); + } + return ret; } // Settings and saved values @@ -333,32 +347,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(); @@ -466,79 +454,99 @@ 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(std::shared_ptr&& hook) { + auto res1 = hook->m_impl->setOwner(m_self); + if (!res1) { + return Err("Cannot claim hook: {}", res1.unwrapErr()); } - return res; + 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)); + + return Ok(ptr); } -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 (!LoaderImpl::get()->isReadyToHook()) { - LoaderImpl::get()->addUninitializedHook(hook, m_self); - return Ok(hook); +Result<> Mod::Impl::disownHook(Hook* hook) { + if (hook->getOwner() != m_self) { + return Err("Cannot disown hook not owned by this mod"); } - 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 res1 = hook->m_impl->setOwner(nullptr); + if (!res1) { + return Err("Cannot disown hook: {}", res1.unwrapErr()); } - return Ok(hook); -} - -Result<> Mod::Impl::removeHook(Hook* hook) { - auto res = this->disableHook(hook); - if (res) { - ranges::remove(m_hooks, hook); - delete hook; + if (this->isEnabled() && hook->getAutoEnable()) { + auto res2 = hook->disable(); + if (!res2) { + return Err("Cannot disable hook: {}", res2.unwrapErr()); + } } - return res; + + m_hooks.erase(std::find_if(m_hooks.begin(), m_hooks.end(), [&](auto& a) { + return a.get() == hook; + })); + + 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(std::shared_ptr&& patch) { + auto res1 = patch->m_impl->setOwner(m_self); + if (!res1) { + return Err("Cannot claim patch: {}", res1.unwrapErr()); } - return ret; + + auto ptr = patch.get(); + 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); } -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"); } - 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; + auto res1 = patch->m_impl->setOwner(nullptr); + 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; + })); + return Ok(); } diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp index e7b7a0a2..335b054f 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 { @@ -17,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 */ @@ -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(std::shared_ptr&& hook); + Result<> disownHook(Hook* hook); + [[nodiscard]] std::vector getHooks() const; + + Result claimPatch(std::shared_ptr&& 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..15471c95 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)) { m_impl->m_self = this; } +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; +std::shared_ptr 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..fccfbf8c --- /dev/null +++ b/loader/src/loader/PatchImpl.cpp @@ -0,0 +1,89 @@ +#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; +} + +std::shared_ptr Patch::Impl::create(void* address, const geode::ByteVector& patch) { + auto impl = std::make_shared( + address, readMemory(address, patch.size()), patch + ); + return std::shared_ptr(new Patch(std::move(impl)), [](Patch* patch) { + delete patch; + }); +} + +std::vector& Patch::Impl::allEnabled() { + static std::vector vec; + return vec; +} + +Result<> Patch::Impl::enable() { + // 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("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("Failed to disable patch: {}", res.unwrapErr()); + m_enabled = false; + allEnabled().erase(std::find(allEnabled().begin(), allEnabled().end(), this)); + 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..d4d55a27 --- /dev/null +++ b/loader/src/loader/PatchImpl.hpp @@ -0,0 +1,32 @@ +#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 std::shared_ptr create(void* address, const ByteVector& patch); + static std::vector& allEnabled(); + + 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; +};