diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 3a3a30d18..cca978b2d 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -431,11 +431,17 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); #include #endif -#define DEFINE_HOOK(name, type) \ - struct name { \ - typedef std::function fn; \ +typedef uint32_t HOOK_ID; + +#define DEFINE_HOOK(name, args) \ + struct name { \ + typedef std::function fn; \ + typedef std::function filter; \ } +#define REGISTER_VB_SHOULD(flag, body) \ + GameInteractor::Instance->RegisterGameHookForID(flag, [](GIVanillaBehavior _, bool* should, void* opt) body) + class GameInteractor { public: static GameInteractor* Instance; @@ -487,11 +493,22 @@ public: static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect); // Game Hooks - uint32_t nextHookId = 1; - template struct RegisteredGameHooks { inline static std::unordered_map functions; }; - template struct HooksToUnregister { inline static std::vector hooks; }; - template uint32_t RegisterGameHook(typename H::fn h) { - // Ensure hook id is unique and not 0, which is reserved for invalid hooks + HOOK_ID nextHookId = 1; + template struct RegisteredGameHooks { + inline static std::unordered_map functions; + inline static std::unordered_map> functionsForID; + inline static std::unordered_map> functionsForPtr; + inline static std::unordered_map> functionsForFilter; + }; + template struct HooksToUnregister { + inline static std::vector hooks; + inline static std::vector hooksForID; + inline static std::vector hooksForPtr; + inline static std::vector hooksForFilter; + }; + + // General Hooks + template HOOK_ID RegisterGameHook(typename H::fn h) { if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; while (RegisteredGameHooks::functions.find(this->nextHookId) != RegisteredGameHooks::functions.end()) { this->nextHookId++; @@ -500,10 +517,10 @@ public: RegisteredGameHooks::functions[this->nextHookId] = h; return this->nextHookId++; } - template void UnregisterGameHook(uint32_t id) { - HooksToUnregister::hooks.push_back(id); + template void UnregisterGameHook(HOOK_ID hookId) { + if (hookId == 0) return; + HooksToUnregister::hooks.push_back(hookId); } - template void ExecuteHooks(Args&&... args) { for (auto& hookId : HooksToUnregister::hooks) { RegisteredGameHooks::functions.erase(hookId); @@ -514,59 +531,167 @@ public: } } - DEFINE_HOOK(OnLoadGame, void(int32_t fileNum)); - DEFINE_HOOK(OnExitGame, void(int32_t fileNum)); - DEFINE_HOOK(OnGameFrameUpdate, void()); - DEFINE_HOOK(OnItemReceive, void(GetItemEntry itemEntry)); - DEFINE_HOOK(OnSaleEnd, void(GetItemEntry itemEntry)); - DEFINE_HOOK(OnTransitionEnd, void(int16_t sceneNum)); - DEFINE_HOOK(OnSceneInit, void(int16_t sceneNum)); - DEFINE_HOOK(OnSceneFlagSet, void(int16_t sceneNum, int16_t flagType, int16_t flag)); - DEFINE_HOOK(OnSceneFlagUnset, void(int16_t sceneNum, int16_t flagType, int16_t flag)); - DEFINE_HOOK(OnFlagSet, void(int16_t flagType, int16_t flag)); - DEFINE_HOOK(OnFlagUnset, void(int16_t flagType, int16_t flag)); - DEFINE_HOOK(OnSceneSpawnActors, void()); - DEFINE_HOOK(OnPlayerUpdate, void()); - DEFINE_HOOK(OnOcarinaSongAction, void()); - DEFINE_HOOK(OnShopSlotChange, void(uint8_t cursorIndex, int16_t price)); - DEFINE_HOOK(OnActorInit, void(void* actor)); - DEFINE_HOOK(OnActorUpdate, void(void* actor)); - DEFINE_HOOK(OnActorKill, void(void* actor)); - DEFINE_HOOK(OnEnemyDefeat, void(void* actor)); - DEFINE_HOOK(OnPlayerBonk, void()); - DEFINE_HOOK(OnPlayDestroy, void()); - DEFINE_HOOK(OnPlayDrawEnd, void()); + // ID based Hooks + template HOOK_ID RegisterGameHookForID(int32_t id, typename H::fn h) { + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; + while (RegisteredGameHooks::functionsForID[id].find(this->nextHookId) != RegisteredGameHooks::functionsForID[id].end()) { + this->nextHookId++; + } - DEFINE_HOOK(OnVanillaBehavior, void(GIVanillaBehavior flag, bool* result, void* opt)); + RegisteredGameHooks::functionsForID[id][this->nextHookId] = h; + return this->nextHookId++; + } + template void UnregisterGameHookForID(HOOK_ID hookId) { + if (hookId == 0) return; + HooksToUnregister::hooksForID.push_back(hookId); + } + template void ExecuteHooksForID(int32_t id, Args&&... args) { + for (auto& hookId : HooksToUnregister::hooksForID) { + for (auto it = RegisteredGameHooks::functionsForID[id].begin(); it != RegisteredGameHooks::functionsForID[id].end(); ) { + if (it->first == hookId) { + it = RegisteredGameHooks::functionsForID[id].erase(it); + HooksToUnregister::hooksForID.erase(std::remove(HooksToUnregister::hooksForID.begin(), HooksToUnregister::hooksForID.end(), hookId), HooksToUnregister::hooksForID.end()); + } else { + ++it; + } + } + } + for (auto& hook : RegisteredGameHooks::functionsForID[id]) { + hook.second(std::forward(args)...); + } + } - DEFINE_HOOK(OnSaveFile, void(int32_t fileNum)); - DEFINE_HOOK(OnLoadFile, void(int32_t fileNum)); - DEFINE_HOOK(OnDeleteFile, void(int32_t fileNum)); - - DEFINE_HOOK(OnDialogMessage, void()); - DEFINE_HOOK(OnPresentTitleCard, void()); - DEFINE_HOOK(OnInterfaceUpdate, void()); - DEFINE_HOOK(OnKaleidoscopeUpdate, void(int16_t inDungeonScene)); - - DEFINE_HOOK(OnPresentFileSelect, void()); - DEFINE_HOOK(OnUpdateFileSelectSelection, void(uint16_t optionIndex)); - DEFINE_HOOK(OnUpdateFileSelectConfirmationSelection, void(uint16_t optionIndex)); - DEFINE_HOOK(OnUpdateFileCopySelection, void(uint16_t optionIndex)); - DEFINE_HOOK(OnUpdateFileCopyConfirmationSelection, void(uint16_t optionIndex)); - DEFINE_HOOK(OnUpdateFileEraseSelection, void(uint16_t optionIndex)); - DEFINE_HOOK(OnUpdateFileEraseConfirmationSelection, void(uint16_t optionIndex)); - DEFINE_HOOK(OnUpdateFileAudioSelection, void(uint8_t optionIndex)); - DEFINE_HOOK(OnUpdateFileTargetSelection, void(uint8_t optionIndex)); - DEFINE_HOOK(OnUpdateFileLanguageSelection, void(uint8_t optionIndex)); - DEFINE_HOOK(OnUpdateFileQuestSelection, void(uint8_t questIndex)); - DEFINE_HOOK(OnUpdateFileBossRushOptionSelection, void(uint8_t optionIndex, uint8_t optionValue)); - DEFINE_HOOK(OnUpdateFileNameSelection, void(int16_t charCode)); - - DEFINE_HOOK(OnSetGameLanguage, void()); + // PTR based Hooks + template HOOK_ID RegisterGameHookForPtr(uintptr_t ptr, typename H::fn h) { + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; + while (RegisteredGameHooks::functionsForPtr[ptr].find(this->nextHookId) != RegisteredGameHooks::functionsForPtr[ptr].end()) { + this->nextHookId++; + } - DEFINE_HOOK(OnFileDropped, void(std::string filePath)); - DEFINE_HOOK(OnAssetAltChange, void()); - DEFINE_HOOK(OnKaleidoUpdate, void()); + RegisteredGameHooks::functionsForPtr[ptr][this->nextHookId] = h; + return this->nextHookId++; + } + template void UnregisterGameHookForPtr(HOOK_ID hookId) { + if (hookId == 0) return; + HooksToUnregister::hooksForPtr.push_back(hookId); + } + template void ExecuteHooksForPtr(uintptr_t ptr, Args&&... args) { + for (auto& hookId : HooksToUnregister::hooksForPtr) { + for (auto it = RegisteredGameHooks::functionsForPtr[ptr].begin(); it != RegisteredGameHooks::functionsForPtr[ptr].end(); ) { + if (it->first == hookId) { + it = RegisteredGameHooks::functionsForPtr[ptr].erase(it); + HooksToUnregister::hooksForPtr.erase(std::remove(HooksToUnregister::hooksForPtr.begin(), HooksToUnregister::hooksForPtr.end(), hookId), HooksToUnregister::hooksForPtr.end()); + } else { + ++it; + } + } + } + for (auto& hook : RegisteredGameHooks::functionsForPtr[ptr]) { + hook.second(std::forward(args)...); + } + } + + // Filter based Hooks + template HOOK_ID RegisterGameHookForFilter(typename H::filter f, typename H::fn h) { + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; + while (RegisteredGameHooks::functionsForFilter.find(this->nextHookId) != RegisteredGameHooks::functionsForFilter.end()) { + this->nextHookId++; + } + + RegisteredGameHooks::functionsForFilter[this->nextHookId] = std::make_pair(f, h); + return this->nextHookId++; + } + template void UnregisterGameHookForFilter(HOOK_ID hookId) { + if (hookId == 0) return; + HooksToUnregister::hooksForFilter.push_back(hookId); + } + template void ExecuteHooksForFilter(Args&&... args) { + for (auto& hookId : HooksToUnregister::hooksForFilter) { + RegisteredGameHooks::functionsForFilter.erase(hookId); + } + HooksToUnregister::hooksForFilter.clear(); + for (auto& hook : RegisteredGameHooks::functionsForFilter) { + if (hook.second.first(std::forward(args)...)) { + hook.second.second(std::forward(args)...); + } + } + } + + class HookFilter { + public: + static auto ActorNotPlayer(Actor* actor) { + return actor->id != ACTOR_PLAYER; + } + // For use with Should hooks + static auto SActorNotPlayer(Actor* actor, bool* result) { + return actor->id != ACTOR_PLAYER; + } + static auto ActorMatchIdAndParams(int16_t id, int16_t params) { + return [id, params](Actor* actor) { + return actor->id == id && actor->params == params; + }; + } + // For use with Should hooks + static auto SActorMatchIdAndParams(int16_t id, int16_t params) { + return [id, params](Actor* actor, bool* result) { + return actor->id == id && actor->params == params; + }; + } + }; + + DEFINE_HOOK(OnLoadGame, (int32_t fileNum)); + DEFINE_HOOK(OnExitGame, (int32_t fileNum)); + DEFINE_HOOK(OnGameFrameUpdate, ()); + DEFINE_HOOK(OnItemReceive, (GetItemEntry itemEntry)); + DEFINE_HOOK(OnSaleEnd, (GetItemEntry itemEntry)); + DEFINE_HOOK(OnTransitionEnd, (int16_t sceneNum)); + DEFINE_HOOK(OnSceneInit, (int16_t sceneNum)); + DEFINE_HOOK(OnSceneFlagSet, (int16_t sceneNum, int16_t flagType, int16_t flag)); + DEFINE_HOOK(OnSceneFlagUnset, (int16_t sceneNum, int16_t flagType, int16_t flag)); + DEFINE_HOOK(OnFlagSet, (int16_t flagType, int16_t flag)); + DEFINE_HOOK(OnFlagUnset, (int16_t flagType, int16_t flag)); + DEFINE_HOOK(OnSceneSpawnActors, ()); + DEFINE_HOOK(OnPlayerUpdate, ()); + DEFINE_HOOK(OnOcarinaSongAction, ()); + DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price)); + DEFINE_HOOK(OnActorInit, (void* actor)); + DEFINE_HOOK(OnActorUpdate, (void* actor)); + DEFINE_HOOK(OnActorKill, (void* actor)); + DEFINE_HOOK(OnEnemyDefeat, (void* actor)); + DEFINE_HOOK(OnPlayerBonk, ()); + DEFINE_HOOK(OnPlayDestroy, ()); + DEFINE_HOOK(OnPlayDrawEnd, ()); + + DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, void* opt)); + + DEFINE_HOOK(OnSaveFile, (int32_t fileNum)); + DEFINE_HOOK(OnLoadFile, (int32_t fileNum)); + DEFINE_HOOK(OnDeleteFile, (int32_t fileNum)); + + DEFINE_HOOK(OnDialogMessage, ()); + DEFINE_HOOK(OnPresentTitleCard, ()); + DEFINE_HOOK(OnInterfaceUpdate, ()); + DEFINE_HOOK(OnKaleidoscopeUpdate, (int16_t inDungeonScene)); + + DEFINE_HOOK(OnPresentFileSelect, ()); + DEFINE_HOOK(OnUpdateFileSelectSelection, (uint16_t optionIndex)); + DEFINE_HOOK(OnUpdateFileSelectConfirmationSelection, (uint16_t optionIndex)); + DEFINE_HOOK(OnUpdateFileCopySelection, (uint16_t optionIndex)); + DEFINE_HOOK(OnUpdateFileCopyConfirmationSelection, (uint16_t optionIndex)); + DEFINE_HOOK(OnUpdateFileEraseSelection, (uint16_t optionIndex)); + DEFINE_HOOK(OnUpdateFileEraseConfirmationSelection, (uint16_t optionIndex)); + DEFINE_HOOK(OnUpdateFileAudioSelection, (uint8_t optionIndex)); + DEFINE_HOOK(OnUpdateFileTargetSelection, (uint8_t optionIndex)); + DEFINE_HOOK(OnUpdateFileLanguageSelection, (uint8_t optionIndex)); + DEFINE_HOOK(OnUpdateFileQuestSelection, (uint8_t questIndex)); + DEFINE_HOOK(OnUpdateFileBossRushOptionSelection, (uint8_t optionIndex, uint8_t optionValue)); + DEFINE_HOOK(OnUpdateFileNameSelection, (int16_t charCode)); + + DEFINE_HOOK(OnSetGameLanguage, ()); + + DEFINE_HOOK(OnFileDropped, (std::string filePath)); + DEFINE_HOOK(OnAssetAltChange, ()); + DEFINE_HOOK(OnKaleidoUpdate, ()); // Helpers static bool IsSaveLoaded(bool allowDbgSave = false); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index b0c119798..93c8fc75e 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -16,34 +16,44 @@ void GameInteractor_ExecuteOnGameFrameUpdate() { void GameInteractor_ExecuteOnItemReceiveHooks(GetItemEntry itemEntry) { GameInteractor::Instance->ExecuteHooks(itemEntry); + GameInteractor::Instance->ExecuteHooksForFilter(itemEntry); } void GameInteractor_ExecuteOnSaleEndHooks(GetItemEntry itemEntry) { GameInteractor::Instance->ExecuteHooks(itemEntry); + GameInteractor::Instance->ExecuteHooksForFilter(itemEntry); } void GameInteractor_ExecuteOnTransitionEndHooks(int16_t sceneNum) { GameInteractor::Instance->ExecuteHooks(sceneNum); + GameInteractor::Instance->ExecuteHooksForID(sceneNum, sceneNum); + GameInteractor::Instance->ExecuteHooksForFilter(sceneNum); } void GameInteractor_ExecuteOnSceneInitHooks(int16_t sceneNum) { GameInteractor::Instance->ExecuteHooks(sceneNum); + GameInteractor::Instance->ExecuteHooksForID(sceneNum, sceneNum); + GameInteractor::Instance->ExecuteHooksForFilter(sceneNum); } void GameInteractor_ExecuteOnSceneFlagSet(int16_t sceneNum, int16_t flagType, int16_t flag) { GameInteractor::Instance->ExecuteHooks(sceneNum, flagType, flag); + GameInteractor::Instance->ExecuteHooksForFilter(sceneNum, flagType, flag); } void GameInteractor_ExecuteOnSceneFlagUnset(int16_t sceneNum, int16_t flagType, int16_t flag) { GameInteractor::Instance->ExecuteHooks(sceneNum, flagType, flag); + GameInteractor::Instance->ExecuteHooksForFilter(sceneNum, flagType, flag); } void GameInteractor_ExecuteOnFlagSet(int16_t flagType, int16_t flag) { GameInteractor::Instance->ExecuteHooks(flagType, flag); + GameInteractor::Instance->ExecuteHooksForFilter(flagType, flag); } void GameInteractor_ExecuteOnFlagUnset(int16_t flagType, int16_t flag) { GameInteractor::Instance->ExecuteHooks(flagType, flag); + GameInteractor::Instance->ExecuteHooksForFilter(flagType, flag); } void GameInteractor_ExecuteOnSceneSpawnActors() { @@ -64,18 +74,30 @@ void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t pr void GameInteractor_ExecuteOnActorInit(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); + GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor); + GameInteractor::Instance->ExecuteHooksForFilter(actor); } void GameInteractor_ExecuteOnActorUpdate(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); + GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor); + GameInteractor::Instance->ExecuteHooksForFilter(actor); } void GameInteractor_ExecuteOnActorKill(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); + GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor); + GameInteractor::Instance->ExecuteHooksForFilter(actor); } void GameInteractor_ExecuteOnEnemyDefeat(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); + GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor); + GameInteractor::Instance->ExecuteHooksForFilter(actor); } void GameInteractor_ExecuteOnPlayerBonk() { @@ -92,6 +114,11 @@ void GameInteractor_ExecuteOnPlayDrawEnd() { bool GameInteractor_Should(GIVanillaBehavior flag, bool result, void* opt) { GameInteractor::Instance->ExecuteHooks(flag, &result, opt); + GameInteractor::Instance->ExecuteHooksForID(flag, flag, &result, opt); + if (opt != nullptr) { + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)opt, flag, &result, opt); + } + GameInteractor::Instance->ExecuteHooksForFilter(flag, &result, opt); return result; }