diff --git a/VERSION b/VERSION index 524cb552..867e5243 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.1 +1.2.0 \ No newline at end of file diff --git a/loader/include/Geode/Utils.hpp b/loader/include/Geode/Utils.hpp index 5aa821cf..0ccd436e 100644 --- a/loader/include/Geode/Utils.hpp +++ b/loader/include/Geode/Utils.hpp @@ -11,3 +11,4 @@ #include "utils/general.hpp" #include "utils/timer.hpp" #include "utils/MiniFunction.hpp" +#include "utils/ObjcHook.hpp" diff --git a/loader/include/Geode/utils/ObjcHook.hpp b/loader/include/Geode/utils/ObjcHook.hpp new file mode 100644 index 00000000..7e156777 --- /dev/null +++ b/loader/include/Geode/utils/ObjcHook.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "../loader/Hook.hpp" +#include "Result.hpp" + +namespace geode { + namespace hook { + /** + * Add a new Objective-C method to a class. This method will be created + * using the imp provided. If the method already exists, it won't do + * anything. + * @param className The name of the class to add the method to + * @param selectorName The name of the method to add + * @param imp The implementation of the method + * @returns Ok() if the method was added successfully, or an error. + */ + Result<> addObjcMethod(std::string const& className, std::string const& selectorName, void* imp); + + /** + * Get the implementation of an Objective-C method. + * @param className The name of the class whose method to get + * @param selectorName The name of the method to get + * @returns The implementation of the method, or an error. + */ + Result<void*> getObjcMethodImp(std::string const& className, std::string const& selectorName); + } + + class ObjcHook { + public: + /** + * Create a hook for an Objective-C method + * @param className The name of the class whose method to hook + * @param selectorName The name of the method to hook + * @param function The detour to run when the method is called + * @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()) { + GEODE_UNWRAP_INTO(auto imp, geode::hook::getObjcMethodImp(className, selectorName)); + + return Ok(Hook::create( + getMod(), + imp, + function, + className + "::" + selectorName, + tulip::hook::TulipConvention::Default, + metadata + )); + } + + /** + * Create a hook for a new Objective-C method. This method will be + * created with a dummy implementation that does nothing. + * @param className The name of the class whose method to hook + * @param selectorName The name of the method to hook + * @param function The detour to run when the method is called + * @param empty A function that takes no arguments and returns nothing. + * This is used to create a dummy method that can be hooked. + * @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()) { + GEODE_UNWRAP(geode::hook::addObjcMethod(className, selectorName, (void*)empty)); + + return ObjcHook::create(className, selectorName, function, metadata); + } + }; +} \ No newline at end of file diff --git a/loader/src/platform/mac/util.mm b/loader/src/platform/mac/util.mm index cd2c83c0..dbe564cd 100644 --- a/loader/src/platform/mac/util.mm +++ b/loader/src/platform/mac/util.mm @@ -7,9 +7,7 @@ using namespace geode::prelude; #include <Geode/loader/Dirs.hpp> #import <AppKit/AppKit.h> -#include <Geode/utils/web.hpp> -#include <Geode/utils/file.hpp> -#include <Geode/utils/cocos.hpp> +#include <Geode/Utils.hpp> #include <Geode/binding/GameManager.hpp> bool utils::clipboard::write(std::string const& data) { @@ -235,4 +233,29 @@ void geode::utils::game::restart() { ), CCDirector::get()->getRunningScene(), false); } +Result<> geode::hook::addObjcMethod(std::string const& className, std::string const& selectorName, void* imp) { + auto cls = objc_getClass(className.c_str()); + if (!cls) + return Err("Class not found"); + + auto sel = sel_registerName(selectorName.c_str()); + + class_addMethod(cls, sel, (IMP)imp, "v@:"); + + return Ok(); +} +Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::string const& selectorName) { + auto cls = objc_getClass(className.c_str()); + if (!cls) + return Err("Class not found"); + + auto sel = sel_registerName(selectorName.c_str()); + + auto method = class_getInstanceMethod(cls, sel); + if (!method) + return Err("Method not found"); + + return Ok((void*)method_getImplementation(method)); +} + #endif diff --git a/loader/src/platform/windows/util.cpp b/loader/src/platform/windows/util.cpp index 7ecb7322..74291a38 100644 --- a/loader/src/platform/windows/util.cpp +++ b/loader/src/platform/windows/util.cpp @@ -184,4 +184,11 @@ void geode::utils::game::restart() { exit(0); } +Result<> geode::hook::addObjcMethod(std::string const& className, std::string const& selectorName, void* imp) { + return Err("Wrong platform"); +} +Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::string const& selectorName) { + return Err("Wrong platform"); +} + #endif