refactor Hook/Patch

This commit is contained in:
ConfiG 2024-01-15 00:42:04 +03:00
parent 5ff74e849a
commit 28c91f762e
No known key found for this signature in database
GPG key ID: 44DA1983F524C11B
17 changed files with 546 additions and 441 deletions

View file

@ -4,7 +4,7 @@
#include "../utils/general.hpp"
#include <matjson.hpp>
#include "Tulip.hpp"
#include <inttypes.h>
#include <cinttypes>
#include <string_view>
#include <tulip/TulipHook.hpp>
@ -16,21 +16,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;
};
}

View file

@ -229,12 +229,6 @@ namespace geode {
return sharedMod<>;
}
/**
* Get all hooks owned by this Mod
* @returns Vector of hooks
*/
std::vector<Hook*> getHooks() const;
/**
* Create a hook at an address. Call the original
* function by calling the original function
@ -250,38 +244,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

View file

@ -21,7 +21,6 @@
break; \
} \
auto hook = Hook::create( \
Mod::get(), \
reinterpret_cast<void*>(address), \
AsStaticFunction_##FunctionName_< \
Derived, \
@ -38,7 +37,6 @@
if constexpr (HasConstructor<Derived>) { \
static auto address = AddressInline_; \
auto hook = Hook::create( \
Mod::get(), \
reinterpret_cast<void*>(address), \
AsStaticFunction_##constructor< \
Derived, \
@ -55,7 +53,6 @@
if constexpr (HasDestructor<Derived>) { \
static auto address = AddressInline_; \
auto hook = Hook::create( \
Mod::get(), \
reinterpret_cast<void*>(address), \
AsStaticFunction_##destructor<Derived, decltype(Resolve<>::func(&Derived::destructor))>::value, \
#ClassName_ "::" #ClassName_, \
@ -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());
}
}
}

View file

@ -123,6 +123,13 @@ namespace geode {
}
}
template<>
struct matjson::Serialize<geode::ByteVector> {
static matjson::Value to_json(geode::ByteVector const& bytes) {
return matjson::Array(bytes.begin(), bytes.end());
}
};
namespace geode::utils::clipboard {
GEODE_DLL bool write(std::string const& data);
GEODE_DLL std::string read();

View file

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

View file

@ -1,15 +1,19 @@
#include "HookImpl.hpp"
#include <utility>
#include "LoaderImpl.hpp"
Hook::Impl::Impl(void* address, void* detour, std::string const& displayName, tulip::hook::HandlerMetadata const& handlerMetadata, tulip::hook::HookMetadata const& hookMetadata, Mod* owner) :
Hook::Impl::Impl(
void* address,
void* detour,
std::string displayName,
tulip::hook::HandlerMetadata handlerMetadata,
tulip::hook::HookMetadata const& hookMetadata) :
m_address(address),
m_detour(detour),
m_displayName(displayName),
m_handlerMetadata(handlerMetadata),
m_hookMetadata(hookMetadata),
m_owner(owner),
m_enabled(false),
m_autoEnable(true) {}
m_displayName(std::move(displayName)),
m_handlerMetadata(std::move(handlerMetadata)),
m_hookMetadata(hookMetadata) {}
Hook::Impl::~Impl() {
if (m_enabled) {
auto res = this->disable();
@ -17,20 +21,82 @@ Hook::Impl::~Impl() {
log::error("Failed to disable hook: {}", res.unwrapErr());
}
}
if (m_owner) {
auto res = m_owner->disownHook(m_self);
if (!res) {
log::error("Failed to disown hook: {}", res.unwrapErr());
}
}
}
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();
}

View file

@ -1,3 +1,5 @@
#pragma once
#include <Geode/loader/Hook.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Mod.hpp>
@ -5,47 +7,59 @@
#include <Geode/utils/ranges.hpp>
#include <vector>
#include "ModImpl.hpp"
#include "ModPatch.hpp"
using namespace geode::prelude;
class Hook::Impl {
class Hook::Impl final : ModPatch {
public:
Impl(
void* address,
void* detour,
std::string const& displayName,
tulip::hook::HandlerMetadata const& handlerMetadata,
tulip::hook::HookMetadata const& hookMetadata,
Mod* owner
std::string displayName,
tulip::hook::HandlerMetadata handlerMetadata,
tulip::hook::HookMetadata const& hookMetadata
);
~Impl();
static 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;
};

View file

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

View file

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

View file

@ -121,32 +121,28 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr<Sett
return m_impl->registerCustomSetting(key, std::move(value));
}
Result<> 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() {

View file

@ -1,6 +1,8 @@
#include "ModImpl.hpp"
#include "LoaderImpl.hpp"
#include "ModMetadataImpl.hpp"
#include "HookImpl.hpp"
#include "PatchImpl.hpp"
#include "about.hpp"
#include "console.hpp"
@ -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();
}

View file

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

View file

@ -0,0 +1,25 @@
#include "ModPatch.hpp"
Mod* ModPatch::getOwner() const {
return m_owner;
}
Result<> ModPatch::setOwner(geode::Mod* mod) {
if (mod && m_owner) {
return Err("Cannot directly replace owner of an already owned mod");
}
m_owner = mod;
return Ok();
}
bool ModPatch::isEnabled() const {
return m_enabled;
}
bool ModPatch::getAutoEnable() const {
return m_autoEnable;
}
void ModPatch::setAutoEnable(bool autoEnable) {
m_autoEnable = autoEnable;
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <Geode/loader/Mod.hpp>
using namespace geode::prelude;
class ModPatch {
public:
Mod* m_owner = nullptr;
bool m_enabled = false;
bool m_autoEnable = true;
[[nodiscard]] Mod* getOwner() const;
Result<> setOwner(Mod* mod);
[[nodiscard]] bool isEnabled() const;
virtual Result<> enable() = 0;
virtual Result<> disable() = 0;
[[nodiscard]] bool getAutoEnable() const;
void setAutoEnable(bool autoEnable);
};

View file

@ -1,58 +1,43 @@
#include <Geode/loader/Hook.hpp>
#include <matjson.hpp>
#include "PatchImpl.hpp"
using namespace geode::prelude;
bool Patch::apply() {
bool res = bool(tulip::hook::writeMemory(m_address, m_patch.data(), m_patch.size()));
if (res)
m_applied = true;
return res;
}
Patch::Patch(std::shared_ptr<Impl>&& impl) : m_impl(std::move(impl)) {}
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();
}

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

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