mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-22 23:48:08 -05:00
Merge pull request #421 from geode-sdk/hook-patch-refactor
Refactor Hook/Patch
This commit is contained in:
commit
a5a3a08d65
19 changed files with 631 additions and 455 deletions
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
25
loader/src/loader/ModPatch.cpp
Normal file
25
loader/src/loader/ModPatch.cpp
Normal 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;
|
||||
}
|
22
loader/src/loader/ModPatch.hpp
Normal file
22
loader/src/loader/ModPatch.hpp
Normal 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);
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
|
|
89
loader/src/loader/PatchImpl.cpp
Normal file
89
loader/src/loader/PatchImpl.cpp
Normal 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;
|
||||
}
|
32
loader/src/loader/PatchImpl.hpp
Normal file
32
loader/src/loader/PatchImpl.hpp
Normal 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;
|
||||
};
|
Loading…
Reference in a new issue