diff --git a/soh/soh/Enhancements/debugger/hookDebugger.cpp b/soh/soh/Enhancements/debugger/hookDebugger.cpp new file mode 100644 index 000000000..b31ddc396 --- /dev/null +++ b/soh/soh/Enhancements/debugger/hookDebugger.cpp @@ -0,0 +1,111 @@ +#include "hookDebugger.h" +#include "../game-interactor/GameInteractor.h" +#include "../../UIWidgets.hpp" +#include +#include + +static std::unordered_map> hookData; + +const ImVec4 grey = ImVec4(0.75, 0.75, 0.75, 1); +const ImVec4 yellow = ImVec4(1, 1, 0, 1); +const ImVec4 red = ImVec4(1, 0, 0, 1); + +void DrawHookRegisteringInfos(const char* hookName) { + if (hookData[hookName].size() == 0) { + ImGui::TextColored(grey, "No hooks found"); + return; + } + + if (ImGui::BeginTable( + ("Table##" + std::string(hookName)).c_str(), + 4, + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit + )) { + ImGui::TableSetupColumn("Id"); + ImGui::TableSetupColumn("Type"); + ImGui::TableSetupColumn("Registration Info"); + //ImGui::TableSetupColumn("Stub"); + ImGui::TableSetupColumn("Number of Calls"); + ImGui::TableHeadersRow(); + for (auto& [id, hookInfo] : hookData[hookName]) { + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text("%d", id); + + ImGui::TableNextColumn(); + switch (hookInfo.registering.type) { + case HOOK_TYPE_NORMAL: + ImGui::Text("Normal"); + break; + case HOOK_TYPE_ID: + ImGui::Text("Id"); + break; + case HOOK_TYPE_PTR: + ImGui::Text("Ptr"); + break; + case HOOK_TYPE_FILTER: + ImGui::Text("Filter"); + break; + default: + ImGui::TextColored(red, "[UNKNOWN]"); + break; + } + + ImGui::TableNextColumn(); + if (hookInfo.registering.valid) { + ImGui::Text("%s(%d:%d) %s", hookInfo.registering.file, hookInfo.registering.line, hookInfo.registering.column, hookInfo.registering.function); + } else { + ImGui::TextColored(yellow, "[Unavaliable]"); + } + + //TODO: not currently possible + /* + ImGui::TableNextColumn(); + + ImGui::BeginDisabled(); + + bool stubButtonPressed = ImGui::Button(("Stub##" + std::to_string(id)).c_str()); + UIWidgets::SetLastItemHoverText("Stub this hook.\nThis is not possible to automatically undo."); + + if (stubButtonPressed) { + //stub + } + + ImGui::EndDisabled(); + */ + + ImGui::TableNextColumn(); + ImGui::Text("%d", hookInfo.calls); + } + ImGui::EndTable(); + } +} + +void HookDebuggerWindow::DrawElement() { +#ifndef __cpp_lib_source_location + ImGui::TextColored( + yellow, + "Some features of the Hook Debugger are unavaliable because SoH was compiled " + "without \"\" support " + "(\"__cpp_lib_source_location\" not defined in \"\")." + ); +#endif + + for (auto& [hookName, _] : hookData) { + if (ImGui::TreeNode(hookName)) { + DrawHookRegisteringInfos(hookName); + ImGui::TreePop(); + } + } +} + +void HookDebuggerWindow::UpdateElement() { + hookData.clear(); + + #define DEFINE_HOOK(name, _) hookData.insert({#name, GameInteractor::Instance->GetHookData()}); + + #include "../game-interactor/GameInteractor_HookTable.h" + + #undef DEFINE_HOOK +} \ No newline at end of file diff --git a/soh/soh/Enhancements/debugger/hookDebugger.h b/soh/soh/Enhancements/debugger/hookDebugger.h new file mode 100644 index 000000000..90e6886b5 --- /dev/null +++ b/soh/soh/Enhancements/debugger/hookDebugger.h @@ -0,0 +1,10 @@ +#include + +class HookDebuggerWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override {}; + void DrawElement() override; + void UpdateElement() override; +}; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index ce5a18b6e..f97fe9f26 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -458,6 +458,12 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); #include #include #include +#include +#ifdef __cpp_lib_source_location +#include +#else +#pragma message("Compiling without support, the Hook Debugger will not be avaliable") +#endif #ifdef ENABLE_REMOTE_CONTROL #include @@ -466,11 +472,40 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); typedef uint32_t HOOK_ID; -#define DEFINE_HOOK(name, args) \ - struct name { \ - typedef std::function fn; \ - typedef std::function filter; \ - } +enum HookType { + HOOK_TYPE_NORMAL, + HOOK_TYPE_ID, + HOOK_TYPE_PTR, + HOOK_TYPE_FILTER, +}; + +struct HookRegisteringInfo { + bool valid; + const char* file; + std::uint_least32_t line; + std::uint_least32_t column; + const char* function; + HookType type; + + HookRegisteringInfo() : valid(false), file("unknown file"), line(0), column(0), function("unknown function"), type(HOOK_TYPE_NORMAL) {} + + HookRegisteringInfo(const char* _file, std::uint_least32_t _line, std::uint_least32_t _column, const char* _function, HookType _type) : + valid(true), file(_file), line(_line), column(_column), function(_function), type(_type) {} +}; + +struct HookInfo { + uint32_t calls; + HookRegisteringInfo registering; + + HookInfo() : calls(0), registering(HookRegisteringInfo{}) {} + HookInfo(HookRegisteringInfo _registering) : calls(0), registering(_registering) {} +}; + +#ifdef __cpp_lib_source_location +#define GET_CURRENT_REGISTERING_INFO(type) HookRegisteringInfo{location.file_name(), location.line(), location.column(), location.function_name(), type} +#else +#define GET_CURRENT_REGISTERING_INFO(type) HookRegisteringInfo{} +#endif #define REGISTER_VB_SHOULD(flag, body) \ GameInteractor::Instance->RegisterGameHookForID(flag, [](GIVanillaBehavior _, bool* should, void* opt) body) @@ -479,7 +514,7 @@ class GameInteractor { public: static GameInteractor* Instance; - // Gsme State + // Game State class State { public: static bool NoUIActive; @@ -532,7 +567,11 @@ public: inline static std::unordered_map> functionsForID; inline static std::unordered_map> functionsForPtr; inline static std::unordered_map> functionsForFilter; + + //Used for the hook debugger + inline static std::unordered_map hookData; }; + template struct HooksToUnregister { inline static std::vector hooks; inline static std::vector hooksForID; @@ -540,50 +579,74 @@ public: inline static std::vector hooksForFilter; }; + template std::unordered_map GetHookData() { + return RegisteredGameHooks::hookData; + } + // General Hooks - template HOOK_ID RegisterGameHook(typename H::fn h) { + template HOOK_ID RegisterGameHook( + typename H::fn h +#ifdef __cpp_lib_source_location + , const std::source_location location = std::source_location::current() +#endif + ) { + // Ensure hook id is unique and not 0, which is reserved for invalid hooks if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; while (RegisteredGameHooks::functions.find(this->nextHookId) != RegisteredGameHooks::functions.end()) { this->nextHookId++; } RegisteredGameHooks::functions[this->nextHookId] = h; + RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_NORMAL)}; return this->nextHookId++; } + 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); + RegisteredGameHooks::hookData.erase(hookId); } HooksToUnregister::hooks.clear(); for (auto& hook : RegisteredGameHooks::functions) { hook.second(std::forward(args)...); + RegisteredGameHooks::hookData[hook.first].calls += 1; } } // ID based Hooks - template HOOK_ID RegisterGameHookForID(int32_t id, typename H::fn h) { + template HOOK_ID RegisterGameHookForID( + int32_t id, typename H::fn h +#ifdef __cpp_lib_source_location + , const std::source_location location = std::source_location::current() +#endif + ) { if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; while (RegisteredGameHooks::functionsForID[id].find(this->nextHookId) != RegisteredGameHooks::functionsForID[id].end()) { this->nextHookId++; } RegisteredGameHooks::functionsForID[id][this->nextHookId] = h; + RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_ID)}; 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()); + RegisteredGameHooks::hookData.erase(hookId); } else { ++it; } @@ -591,29 +654,39 @@ public: } for (auto& hook : RegisteredGameHooks::functionsForID[id]) { hook.second(std::forward(args)...); + RegisteredGameHooks::hookData[hook.first].calls += 1; } } // PTR based Hooks - template HOOK_ID RegisterGameHookForPtr(uintptr_t ptr, typename H::fn h) { + template HOOK_ID RegisterGameHookForPtr( + uintptr_t ptr, typename H::fn h +#ifdef __cpp_lib_source_location + , const std::source_location location = std::source_location::current() +#endif + ) { if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; while (RegisteredGameHooks::functionsForPtr[ptr].find(this->nextHookId) != RegisteredGameHooks::functionsForPtr[ptr].end()) { this->nextHookId++; } RegisteredGameHooks::functionsForPtr[ptr][this->nextHookId] = h; + RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_PTR)}; 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()); + RegisteredGameHooks::hookData.erase(hookId); } else { ++it; } @@ -621,31 +694,42 @@ public: } for (auto& hook : RegisteredGameHooks::functionsForPtr[ptr]) { hook.second(std::forward(args)...); + RegisteredGameHooks::hookData[hook.first].calls += 1; } } // Filter based Hooks - template HOOK_ID RegisterGameHookForFilter(typename H::filter f, typename H::fn h) { + template HOOK_ID RegisterGameHookForFilter( + typename H::filter f, typename H::fn h +#ifdef __cpp_lib_source_location + , const std::source_location location = std::source_location::current() +#endif + ) { 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); + RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_FILTER)}; 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); + RegisteredGameHooks::hookData.erase(hookId); } HooksToUnregister::hooksForFilter.clear(); for (auto& hook : RegisteredGameHooks::functionsForFilter) { if (hook.second.first(std::forward(args)...)) { hook.second.second(std::forward(args)...); + RegisteredGameHooks::hookData[hook.first].calls += 1; } } } @@ -672,59 +756,15 @@ public: } }; - 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 DEFINE_HOOK(name, args) \ + struct name { \ + typedef std::function fn; \ + typedef std::function filter; \ + } - DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, void* opt)); +#include "GameInteractor_HookTable.h" - 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, ()); +#undef DEFINE_HOOK // Helpers static bool IsSaveLoaded(bool allowDbgSave = false); @@ -785,5 +825,7 @@ public: #endif }; +#undef GET_CURRENT_REGISTERING_INFO + #endif /* __cplusplus */ #endif /* GameInteractor_h */ diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h new file mode 100644 index 000000000..a3fc284ee --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -0,0 +1,57 @@ +/** + * Hook Table + * + * DEFINE_HOOK arguments: + * - Argument 1: Name of the hook + * - Argument 2: Function type that the hook uses + */ +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, ()); \ No newline at end of file diff --git a/soh/soh/SohGui.cpp b/soh/soh/SohGui.cpp index 4bef8cea6..eda8641ef 100644 --- a/soh/soh/SohGui.cpp +++ b/soh/soh/SohGui.cpp @@ -122,6 +122,7 @@ namespace SohGui { std::shared_ptr mActorViewerWindow; std::shared_ptr mColViewerWindow; std::shared_ptr mSaveEditorWindow; + std::shared_ptr mHookDebuggerWindow; std::shared_ptr mDLViewerWindow; std::shared_ptr mValueViewerWindow; std::shared_ptr mMessageViewerWindow; @@ -179,6 +180,8 @@ namespace SohGui { gui->AddGuiWindow(mColViewerWindow); mSaveEditorWindow = std::make_shared(CVAR_WINDOW("SaveEditor"), "Save Editor", ImVec2(520, 600)); gui->AddGuiWindow(mSaveEditorWindow); + mHookDebuggerWindow = std::make_shared(CVAR_WINDOW("HookDebugger"), "Hook Debugger", ImVec2(1250, 850)); + gui->AddGuiWindow(mHookDebuggerWindow); mDLViewerWindow = std::make_shared(CVAR_WINDOW("DLViewer"), "Display List Viewer", ImVec2(520, 600)); gui->AddGuiWindow(mDLViewerWindow); mValueViewerWindow = std::make_shared(CVAR_WINDOW("ValueViewer"), "Value Viewer", ImVec2(520, 600)); @@ -226,6 +229,7 @@ namespace SohGui { mValueViewerWindow = nullptr; mMessageViewerWindow = nullptr; mSaveEditorWindow = nullptr; + mHookDebuggerWindow = nullptr; mColViewerWindow = nullptr; mActorViewerWindow = nullptr; mCosmeticsEditorWindow = nullptr; diff --git a/soh/soh/SohGui.hpp b/soh/soh/SohGui.hpp index 5152bf69c..9bdedc504 100644 --- a/soh/soh/SohGui.hpp +++ b/soh/soh/SohGui.hpp @@ -16,6 +16,7 @@ #include "Enhancements/debugger/actorViewer.h" #include "Enhancements/debugger/colViewer.h" #include "Enhancements/debugger/debugSaveEditor.h" +#include "Enhancements/debugger/hookDebugger.h" #include "Enhancements/debugger/dlViewer.h" #include "Enhancements/debugger/valueViewer.h" #include "Enhancements/gameplaystatswindow.h" diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 35f0b6ae1..31ef681fc 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -28,6 +28,7 @@ #include "Enhancements/debugger/actorViewer.h" #include "Enhancements/debugger/colViewer.h" #include "Enhancements/debugger/debugSaveEditor.h" +#include "Enhancements/debugger/hookDebugger.h" #include "Enhancements/debugger/dlViewer.h" #include "Enhancements/debugger/valueViewer.h" #include "Enhancements/gameplaystatswindow.h" @@ -1761,6 +1762,7 @@ void DrawCheatsMenu() { extern std::shared_ptr mStatsWindow; extern std::shared_ptr mConsoleWindow; extern std::shared_ptr mSaveEditorWindow; +extern std::shared_ptr mHookDebuggerWindow; extern std::shared_ptr mColViewerWindow; extern std::shared_ptr mActorViewerWindow; extern std::shared_ptr mDLViewerWindow; @@ -1844,6 +1846,12 @@ void DrawDeveloperToolsMenu() { } } UIWidgets::Spacer(0); + if (mHookDebuggerWindow) { + if (ImGui::Button(GetWindowButtonText("Hook Debugger", CVarGetInteger(CVAR_WINDOW("HookDebugger"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { + mHookDebuggerWindow->ToggleVisibility(); + } + } + UIWidgets::Spacer(0); if (mColViewerWindow) { if (ImGui::Button(GetWindowButtonText("Collision Viewer", CVarGetInteger(CVAR_WINDOW("CollisionViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { mColViewerWindow->ToggleVisibility();