mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-26 17:36:05 -05:00
refactor Hook/Patch
This commit is contained in:
parent
5ff74e849a
commit
28c91f762e
17 changed files with 546 additions and 441 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,17 @@ 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
|
||||
* @param address The address to hook
|
||||
* @param detour The detour to run when the hook is hit. The detour's
|
||||
* calling convention should be cdecl
|
||||
|
@ -39,10 +35,9 @@ namespace geode {
|
|||
* @param handlerMetadata Metadata for the hook handler
|
||||
* @param hookMetadata Metadata for the hook itself
|
||||
* @returns The created hook, or an error. Make sure to add the created
|
||||
* hook to the mod that owns it using mod->addHook!
|
||||
* hook to the mod that owns it using mod->claimHook!
|
||||
*/
|
||||
static Hook* create(
|
||||
Mod* owner,
|
||||
void* address,
|
||||
void* detour,
|
||||
std::string const& displayName,
|
||||
|
@ -50,9 +45,8 @@ namespace geode {
|
|||
tulip::hook::HookMetadata const& hookMetadata
|
||||
);
|
||||
|
||||
template <class DetourType>
|
||||
template<class DetourType>
|
||||
static Hook* create(
|
||||
Mod* owner,
|
||||
void* address,
|
||||
DetourType detour,
|
||||
std::string const& displayName,
|
||||
|
@ -64,7 +58,6 @@ namespace geode {
|
|||
.m_abstract = tulip::hook::AbstractFunction::from(detour)
|
||||
};
|
||||
return Hook::create(
|
||||
owner,
|
||||
address,
|
||||
reinterpret_cast<void*>(detour),
|
||||
displayName,
|
||||
|
@ -77,135 +70,130 @@ namespace geode {
|
|||
Hook operator=(Hook const&) = delete;
|
||||
|
||||
/**
|
||||
* Get the address of the function hooked.
|
||||
* @returns Address
|
||||
* Get the owner of this hook.
|
||||
* @returns Pointer to the owner's Mod handle.
|
||||
*/
|
||||
uintptr_t getAddress() const;
|
||||
|
||||
/**
|
||||
* Get the display name of the function hooked.
|
||||
* @returns Display name
|
||||
*/
|
||||
std::string_view getDisplayName() const;
|
||||
[[nodiscard]] Mod* getOwner() const;
|
||||
|
||||
/**
|
||||
* Get whether the hook is enabled or not.
|
||||
* @returns True if enabled, false if not.
|
||||
*/
|
||||
bool isEnabled() const;
|
||||
[[nodiscard]] bool isEnabled() const;
|
||||
|
||||
/**
|
||||
* Get the owner of this hook.
|
||||
* @returns Pointer to the owner's Mod handle.
|
||||
*/
|
||||
Mod* getOwner() const;
|
||||
Result<> enable();
|
||||
|
||||
/**
|
||||
* Get info about the hook as JSON
|
||||
* @note For IPC
|
||||
*/
|
||||
matjson::Value getRuntimeInfo() const;
|
||||
|
||||
/**
|
||||
* Get the metadata of the hook.
|
||||
* @returns Hook metadata
|
||||
*/
|
||||
tulip::hook::HookMetadata getHookMetadata() const;
|
||||
|
||||
/**
|
||||
* Set the metadata of the hook.
|
||||
* @param metadata Hook metadata
|
||||
*/
|
||||
void setHookMetadata(tulip::hook::HookMetadata const& metadata);
|
||||
|
||||
/**
|
||||
* Get the priority of the hook.
|
||||
* @returns Priority
|
||||
*/
|
||||
int32_t getPriority() const;
|
||||
|
||||
/**
|
||||
* Set the priority of the hook.
|
||||
* @param priority Priority
|
||||
*/
|
||||
void setPriority(int32_t priority);
|
||||
Result<> disable();
|
||||
|
||||
/**
|
||||
* Get whether the hook should be auto enabled or not.
|
||||
* @returns Auto enable
|
||||
*/
|
||||
bool getAutoEnable() const;
|
||||
[[nodiscard]] bool getAutoEnable() const;
|
||||
|
||||
/**
|
||||
* Set whether the hook should be auto enabled or not.
|
||||
* @param autoEnable Auto enable
|
||||
*/
|
||||
void setAutoEnable(bool autoEnable);
|
||||
|
||||
/**
|
||||
* Get the address of the function hooked.
|
||||
* @returns Address
|
||||
*/
|
||||
[[nodiscard]] uintptr_t getAddress() const;
|
||||
|
||||
/**
|
||||
* Get the display name of the function hooked.
|
||||
* @returns Display name
|
||||
*/
|
||||
[[nodiscard]] std::string_view getDisplayName() const;
|
||||
|
||||
/**
|
||||
* Get info about the hook as JSON
|
||||
* @note For IPC
|
||||
*/
|
||||
[[nodiscard]] matjson::Value getRuntimeInfo() const;
|
||||
|
||||
/**
|
||||
* Get the metadata of the hook.
|
||||
* @returns Hook metadata
|
||||
*/
|
||||
[[nodiscard]] tulip::hook::HookMetadata getHookMetadata() const;
|
||||
|
||||
/**
|
||||
* Set the metadata of the hook.
|
||||
* @param metadata Hook metadata
|
||||
*/
|
||||
void setHookMetadata(tulip::hook::HookMetadata const& metadata);
|
||||
|
||||
/**
|
||||
* Get the priority of the hook.
|
||||
* @returns Priority
|
||||
*/
|
||||
[[nodiscard]] int32_t getPriority() const;
|
||||
|
||||
/**
|
||||
* Set the priority of the hook.
|
||||
* @param priority Priority
|
||||
*/
|
||||
void setPriority(int32_t priority);
|
||||
};
|
||||
|
||||
class GEODE_DLL Patch final {
|
||||
// Change to private in 2.0.0
|
||||
protected:
|
||||
Mod* m_owner;
|
||||
void* m_address;
|
||||
ByteVector m_original;
|
||||
ByteVector m_patch;
|
||||
bool m_applied;
|
||||
bool m_autoEnable;
|
||||
|
||||
// Only allow friend classes to create
|
||||
// patches. Whatever method created the
|
||||
// patches should take care of populating
|
||||
// m_owner, m_address, m_original and
|
||||
// m_patch.
|
||||
Patch();
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<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;
|
||||
static Patch* create(void* address, const ByteVector& patch);
|
||||
|
||||
/**
|
||||
* Get whether the patch is applied or not.
|
||||
* @returns True if applied, false if not.
|
||||
*/
|
||||
bool isApplied() const;
|
||||
|
||||
bool apply();
|
||||
bool restore();
|
||||
Patch(Patch const&) = delete;
|
||||
Patch operator=(Patch const&) = delete;
|
||||
|
||||
/**
|
||||
* Get the owner of this patch.
|
||||
* @returns Pointer to the owner's Mod handle.
|
||||
*/
|
||||
Mod* getOwner() const;
|
||||
[[nodiscard]] Mod* getOwner() const;
|
||||
|
||||
/**
|
||||
* Get info about the patch as JSON
|
||||
* @note For IPC
|
||||
* Get whether the patch is enabled or not.
|
||||
* @returns True if enabled, false if not.
|
||||
*/
|
||||
matjson::Value getRuntimeInfo() const;
|
||||
[[nodiscard]] bool isEnabled() const;
|
||||
|
||||
Result<> enable();
|
||||
|
||||
Result<> disable();
|
||||
|
||||
/**
|
||||
* Get whether the patch should be auto enabled or not.
|
||||
* @returns Auto enable
|
||||
*/
|
||||
bool getAutoEnable() const;
|
||||
[[nodiscard]] bool getAutoEnable() const;
|
||||
|
||||
/**
|
||||
* Set whether the patch should be auto enabled or not.
|
||||
* @param autoEnable Auto enable
|
||||
*/
|
||||
void setAutoEnable(bool autoEnable);
|
||||
|
||||
/**
|
||||
* Get the address of the patch.
|
||||
* @returns Address
|
||||
*/
|
||||
[[nodiscard]] uintptr_t getAddress() const;
|
||||
|
||||
/**
|
||||
* Get info about the patch as JSON
|
||||
* @note For IPC
|
||||
*/
|
||||
[[nodiscard]] matjson::Value getRuntimeInfo() const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,36 @@ 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(this->claimHook(hook));
|
||||
return Ok(hook);
|
||||
}
|
||||
|
||||
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(this->claimHook(hook));
|
||||
return Ok(hook);
|
||||
}
|
||||
|
||||
Result<> claimHook(Hook* hook);
|
||||
|
||||
Result<> disownHook(Hook* hook);
|
||||
|
||||
/**
|
||||
* Enable a hook owned by this Mod
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
* Get all hooks owned by this Mod
|
||||
* @returns Vector of hooks
|
||||
*/
|
||||
Result<> enableHook(Hook* hook);
|
||||
|
||||
/**
|
||||
* Disable a hook owned by this Mod
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
*/
|
||||
Result<> disableHook(Hook* hook);
|
||||
|
||||
/**
|
||||
* Remove a hook owned by this Mod
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
*/
|
||||
Result<> removeHook(Hook* hook);
|
||||
[[nodiscard]] std::vector<Hook*> getHooks() const;
|
||||
|
||||
/**
|
||||
* Write a patch at an address
|
||||
|
@ -290,14 +282,21 @@ namespace geode {
|
|||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
*/
|
||||
Result<Patch*> patch(void* address, ByteVector const& data);
|
||||
Result<Patch*> patch(void* address, ByteVector const& data) {
|
||||
auto patch = Patch::create(address, data);
|
||||
GEODE_UNWRAP(this->claimPatch(patch));
|
||||
return Ok(patch);
|
||||
}
|
||||
|
||||
Result<> claimPatch(Patch* patch);
|
||||
|
||||
Result<> disownPatch(Patch* patch);
|
||||
|
||||
/**
|
||||
* Remove a patch owned by this Mod
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
* Get all patches owned by this Mod
|
||||
* @returns Vector of patches
|
||||
*/
|
||||
Result<> unpatch(Patch* patch);
|
||||
[[nodiscard]] std::vector<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_, \
|
||||
|
@ -98,9 +95,9 @@ namespace geode::modifier {
|
|||
test->ModifyDerived::apply();
|
||||
ModifyDerived::Derived::onModify(*this);
|
||||
for (auto& [uuid, hook] : m_hooks) {
|
||||
auto res = Mod::get()->addHook(hook);
|
||||
auto res = Mod::get()->claimHook(hook);
|
||||
if (!res) {
|
||||
log::error("Failed to add hook {}: {}", hook->getDisplayName(), res.error());
|
||||
log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() = default;
|
||||
|
||||
// These classes (Hook and Patch) are nasty using new and delete, change them in 2.0.0
|
||||
Hook* Hook::create(
|
||||
Mod* owner,
|
||||
void* address,
|
||||
void* detour,
|
||||
std::string const& displayName,
|
||||
tulip::hook::HandlerMetadata const& handlerMetadata,
|
||||
tulip::hook::HookMetadata const& hookMetadata
|
||||
) {
|
||||
auto impl = std::make_shared<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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
auto hook = new Hook(std::move(impl));
|
||||
impl->m_self = hook;
|
||||
return hook;
|
||||
}
|
||||
|
||||
Result<> Hook::Impl::enable() {
|
||||
if (m_enabled) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// During a transition between updates when it's important to get a
|
||||
// non-functional version that compiles, address 0x9999999 is used to mark
|
||||
// functions not yet RE'd but that would prevent compilation
|
||||
if ((uintptr_t)m_address == (geode::base::get() + 0x9999999)) {
|
||||
if (m_owner) {
|
||||
log::warn(
|
||||
"Hook {} for {} uses placeholder address, refusing to hook",
|
||||
m_displayName, m_owner->getID()
|
||||
);
|
||||
}
|
||||
else {
|
||||
log::warn("Hook {} uses placeholder address, refusing to hook", m_displayName);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getOrCreateHandler(m_address, m_handlerMetadata));
|
||||
m_handle = tulip::hook::createHook(handler, m_detour, m_hookMetadata);
|
||||
m_enabled = true;
|
||||
|
||||
if (m_owner) {
|
||||
log::debug("Enabled {} hook at {} for {}", m_displayName, m_address, m_owner->getID());
|
||||
}
|
||||
else {
|
||||
log::debug("Enabled {} hook at {}", m_displayName, m_address);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Hook::Impl::disable() {
|
||||
if (!m_enabled)
|
||||
return Ok();
|
||||
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
|
||||
tulip::hook::removeHook(handler, m_handle);
|
||||
m_enabled = false;
|
||||
log::debug("Disabled {} hook", m_displayName);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
uintptr_t Hook::Impl::getAddress() const {
|
||||
return reinterpret_cast<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 Hook* create(
|
||||
void* address,
|
||||
void* detour,
|
||||
std::string const& displayName,
|
||||
tulip::hook::HandlerMetadata const& handlerMetadata,
|
||||
tulip::hook::HookMetadata const& hookMetadata
|
||||
);
|
||||
|
||||
template<class DetourType>
|
||||
static Hook* create(
|
||||
void* address,
|
||||
DetourType detour,
|
||||
std::string const& displayName,
|
||||
tulip::hook::TulipConvention convention,
|
||||
tulip::hook::HookMetadata const& hookMetadata
|
||||
);
|
||||
|
||||
Hook* m_self = nullptr;
|
||||
void* m_address;
|
||||
void* m_detour;
|
||||
std::string m_displayName;
|
||||
tulip::hook::HandlerMetadata m_handlerMetadata;
|
||||
tulip::hook::HookMetadata m_hookMetadata;
|
||||
Mod* m_owner;
|
||||
tulip::hook::HookHandle m_handle;
|
||||
bool m_enabled = false;
|
||||
bool m_autoEnable = true;
|
||||
tulip::hook::HookHandle m_handle = 0;
|
||||
|
||||
|
||||
// Used by Mod
|
||||
Result<> enable();
|
||||
Result<> disable();
|
||||
Result<> updateMetadata();
|
||||
|
||||
uintptr_t getAddress() const;
|
||||
std::string_view getDisplayName() const;
|
||||
bool isEnabled() const;
|
||||
Mod* getOwner() const;
|
||||
matjson::Value getRuntimeInfo() const;
|
||||
tulip::hook::HookMetadata getHookMetadata() const;
|
||||
void setHookMetadata(tulip::hook::HookMetadata const& metadata);
|
||||
int32_t getPriority() const;
|
||||
void setPriority(int32_t priority);
|
||||
bool getAutoEnable() const;
|
||||
void setAutoEnable(bool autoEnable);
|
||||
};
|
||||
|
||||
Result<> updateHookMetadata();
|
||||
|
||||
friend class Hook;
|
||||
friend class Mod;
|
||||
};
|
||||
|
|
|
@ -681,7 +681,7 @@ bool Loader::Impl::loadHooks() {
|
|||
m_readyToHook = true;
|
||||
bool hadErrors = false;
|
||||
for (auto const& [hook, mod] : m_uninitializedHooks) {
|
||||
auto res = mod->addHook(hook);
|
||||
auto res = hook->enable();
|
||||
if (!res) {
|
||||
log::logImpl(Severity::Error, mod, "{}", res.unwrapErr());
|
||||
hadErrors = true;
|
||||
|
@ -728,35 +728,18 @@ void Loader::Impl::releaseNextMod() {
|
|||
m_nextModLock.unlock();
|
||||
}
|
||||
|
||||
Result<> Loader::Impl::createHandler(void* address, tulip::hook::HandlerMetadata const& metadata) {
|
||||
if (m_handlerHandles.count(address)) {
|
||||
return Err("Handler already exists at address");
|
||||
}
|
||||
|
||||
GEODE_UNWRAP_INTO(auto handle, tulip::hook::createHandler(address, metadata));
|
||||
m_handlerHandles[address] = handle;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
bool Loader::Impl::hasHandler(void* address) {
|
||||
return m_handlerHandles.count(address) > 0;
|
||||
}
|
||||
|
||||
Result<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<> Mod::claimHook(Hook* hook) {
|
||||
return m_impl->claimHook(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<> Mod::claimPatch(Patch* patch) {
|
||||
return m_impl->claimPatch(patch);
|
||||
}
|
||||
|
||||
Result<> Mod::enableHook(Hook* hook) {
|
||||
return m_impl->enableHook(hook);
|
||||
Result<> Mod::disownPatch(Patch* patch) {
|
||||
return m_impl->disownPatch(patch);
|
||||
}
|
||||
|
||||
Result<> Mod::disableHook(Hook* hook) {
|
||||
return m_impl->disableHook(hook);
|
||||
}
|
||||
|
||||
Result<> Mod::removeHook(Hook* hook) {
|
||||
return m_impl->removeHook(hook);
|
||||
}
|
||||
|
||||
Result<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"
|
||||
|
||||
|
@ -133,6 +135,10 @@ std::vector<Hook*> Mod::Impl::getHooks() const {
|
|||
return m_hooks;
|
||||
}
|
||||
|
||||
std::vector<Patch*> Mod::Impl::getPatches() const {
|
||||
return m_patches;
|
||||
}
|
||||
|
||||
// Settings and saved values
|
||||
|
||||
Result<> Mod::Impl::loadData() {
|
||||
|
@ -328,32 +334,6 @@ Result<> Mod::Impl::loadBinary() {
|
|||
|
||||
LoaderImpl::get()->releaseNextMod();
|
||||
|
||||
for (auto const& hook : m_hooks) {
|
||||
if (!hook) {
|
||||
log::warn("Hook is null in mod \"{}\"", m_metadata.getName());
|
||||
continue;
|
||||
}
|
||||
if (hook->getAutoEnable()) {
|
||||
auto res = this->enableHook(hook);
|
||||
if (!res) {
|
||||
log::error("Can't enable hook {} for mod {}: {}", hook->getDisplayName(), m_metadata.getID(), res.unwrapErr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& patch : m_patches) {
|
||||
if (!patch) {
|
||||
log::warn("Patch is null in mod \"{}\"", m_metadata.getName());
|
||||
continue;
|
||||
}
|
||||
if (patch->getAutoEnable()) {
|
||||
if (!patch->apply()) {
|
||||
log::warn("Unable to apply patch at {}", patch->getAddress());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_enabled = true;
|
||||
|
||||
ModStateEvent(m_self, ModEventType::Loaded).post();
|
||||
|
@ -461,79 +441,90 @@ bool Mod::Impl::depends(std::string_view const id) const {
|
|||
|
||||
// Hooks
|
||||
|
||||
Result<> Mod::Impl::enableHook(Hook* hook) {
|
||||
auto res = hook->enable();
|
||||
if (!res) {
|
||||
log::error("Can't enable hook {} for mod {}: {}", hook->getDisplayName(), m_metadata.getID(), res.unwrapErr());
|
||||
Result<> Mod::Impl::claimHook(Hook* hook) {
|
||||
auto res1 = hook->m_impl->setOwner(m_self);
|
||||
if (!res1) {
|
||||
return Err("Cannot claim hook: {}", res1.unwrapErr());
|
||||
}
|
||||
m_hooks.push_back(hook);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result<> Mod::Impl::disableHook(Hook* hook) {
|
||||
return hook->disable();
|
||||
}
|
||||
|
||||
Result<Hook*> Mod::Impl::addHook(Hook* hook) {
|
||||
if (!ranges::contains(m_hooks, [&](auto const& h) { return h == hook; }))
|
||||
m_hooks.push_back(hook);
|
||||
if (!this->isEnabled() || !hook->getAutoEnable())
|
||||
return Ok();
|
||||
|
||||
if (!LoaderImpl::get()->isReadyToHook()) {
|
||||
LoaderImpl::get()->addUninitializedHook(hook, m_self);
|
||||
return Ok(hook);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
if (!this->isEnabled() || !hook->getAutoEnable())
|
||||
return Ok(hook);
|
||||
|
||||
auto res = this->enableHook(hook);
|
||||
if (!res) {
|
||||
delete hook;
|
||||
return Err("Can't create hook: " + res.unwrapErr());
|
||||
auto res2 = hook->enable();
|
||||
if (!res2) {
|
||||
return Err("Cannot enable hook: {}", res2.unwrapErr());
|
||||
}
|
||||
|
||||
return Ok(hook);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::Impl::removeHook(Hook* hook) {
|
||||
auto res = this->disableHook(hook);
|
||||
if (res) {
|
||||
ranges::remove(m_hooks, hook);
|
||||
delete hook;
|
||||
Result<> Mod::Impl::disownHook(Hook* hook) {
|
||||
if (hook->getOwner() != m_self) {
|
||||
return Err("Cannot disown hook not owned by this mod");
|
||||
}
|
||||
return res;
|
||||
|
||||
auto res1 = hook->m_impl->setOwner(nullptr);
|
||||
if (!res1) {
|
||||
return Err("Cannot disown hook: {}", res1.unwrapErr());
|
||||
}
|
||||
m_hooks.erase(std::find(m_hooks.begin(), m_hooks.end(), hook));
|
||||
|
||||
if (!this->isEnabled() || !hook->getAutoEnable())
|
||||
return Ok();
|
||||
|
||||
auto res2 = hook->disable();
|
||||
if (!res2) {
|
||||
return Err("Cannot disable hook: {}", res2.unwrapErr());
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Patches
|
||||
|
||||
// TODO: replace this with a safe one
|
||||
static ByteVector readMemory(void* address, size_t amount) {
|
||||
ByteVector ret;
|
||||
for (size_t i = 0; i < amount; i++) {
|
||||
ret.push_back(*reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(address) + i));
|
||||
Result<> Mod::Impl::claimPatch(Patch* patch) {
|
||||
auto res1 = patch->m_impl->setOwner(m_self);
|
||||
if (!res1) {
|
||||
return Err("Cannot claim patch: {}", res1.unwrapErr());
|
||||
}
|
||||
return ret;
|
||||
m_patches.push_back(patch);
|
||||
|
||||
if (!this->isEnabled() || !patch->getAutoEnable())
|
||||
return Ok();
|
||||
|
||||
auto res2 = patch->enable();
|
||||
if (!res2) {
|
||||
return Err("Cannot enable patch: {}", res2.unwrapErr());
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<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");
|
||||
}
|
||||
|
||||
auto res1 = patch->m_impl->setOwner(nullptr);
|
||||
if (!res1) {
|
||||
return Err("Cannot disown patch: {}", res1.unwrapErr());
|
||||
}
|
||||
m_patches.erase(std::find(m_patches.begin(), m_patches.end(), patch));
|
||||
|
||||
if (!this->isEnabled() || !patch->getAutoEnable())
|
||||
return Ok();
|
||||
|
||||
auto res2 = patch->disable();
|
||||
if (!res2) {
|
||||
return Err("Cannot disable patch: {}", res2.unwrapErr());
|
||||
}
|
||||
m_patches.push_back(p);
|
||||
return Ok(p);
|
||||
}
|
||||
|
||||
Result<> Mod::Impl::unpatch(Patch* patch) {
|
||||
if (!patch->restore())
|
||||
return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
|
||||
ranges::remove(m_patches, patch);
|
||||
delete patch;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <matjson.hpp>
|
||||
#include "ModPatch.hpp"
|
||||
|
||||
namespace geode {
|
||||
class Mod::Impl {
|
||||
|
@ -112,13 +113,14 @@ namespace geode {
|
|||
SettingValue* getSetting(std::string_view const key) const;
|
||||
void registerCustomSetting(std::string_view const key, std::unique_ptr<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<> claimHook(Hook* hook);
|
||||
Result<> disownHook(Hook* hook);
|
||||
[[nodiscard]] std::vector<Hook*> getHooks() const;
|
||||
|
||||
Result<> claimPatch(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)) {}
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
|
69
loader/src/loader/PatchImpl.cpp
Normal file
69
loader/src/loader/PatchImpl.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
Patch* Patch::Impl::create(void* address, const geode::ByteVector& patch) {
|
||||
auto impl = std::make_shared<Impl>(
|
||||
address, readMemory(address, patch.size()), patch
|
||||
);
|
||||
auto p = new Patch(std::move(impl));
|
||||
impl->m_self = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
Result<> Patch::Impl::enable() {
|
||||
// TODO: add overlap checking
|
||||
auto res = tulip::hook::writeMemory(m_address, m_patch.data(), m_patch.size());
|
||||
if (!res) return Err(res.unwrapErr());
|
||||
m_enabled = true;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Patch::Impl::disable() {
|
||||
auto res = tulip::hook::writeMemory(m_address, m_original.data(), m_original.size());
|
||||
if (!res) return Err(res.unwrapErr());
|
||||
m_enabled = false;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
uintptr_t Patch::Impl::getAddress() const {
|
||||
return reinterpret_cast<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;
|
||||
}
|
31
loader/src/loader/PatchImpl.hpp
Normal file
31
loader/src/loader/PatchImpl.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#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 Patch* create(void* address, const ByteVector& patch);
|
||||
|
||||
Patch* m_self = nullptr;
|
||||
void* m_address;
|
||||
ByteVector m_original;
|
||||
ByteVector m_patch;
|
||||
|
||||
Result<> enable();
|
||||
Result<> disable();
|
||||
|
||||
uintptr_t getAddress() const;
|
||||
matjson::Value getRuntimeInfo() const;
|
||||
|
||||
friend class Patch;
|
||||
friend class Mod;
|
||||
};
|
Loading…
Reference in a new issue