Merge pull request #421 from geode-sdk/hook-patch-refactor

Refactor Hook/Patch
This commit is contained in:
mat 2024-01-18 13:29:20 -03:00 committed by GitHub
commit a5a3a08d65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 631 additions and 455 deletions

View file

@ -4,7 +4,7 @@
#include "../utils/general.hpp"
#include <matjson.hpp>
#include "Tulip.hpp"
#include <inttypes.h>
#include <cinttypes>
#include <string_view>
#include <tulip/TulipHook.hpp>
@ -16,21 +16,20 @@ namespace geode {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
Hook(std::shared_ptr<Impl>&& impl);
explicit Hook(std::shared_ptr<Impl>&& 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<Hook> create(
void* address,
void* detour,
std::string const& displayName,
@ -50,9 +48,8 @@ namespace geode {
tulip::hook::HookMetadata const& hookMetadata
);
template <class DetourType>
static Hook* create(
Mod* owner,
template<class DetourType>
static std::shared_ptr<Hook> 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<void*>(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<Impl> m_impl;
explicit Patch(std::shared_ptr<Impl>&& 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<Patch> 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;
};
}

View file

@ -229,12 +229,6 @@ namespace geode {
return sharedMod<>;
}
/**
* Get all hooks owned by this Mod
* @returns Vector of hooks
*/
std::vector<Hook*> 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 <class DetourType>
Result<Hook*> addHook(
template<class DetourType>
Result<Hook*> 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<Hook*> addHook(Hook* hook);
Result<Hook*> 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<Hook*> claimHook(std::shared_ptr<Hook>&& 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<Hook*> 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*> patch(void* address, ByteVector const& data);
Result<Patch*> 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<Patch*> claimPatch(std::shared_ptr<Patch>&& 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<Patch*> getPatches() const;
/**
* Enable this mod

View file

@ -21,7 +21,6 @@
break; \
} \
auto hook = Hook::create( \
Mod::get(), \
reinterpret_cast<void*>(address), \
AsStaticFunction_##FunctionName_< \
Derived, \
@ -38,7 +37,6 @@
if constexpr (HasConstructor<Derived>) { \
static auto address = AddressInline_; \
auto hook = Hook::create( \
Mod::get(), \
reinterpret_cast<void*>(address), \
AsStaticFunction_##constructor< \
Derived, \
@ -55,7 +53,6 @@
if constexpr (HasDestructor<Derived>) { \
static auto address = AddressInline_; \
auto hook = Hook::create( \
Mod::get(), \
reinterpret_cast<void*>(address), \
AsStaticFunction_##destructor<Derived, decltype(Resolve<>::func(&Derived::destructor))>::value, \
#ClassName_ "::" #ClassName_, \
@ -73,13 +70,13 @@ namespace geode::modifier {
template <class ModifyDerived>
class ModifyBase {
public:
std::map<std::string, Hook*> m_hooks;
std::map<std::string, std::shared_ptr<Hook>> m_hooks;
Result<Hook*> 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<ModifyDerived*>(this);
test->ModifyDerived::apply();
ModifyDerived::Derived::onModify(*this);
std::vector<std::string> 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);
}
}

View file

@ -35,7 +35,7 @@ namespace geode {
* @returns The created hook, or an error.
*/
template <class Func>
static Result<Hook*> create(std::string const& className, std::string const& selectorName, Func function, tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) {
static Result<std::shared_ptr<Hook>> 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 <class Func>
static Result<Hook*> create(std::string const& className, std::string const& selectorName, Func function, void(*empty)(), tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) {
static Result<std::shared_ptr<Hook>> 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);

View file

@ -123,6 +123,13 @@ namespace geode {
}
}
template<>
struct matjson::Serialize<geode::ByteVector> {
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();

View file

@ -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
}
}

View file

@ -1,30 +1,43 @@
#include <Geode/loader/Hook.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/utils/casts.hpp>
#include <Geode/utils/ranges.hpp>
#include <vector>
#include "ModImpl.hpp"
#include "HookImpl.hpp"
using namespace geode::prelude;
Hook::Hook(std::shared_ptr<Impl>&& impl) : m_impl(std::move(impl)) {}
Hook::~Hook() {}
Hook::Hook(std::shared_ptr<Impl>&& 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> 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<Hook::Impl>(
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();
}

View file

@ -1,15 +1,19 @@
#include "HookImpl.hpp"
#include <utility>
#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> 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<Impl>(
address, detour, displayName, handlerMetadata, hookMetadata
);
return std::shared_ptr<Hook>(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<uintptr_t>(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<uintptr_t>(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();
}

View file

@ -1,3 +1,5 @@
#pragma once
#include <Geode/loader/Hook.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Mod.hpp>
@ -5,47 +7,59 @@
#include <Geode/utils/ranges.hpp>
#include <vector>
#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<Hook> create(
void* address,
void* detour,
std::string const& displayName,
tulip::hook::HandlerMetadata const& handlerMetadata,
tulip::hook::HookMetadata const& hookMetadata
);
template<class DetourType>
static std::shared_ptr<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);
};
Result<> updateHookMetadata();
friend class Hook;
friend class Mod;
};

View file

@ -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<tulip::hook::HandlerHandle> 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<tulip::hook::HandlerHandle> 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);
}

View file

@ -69,10 +69,8 @@ namespace geode {
std::unordered_map<void*, tulip::hook::HandlerHandle> m_handlerHandles;
Result<> createHandler(void* address, tulip::hook::HandlerMetadata const& metadata);
bool hasHandler(void* address);
Result<tulip::hook::HandlerHandle> getHandler(void* address);
Result<> removeHandler(void* address);
Result<tulip::hook::HandlerHandle> getOrCreateHandler(void* address, tulip::hook::HandlerMetadata const& metadata);
bool loadHooks();

View file

@ -121,32 +121,28 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr<Sett
return m_impl->registerCustomSetting(key, std::move(value));
}
Result<Hook*> Mod::claimHook(std::shared_ptr<Hook>&& hook) {
return m_impl->claimHook(std::move(hook));
}
Result<> Mod::disownHook(Hook* hook) {
return m_impl->disownHook(hook);
}
std::vector<Hook*> Mod::getHooks() const {
return m_impl->getHooks();
}
Result<Hook*> Mod::addHook(Hook* hook) {
return m_impl->addHook(hook);
Result<Patch*> Mod::claimPatch(std::shared_ptr<Patch>&& 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<Patch*> 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<Patch*> Mod::getPatches() const {
return m_impl->getPatches();
}
Result<> Mod::enable() {

View file

@ -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<Hook*> Mod::Impl::getHooks() const {
return m_hooks;
std::vector<Hook*> ret;
for (auto& hook : m_hooks) {
ret.push_back(hook.get());
}
return ret;
}
std::vector<Patch*> Mod::Impl::getPatches() const {
std::vector<Patch*> 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<Hook*> Mod::Impl::claimHook(std::shared_ptr<Hook>&& 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<Hook*> 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<uint8_t*>(reinterpret_cast<uintptr_t>(address) + i));
Result<Patch*> Mod::Impl::claimPatch(std::shared_ptr<Patch>&& 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<Patch*> 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<uintptr_t>(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();
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <matjson.hpp>
#include "ModPatch.hpp"
namespace geode {
class Mod::Impl {
@ -17,11 +18,11 @@ namespace geode {
/**
* Hooks owned by this mod
*/
std::vector<Hook*> m_hooks;
std::vector<std::shared_ptr<Hook>> m_hooks;
/**
* Patches owned by this mod
*/
std::vector<Patch*> m_patches;
std::vector<std::shared_ptr<Patch>> 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<SettingValue> value);
std::vector<Hook*> getHooks() const;
Result<Hook*> addHook(Hook* hook);
Result<> enableHook(Hook* hook);
Result<> disableHook(Hook* hook);
Result<> removeHook(Hook* hook);
Result<Patch*> patch(void* address, ByteVector const& data);
Result<> unpatch(Patch* patch);
Result<Hook*> claimHook(std::shared_ptr<Hook>&& hook);
Result<> disownHook(Hook* hook);
[[nodiscard]] std::vector<Hook*> getHooks() const;
Result<Patch*> claimPatch(std::shared_ptr<Patch>&& patch);
Result<> disownPatch(Patch* patch);
[[nodiscard]] std::vector<Patch*> getPatches() const;
Result<> enable();
Result<> disable();
Result<> uninstall(bool deleteSaveData = false);

View file

@ -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;
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <Geode/loader/Mod.hpp>
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);
};

View file

@ -1,58 +1,43 @@
#include <Geode/loader/Hook.hpp>
#include <matjson.hpp>
#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>&& 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<uintptr_t>(m_address);
}
bool Patch::isApplied() const {
return m_applied;
std::shared_ptr<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<ByteVector> {
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<uintptr_t>(m_address));
json["original"] = m_original;
json["patch"] = m_patch;
json["applied"] = m_applied;
return json;
return m_impl->getRuntimeInfo();
}

View file

@ -0,0 +1,89 @@
#include "PatchImpl.hpp"
#include <utility>
#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<uint8_t*>(reinterpret_cast<uintptr_t>(address) + i));
}
return ret;
}
std::shared_ptr<Patch> Patch::Impl::create(void* address, const geode::ByteVector& patch) {
auto impl = std::make_shared<Impl>(
address, readMemory(address, patch.size()), patch
);
return std::shared_ptr<Patch>(new Patch(std::move(impl)), [](Patch* patch) {
delete patch;
});
}
std::vector<Patch::Impl*>& Patch::Impl::allEnabled() {
static std::vector<Patch::Impl*> 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<uintptr_t>(m_address);
}
matjson::Value Patch::Impl::getRuntimeInfo() const {
auto json = matjson::Object();
json["address"] = std::to_string(reinterpret_cast<uintptr_t>(m_address));
json["original"] = m_original;
json["patch"] = m_patch;
json["enabled"] = m_enabled;
return json;
}

View file

@ -0,0 +1,32 @@
#pragma once
#include <Geode/loader/Hook.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Mod.hpp>
#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<Patch> create(void* address, const ByteVector& patch);
static std::vector<Patch::Impl*>& 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;
};