#pragma once #include "AsStaticFunction.hpp" #include "Field.hpp" #include #include "IDManager.hpp" #include #include #include #include #define GEODE_APPLY_MODIFY_FOR_FUNCTION(AddressInline_, Convention_, ClassName_, FunctionName_, ...) \ do { \ static auto constexpr different = Unique::different< \ Resolve<__VA_ARGS__>::func(&Base::FunctionName_), \ Resolve<__VA_ARGS__>::func(&Derived::FunctionName_) \ >(); \ using BaseFuncType = decltype(Resolve<__VA_ARGS__>::func(&Base::FunctionName_)); \ using DerivedFuncType = decltype(Resolve<__VA_ARGS__>::func(&Derived::FunctionName_)); \ if constexpr (different) { \ static auto address = AddressInline_; \ static_assert( \ !different || !std::is_same_v::type, TodoReturn>, \ "Function" #ClassName_ "::" #FunctionName_ " has a TodoReturn type, " \ "please fix it by editing the bindings." \ ); \ if (address == 0) { \ log::error( \ "Address of {} returned nullptr, can't hook", #ClassName_ "::" #FunctionName_ \ ); \ break; \ } \ auto hook = Hook::create( \ reinterpret_cast(address), \ AsStaticFunction_##FunctionName_< \ Derived, \ DerivedFuncType>::value, \ #ClassName_ "::" #FunctionName_, \ tulip::hook::TulipConvention::Convention_ \ ); \ this->m_hooks[#ClassName_ "::" #FunctionName_] = hook; \ } \ } while (0); #define GEODE_APPLY_MODIFY_FOR_FUNCTION_ERROR(ClassName_, FunctionName_, ...) \ do { \ static_assert(!FunctionExists_##FunctionName_, \ "Function " #ClassName_ "::" #FunctionName_ " does not have an available address in the" \ " bindings, please add it to the bindings to hook it." \ ); \ } while (0); #define GEODE_APPLY_MODIFY_FOR_FUNCTION_ERROR_DEFINED(ClassName_, FunctionName_, ...) \ do { \ static auto constexpr different = Unique::different< \ Resolve<__VA_ARGS__>::func(&Base::FunctionName_), \ Resolve<__VA_ARGS__>::func(&Derived::FunctionName_) \ >(); \ static_assert(!different, \ "Function " #ClassName_ "::" #FunctionName_ " does not have an available address in the" \ " bindings, please add it to the bindings to hook it." \ ); \ } while (0); #define GEODE_APPLY_MODIFY_FOR_FUNCTION_ERROR_INLINE(ClassName_, FunctionName_, ...) \ do { \ static auto constexpr different = Unique::different< \ Resolve<__VA_ARGS__>::func(&Base::FunctionName_), \ Resolve<__VA_ARGS__>::func(&Derived::FunctionName_) \ >(); \ static_assert(!different, \ "Function " #ClassName_ "::" #FunctionName_ " cannot be hooked due to an inline definition" \ " existing for the function." \ ); \ } while (0); #define GEODE_APPLY_MODIFY_FOR_CONSTRUCTOR(AddressInline_, Convention_, ClassName_, ...) \ do { \ if constexpr (HasConstructor) { \ static auto address = AddressInline_; \ auto hook = Hook::create( \ reinterpret_cast(address), \ AsStaticFunction_##constructor< \ Derived, \ decltype(Resolve<__VA_ARGS__>::func(&Derived::constructor))>::value, \ #ClassName_ "::" #ClassName_, \ tulip::hook::TulipConvention::Convention_ \ ); \ this->m_hooks[#ClassName_ "::" #ClassName_] = hook; \ } \ } while (0); #define GEODE_APPLY_MODIFY_FOR_DESTRUCTOR(AddressInline_, Convention_, ClassName_) \ do { \ if constexpr (HasDestructor) { \ static auto address = AddressInline_; \ auto hook = Hook::create( \ reinterpret_cast(address), \ AsStaticFunction_##destructor::func(&Derived::destructor))>::value, \ #ClassName_ "::" #ClassName_, \ tulip::hook::TulipConvention::Convention_ \ ); \ this->m_hooks[#ClassName_ "::" #ClassName_] = hook; \ } \ } while (0); namespace geode { class Priority { public: static inline constexpr int32_t First = -3000; static inline constexpr int32_t VeryEarly = -2000; static inline constexpr int32_t Early = -1000; static inline constexpr int32_t Normal = 0; static inline constexpr int32_t Late = 1000; static inline constexpr int32_t VeryLate = 2000; static inline constexpr int32_t Last = 3000; static inline constexpr int32_t FirstPre = First; static inline constexpr int32_t VeryEarlyPre = VeryEarly; static inline constexpr int32_t EarlyPre = Early; static inline constexpr int32_t NormalPre = Normal; static inline constexpr int32_t LatePre = Late; static inline constexpr int32_t VeryLatePre = VeryLate; static inline constexpr int32_t LastPre = Last; static inline constexpr int32_t FirstPost = Last; static inline constexpr int32_t VeryEarlyPost = VeryLate; static inline constexpr int32_t EarlyPost = Late; static inline constexpr int32_t NormalPost = Normal; static inline constexpr int32_t LatePost = Early; static inline constexpr int32_t VeryLatePost = VeryEarly; static inline constexpr int32_t LastPost = First; }; } namespace geode::modifier { template class ModifyDerive; template class ModifyBase { public: std::map> m_hooks; /// @brief Get a hook by name /// @param name The name of the hook to get /// @returns Ok if the hook was found, Err if the hook was not found Result 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].get()); } /// @brief Set the priority of a hook /// @param name The name of the hook to set the priority of /// @param priority The priority to set the hook to /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found Result<> setHookPriority(std::string const& name, int32_t priority = Priority::Normal) { GEODE_UNWRAP_INTO(auto hook, this->getHook(name)); hook->setPriority(priority); return Ok(); } /// @brief Set the priority of a hook /// @param name The name of the hook to set the priority of /// @param priority The priority to set the hook to /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found Result<> setHookPriorityPre(std::string const& name, int32_t priority = Priority::Normal) { return this->setHookPriority(name, priority); } /// @brief Set the priority of a hook /// @param name The name of the hook to set the priority of /// @param priority The priority to set the hook to /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found Result<> setHookPriorityPost(std::string const& name, int32_t priority = Priority::Normal) { return this->setHookPriority(name, -priority); } /// @brief Set the priority of a hook to be after another hook in different mods /// @param name The name of the hook to set the priority of /// @param after The mod ids of the mods to set the priority after /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found template requires (std::is_convertible_v && ...) Result<> setHookPriorityAfter(std::string const& name, C&&... after) { GEODE_UNWRAP_INTO(auto hook, this->getHook(name)); ([&](){ auto mod = Loader::get()->getInstalledMod(after); if (!mod) return; auto hooks = mod->getHooks(); auto func = [=](){ for (auto modHook : hooks) { if (modHook->getAddress() != hook->getAddress()) continue; auto priority = hook->getPriority(); if (priority < modHook->getPriority()) { hook->setPriority(modHook->getPriority() + 1); } } }; if (Loader::get()->isModLoaded(mod)) { func(); } else { new EventListener(func, ModStateFilter(mod, ModEventType::Loaded)); } } (), ...); return Ok(); } /// @brief Set the priority of a hook to be before another hook in different mods /// @param name The name of the hook to set the priority of /// @param before The mod ids of the mods to set the priority before /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found template requires (std::is_convertible_v && ...) Result<> setHookPriorityBefore(std::string const& name, C&&... before) { GEODE_UNWRAP_INTO(auto hook, this->getHook(name)); ([&](){ auto mod = Loader::get()->getInstalledMod(before); if (!mod) return; auto hooks = mod->getHooks(); auto func = [=](){ for (auto modHook : hooks) { if (modHook->getAddress() != hook->getAddress()) continue; auto priority = hook->getPriority(); if (priority > modHook->getPriority()) { hook->setPriority(modHook->getPriority() - 1); } } }; if (Loader::get()->isModLoaded(mod)) { func(); } else { new EventListener(func, ModStateFilter(mod, ModEventType::Loaded)); } } (), ...); return Ok(); } /// @brief Set the priority of a hook to be after another hook in different mods /// @param name The name of the hook to set the priority of /// @param after The mod ids of the mods to set the priority after /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found template requires (std::is_convertible_v && ...) Result<> setHookPriorityAfterPre(std::string const& name, C&&... after) { return this->setHookPriorityAfter(name, Priority::NormalPre, std::forward(after)...); } /// @brief Set the priority of a hook to be before another hook in different mods /// @param name The name of the hook to set the priority of /// @param before The mod ids of the mods to set the priority before /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found template requires (std::is_convertible_v && ...) Result<> setHookPriorityBeforePre(std::string const& name, C&&... before) { return this->setHookPriorityBefore(name, Priority::NormalPre, std::forward(before)...); } /// @brief Set the priority of a hook to be after another hook in different mods /// @param name The name of the hook to set the priority of /// @param after The mod ids of the mods to set the priority after /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found template requires (std::is_convertible_v && ...) Result<> setHookPriorityAfterPost(std::string const& name, C&&... after) { return this->setHookPriorityBefore(name, Priority::NormalPost, std::forward(after)...); } /// @brief Set the priority of a hook to be before another hook in different mods /// @param name The name of the hook to set the priority of /// @param before The mod ids of the mods to set the priority before /// @returns Ok if the hook was found and the priority was set, Err if the hook was not found template requires (std::is_convertible_v && ...) Result<> setHookPriorityBeforePost(std::string const& name, C&&... before) { return this->setHookPriorityAfter(name, Priority::NormalPost, std::forward(before)...); } // unordered_map idea ModifyBase() { struct EboCheck : ModifyDerived::Base { std::aligned_storage_t< std::alignment_of_v, std::alignment_of_v > m_padding; }; static constexpr auto baseSize = sizeof(typename ModifyDerived::Base); static constexpr auto derivedSize = sizeof(typename ModifyDerived::Derived); static constexpr auto alignment = std::alignment_of_v; static constexpr bool hasEbo = sizeof(EboCheck) == sizeof(typename ModifyDerived::Base); static constexpr bool hasImproperCustomFields = hasEbo ? derivedSize != baseSize : derivedSize != baseSize + alignment; static_assert(!hasImproperCustomFields, "\n--- Error in modify class:\n" " Do not add members to a modify class, use `struct Fields` instead.\n" " See https://docs.geode-sdk.org/tutorials/fields for more info." "\n---" ); // i really dont want to recompile codegen auto test = static_cast(this); test->ModifyDerived::apply(); ModifyDerived::Derived::onModify(*this); std::vector added; for (auto& [uuid, hook] : m_hooks) { auto res = Mod::get()->claimHook(hook); if (!res) { log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.unwrapErr()); } else { added.push_back(uuid); } } for (auto& uuid : added) { m_hooks.erase(uuid); } } virtual void apply() {} template friend class ModifyDerive; // explicit Modify(Property property) idea }; template class ModifyDerive { public: ModifyDerive() { static_assert( alwaysFalse, "Modified class not recognized, please include to be " "able to use it." ); } }; } namespace geode { // The intellisense compiler is quite dumb, and will very often error on modify classes // with an error of "incomplete type is not allowed", despite not being an issue in actual compilation. // So as a workaround use the compiler defined "__INTELLISENSE__" macro, which gets set to 1 on the intellisense pass. // See https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170#microsoft-specific-predefined-macros #if __INTELLISENSE__ != 1 && !defined(__CLION_IDE__) template class Modify : public Base { private: static inline modifier::ModifyDerive s_apply; // because for some reason we need it static inline auto s_applyRef = &Modify::s_apply; public: // abusing the internal stuff // basically we dont want modify to invoke base ctors and dtors // we already have utilities for these, which are ccdestructor // and the cutoff constructor Modify() : Base(CutoffConstructor, sizeof(Base)) {} ~Modify() { cocos2d::CCDestructor::lock(this) = true; } Modify(Modify const&) = delete; Modify(Modify&&) = delete; Modify& operator=(Modify const&) = delete; Modify& operator=(Modify&&) = delete; modifier::FieldIntermediate m_fields; static void onModify(auto& self) {} }; #else template class Modify : public Base { public: modifier::FieldIntermediate m_fields; }; #endif } /** * Main class implementation, it has the structure * * class hook0Dummy; * template * struct hook0 {}; * namespace { * struct hook0Parent {}; * } * template<> * struct GEODE_HIDDEN hook0 : Modify, MenuLayer> { * // code stuff idk * }; * * I tried to make the macro as verbose as it can be but * I am bad at this stuff */ #if __INTELLISENSE__ != 1 && !defined(__CLION_IDE__) #define GEODE_MODIFY_DECLARE_ANONYMOUS(base, derived) \ derived##Dummy; \ template \ struct derived {}; \ namespace { \ struct derived##Parent {}; \ } \ template <> \ struct GEODE_HIDDEN derived : geode::Modify, base> #define GEODE_MODIFY_DECLARE(base, derived) \ derived##Dummy; \ struct GEODE_HIDDEN derived : geode::Modify #else // Simplify the modify macro for intellisense, to hopefully help perfomance a bit #define GEODE_MODIFY_DECLARE(base, derived) \ derived##Dummy; \ struct derived : geode::Modify #define GEODE_MODIFY_DECLARE_ANONYMOUS(base, derived) GEODE_MODIFY_DECLARE(base, derived) #endif #define GEODE_MODIFY_REDIRECT4(base, derived) GEODE_MODIFY_DECLARE(base, derived) #define GEODE_MODIFY_REDIRECT3(base, derived) GEODE_MODIFY_DECLARE_ANONYMOUS(base, derived) #define GEODE_MODIFY_REDIRECT2(base) GEODE_MODIFY_REDIRECT3(base, GEODE_CONCAT(hook, __LINE__)) #define GEODE_MODIFY_REDIRECT1(base) GEODE_MODIFY_REDIRECT2(base) /** * Interfaces for the class implementation * * class $modify(MenuLayer) {}; * class $modify(MyMenuLayerInterface, MenuLayer) {}; */ #define GEODE_CRTP1(base) GEODE_MODIFY_REDIRECT1(base) #define GEODE_CRTP2(derived, base) GEODE_MODIFY_REDIRECT4(base, derived) #define $modify(...) \ GEODE_INVOKE(GEODE_CONCAT(GEODE_CRTP, GEODE_NUMBER_OF_ARGS(__VA_ARGS__)), __VA_ARGS__) /** * This function is meant to hook / override a GD function in a Modified class. * **This is merely an annotation for clarity** - while there may be linters that * check for it, it is not required */ #define $override