From 195cda8292925ca83a403dd3329f5fbca3a079b4 Mon Sep 17 00:00:00 2001 From: linuxversion <1660477+SomeCrazyGuy@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:27:15 -0400 Subject: [PATCH] Version 1.4.1 --- Starfield Console Replacer.vcxproj | 2 + VersionInfo.rc | Bin 4730 -> 4730 bytes src/console.cpp | 217 +++++++++++------------------ src/game_hooks.cpp | 213 ++++++++++++++++++++++++++++ src/game_hooks.h | 25 ++++ src/gui.cpp | 8 +- src/log_buffer.cpp | 3 +- src/main.cpp | 17 ++- src/main.h | 5 +- 9 files changed, 344 insertions(+), 146 deletions(-) create mode 100644 src/game_hooks.cpp create mode 100644 src/game_hooks.h diff --git a/Starfield Console Replacer.vcxproj b/Starfield Console Replacer.vcxproj index 28e8c83..2b46df9 100644 --- a/Starfield Console Replacer.vcxproj +++ b/Starfield Console Replacer.vcxproj @@ -111,6 +111,7 @@ copy /Y "$(OutDir)$(TargetName).dll" "$(OutDir)BetterConsole.asi" + @@ -137,6 +138,7 @@ copy /Y "$(OutDir)$(TargetName).dll" "$(OutDir)BetterConsole.asi" + diff --git a/VersionInfo.rc b/VersionInfo.rc index 3f64dd430b9d8700f57bd33353800a7c17fc125d..d3c91b561cc896b0ea0c725c99d8a08e214521e5 100644 GIT binary patch delta 42 wcmeyR@=ImIBo0Qy$&)z@84Wid<;Z0Qa(wxuCmZmJZBF5N!3Y%f< #include -// Console print interface: -//48 89 5c 24 ?? 48 89 6c 24 ?? 48 89 74 24 ?? 57 b8 30 10 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 49 - new sig with 1 match -//48 89 5c 24 ?? 48 89 6c 24 ?? 48 89 74 24 ?? 57 b8 30 10 -- the 0x1030 stack size might be too unique -/* - undefined4 uVar1; - int iVar2; - char local_1010 [4096]; - undefined8 local_10; - - local_10 = 0x142883992; - uVar1 = FUN_140839080(); - FUN_140587784(0x40); - - //calling vsnprintf() with 4096 buffer and using the return value to append "\n\0" - iVar2 = FUN_1412518e8(local_1010,0x1000,param_2,param_4,param_3); - if (0 < iVar2) { - local_1010[iVar2] = '\n'; //note the behavior of adding "\n\0" - local_1010[iVar2 + 1] = '\0'; - FUN_14288379c(param_1,local_1010,param_2); - } - FUN_140587784(uVar1); - return; -*/ - -// Console run command interface: -//48 8b c4 48 89 50 ?? 4c 89 40 ?? 4c 89 48 ?? 55 53 56 57 41 55 41 56 41 57 48 8d -/* -check for this in ghidra: - -_strnicmp((char *)local_838,"ForEachRef[",0xb) -... -memset(local_c38,0,0x400); -pcVar15 = "float fresult\nref refr\nset refr to GetSelectedRef\nset fresult to "; - -*/ - -// Forced offsets 1.12.32 -#define OFFSET_CONSOLE_PRINT 0x29cd9b8 -#define OFFSET_CONSOLE_RUN 0x29c7f44 - -#define OUTPUT_FILE_PATH "BetterConsoleOutput.txt" -#define HISTORY_FILE_PATH "BetterConsoleHistory.txt" +#include "game_hooks.h" + #define NUM_HOTKEY_COMMANDS 16 @@ -55,16 +15,10 @@ enum class InputMode : uint32_t { SearchHistory, }; -static void console_run(void* consolemgr, char* cmd); -static void console_print(void* consolemgr, const char* fmt, va_list args); - static int CALLBACK_inputtext_cmdline(ImGuiInputTextCallbackData* data); static int CALLBACK_inputtext_switch_mode(ImGuiInputTextCallbackData* data); static bool strcasestr(const char* haystack, const char* needle); -static void(*OLD_ConsolePrintV)(void*, const char*, va_list) = nullptr; -static void(*OLD_ConsoleRun)(void*, char*) = nullptr; - static bool UpdateScroll = false; static bool UpdateFocus = true; static int HistoryIndex = -1; @@ -75,10 +29,9 @@ static const hook_api_t* HookAPI = nullptr; static const log_buffer_api_t* LogBuffer = nullptr; static const simple_draw_t* SimpleDraw = nullptr; static const config_api_t* Config = nullptr; +static const auto GameHook = GameHook_GetData(); static char IOBuffer[256 * 1024]; -static LogBufferHandle OutputHandle; -static LogBufferHandle HistoryHandle; static std::vector SearchOutputLines{}; static std::vector SearchHistoryLines{}; @@ -91,83 +44,112 @@ struct Command { } HotkeyCommands[NUM_HOTKEY_COMMANDS]; -static void forward_to_old_consoleprint(void* consolemgr, const char* fmt, ...) { - if (!consolemgr) return; - va_list args; - va_start(args, fmt); - OLD_ConsolePrintV(consolemgr, fmt, args); - va_end(args); -} +static void draw_console_window(void* imgui_context) { + (void)imgui_context; + static bool ConsoleReadyIgnored = false; + if (!GameHook->ConsoleReadyFlag && !ConsoleReadyIgnored) { + SimpleDraw->Text("Cannot detect if the console is ready. Proceed anyway?"); + if (SimpleDraw->Button("Ignore and continue")) { + ConsoleReadyIgnored = true; + } + return; + } -static void console_print(void* consolemgr, const char* fmt, va_list args) { - auto size = vsnprintf(IOBuffer, sizeof(IOBuffer), fmt, args); - if (size <= 0) return; - forward_to_old_consoleprint(consolemgr, "%s", IOBuffer); //send it already converted - LogBuffer->Append(OutputHandle, IOBuffer); - IOBuffer[0] = 0; - UpdateScroll = true; -} + if (!(GameHook->ConsoleRun && GameHook->ConsoleOutputHooked)) { + if (!GameHook->ConsoleOutputHooked) { + SimpleDraw->Text("Cannot hook console print function. cannot start console"); + } + if (!GameHook->ConsoleRun) { + SimpleDraw->Text("Cannot hook console run function. cannot start console"); + } -static void console_run(void* consolemgr, char* cmd) { - LogBuffer->Append(HistoryHandle, cmd); - if (OLD_ConsoleRun) { - OLD_ConsoleRun(consolemgr, cmd); + SimpleDraw->Text("Incompatible mod loaded or incompatible game version"); + SimpleDraw->Text("BetterConsole '%s' is compatible with game version '%s'", BETTERCONSOLE_VERSION, GAME_VERSION); + return; } - else { - DEBUG("console hook not ready when running command: %s", cmd); + + static bool GamePausedIgnored = false; + if (!GameHook->GetGamePausedFlag() && !GamePausedIgnored) { + SimpleDraw->Text("Cannot detect if the game is paused. Proceed anyway?"); + if (SimpleDraw->Button("Ignore and continue")) { + GamePausedIgnored = true; + } + return; } -} -static void draw_console_window(void* imgui_context) { - (void)imgui_context; - static bool GameState = true; - static const char* GameStates[] = { "Paused", "Running" }; - if (ImGui::Button(GameStates[GameState])) { - char tgp[] = "ToggleGamePause"; - console_run(NULL, tgp); - GameState = !GameState; + if(GameHook->ConsoleReadyFlag && !*GameHook->ConsoleReadyFlag) { + SimpleDraw->Text("Waiting for console to become ready..."); + return; + } + + + if (GamePausedIgnored) { + if (SimpleDraw->Button("Toggle Game Pause")) { + GameHook->ConsoleRun("ToggleGamePause"); + } + } + else { + bool* flag = GameHook->GetGamePausedFlag(); + const char* const GamePauseText = *flag ? "Resume Game" : "Pause Game"; + if (SimpleDraw->Button(GamePauseText)) { + *flag = !*flag; + } } ImGui::SameLine(); - ImGui::SetNextItemWidth(-(ImGui::GetFontSize() * 20)); + ImGui::SetNextItemWidth(-(ImGui::GetFontSize() * 12.0f)); + if (UpdateFocus) { ImGui::SetKeyboardFocusHere(); UpdateFocus = false; } if (CommandMode == InputMode::Command) { - if (ImGui::InputText("Command Mode", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_cmdline)) { - HistoryIndex = LogBuffer->GetLineCount(HistoryHandle); - console_run(NULL, IOBuffer); + if (ImGui::InputText("Command Mode ", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_cmdline)) { + HistoryIndex = LogBuffer->GetLineCount(GameHook->ConsoleInput); + GameHook->ConsoleRun(IOBuffer); IOBuffer[0] = 0; UpdateFocus = true; } - SimpleDraw->ShowLogBuffer(OutputHandle, UpdateScroll); + SimpleDraw->SameLine(); + if (SimpleDraw->Button("Clear")) { + LogBuffer->Clear(GameHook->ConsoleOutput); + } + SimpleDraw->ShowLogBuffer(GameHook->ConsoleOutput, UpdateScroll); + } else if (CommandMode == InputMode::SearchOutput) { - if (ImGui::InputText("Search Output", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_switch_mode)) { + if (ImGui::InputText("Search Output ", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_switch_mode)) { SearchOutputLines.clear(); - for (uint32_t i = 0; i < LogBuffer->GetLineCount(OutputHandle); ++i) { - if (strcasestr(LogBuffer->GetLine(OutputHandle, i), IOBuffer)) { + for (uint32_t i = 0; i < LogBuffer->GetLineCount(GameHook->ConsoleOutput); ++i) { + if (strcasestr(LogBuffer->GetLine(GameHook->ConsoleOutput, i), IOBuffer)) { SearchOutputLines.push_back(i); } } } - SimpleDraw->ShowFilteredLogBuffer(OutputHandle, SearchOutputLines.data(), (uint32_t)SearchOutputLines.size(), UpdateScroll); + SimpleDraw->SameLine(); + if (SimpleDraw->Button("Clear")) { + LogBuffer->Clear(GameHook->ConsoleOutput); + } + SimpleDraw->ShowFilteredLogBuffer(GameHook->ConsoleOutput, SearchOutputLines.data(), (uint32_t)SearchOutputLines.size(), UpdateScroll); } else if (CommandMode == InputMode::SearchHistory) { if (ImGui::InputText("Search History", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_switch_mode)) { SearchHistoryLines.clear(); - for (uint32_t i = 0; i < LogBuffer->GetLineCount(HistoryHandle); ++i) { - if (strcasestr(LogBuffer->GetLine(HistoryHandle, i), IOBuffer)) { + for (uint32_t i = 0; i < LogBuffer->GetLineCount(GameHook->ConsoleInput); ++i) { + if (strcasestr(LogBuffer->GetLine(GameHook->ConsoleInput, i), IOBuffer)) { SearchHistoryLines.push_back(i); } } } - SimpleDraw->ShowFilteredLogBuffer(HistoryHandle, SearchHistoryLines.data(), (uint32_t)SearchHistoryLines.size(), UpdateScroll); + SimpleDraw->SameLine(); + if (SimpleDraw->Button("Clear")) { + LogBuffer->Clear(GameHook->ConsoleInput); + } + SimpleDraw->ShowFilteredLogBuffer(GameHook->ConsoleInput, SearchHistoryLines.data(), (uint32_t)SearchHistoryLines.size(), UpdateScroll); } else { ASSERT(false && "Invalid command mode"); @@ -179,7 +161,7 @@ static void draw_console_window(void* imgui_context) { static int CALLBACK_inputtext_cmdline(ImGuiInputTextCallbackData* data) { if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) { - const auto HistoryMax = (int)LogBuffer->GetLineCount(HistoryHandle); + const auto HistoryMax = (int)LogBuffer->GetLineCount(GameHook->ConsoleInput); if (data->EventKey == ImGuiKey_UpArrow) { --HistoryIndex; @@ -198,7 +180,7 @@ static int CALLBACK_inputtext_cmdline(ImGuiInputTextCallbackData* data) { if (HistoryMax) { data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, LogBuffer->GetLine(HistoryHandle, HistoryIndex)); + data->InsertChars(0, LogBuffer->GetLine(GameHook->ConsoleInput, HistoryIndex)); } } else if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) { @@ -265,7 +247,7 @@ static void CALLBACK_console_hotkey(uintptr_t userdata) { auto index = (uint32_t)userdata; char* hotkey = HotkeyCommands[index].data; if (hotkey[0]) { - API->Console->RunCommand(hotkey); + GameHook->ConsoleRun(hotkey); } } @@ -290,49 +272,12 @@ extern void setup_console(const BetterAPI* api) { HookAPI = API->Hook; SimpleDraw = API->SimpleDraw; Config = API->Config; - - OutputHandle = LogBuffer->Create("Console Output", OUTPUT_FILE_PATH); - HistoryHandle = LogBuffer->Restore("Command History", HISTORY_FILE_PATH); - - DEBUG("Hooking print function using AOB method"); - auto hook_print_aob = HookAPI->AOBScanEXE("48 89 5c 24 ?? 48 89 6c 24 ?? 48 89 74 24 ?? 57 b8 30 10 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 49"); - - if (!hook_print_aob) { - DEBUG("hook_print_aob AOB method failed, (incompatible mod or game update?)"); - hook_print_aob = HookAPI->Relocate(OFFSET_CONSOLE_PRINT); - } - - OLD_ConsolePrintV = (decltype(OLD_ConsolePrintV))HookAPI->HookFunction( - (FUNC_PTR)hook_print_aob, - (FUNC_PTR)console_print - ); - - DEBUG("Hooking run function using AOB method"); - auto hook_run_aob = HookAPI->AOBScanEXE("48 8b c4 48 89 50 ?? 4c 89 40 ?? 4c 89 48 ?? 55 53 56 57 41 55 41 56 41 57 48 8d"); - if (!hook_run_aob) { - DEBUG("hook_run_aob AOB method failed, (incompatible mod or game update?)"); - hook_run_aob = HookAPI->Relocate(OFFSET_CONSOLE_RUN); - } - - OLD_ConsoleRun = (decltype(OLD_ConsoleRun))HookAPI->HookFunction( - (FUNC_PTR)hook_run_aob, - (FUNC_PTR)console_run - ); - IOBuffer[0] = 0; } - -static void run_comand(char* command) { - console_run(NULL, command); -} - - -static constexpr struct console_api_t Console { - run_comand -}; - -extern constexpr const struct console_api_t* GetConsoleAPI() { - return &Console; +//probably move this out in the future now that all hooks are in one place +extern const struct console_api_t* GetConsoleAPI() { + static const console_api_t api = { GameHook->ConsoleRun }; + return &api; } \ No newline at end of file diff --git a/src/game_hooks.cpp b/src/game_hooks.cpp new file mode 100644 index 0000000..71e4de8 --- /dev/null +++ b/src/game_hooks.cpp @@ -0,0 +1,213 @@ +#include "main.h" +#include "hook_api.h" +#include "game_hooks.h" + +#include + +static const auto HookAPI = GetHookAPI(); + +static void (*Game_ConsoleRun)(void* consolemgr, char* cmd) = nullptr; +static void(*Game_ConsolePrintV)(void* consolemgr, const char* fmt, va_list args) = nullptr; +static void(*Game_StartingConsoleCommand)(void* A, uint32_t* type) = nullptr; + +static const auto LogBuffer = GetLogBufferAPI(); + +static GameHookData HookData{}; + +extern const GameHookData* GameHook_GetData(void) { + return &HookData; +} + + +static void HookedConsoleRun(void* consolemgr, char* cmd) { + if (HookData.ConsoleReadyFlag && !*HookData.ConsoleReadyFlag) { + DEBUG("StartingConsoleCommand was not called, not ready for console command '%s'", cmd); + } + + Game_ConsoleRun(consolemgr, cmd); + + if (!HookData.ConsoleInput) { + DEBUG("ConsoleInput logbuffer not ready when running command: '%s'", cmd); + return; + } + LogBuffer->Append(HookData.ConsoleInput, cmd); +} + + +static void HookedConsolePrintV(void* consolemgr, const char* fmt, va_list args) { + Game_ConsolePrintV(consolemgr, fmt, args); + + if (!HookData.ConsoleOutput) { + DEBUG("ConsoleOutput logbuffer not ready"); + return; + } + + char buffer[4096]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + LogBuffer->Append(HookData.ConsoleOutput, buffer); +} + + +static void HookedStartingConsoleCommand(void* A, uint32_t* type) { + static bool ready = false; + HookData.ConsoleReadyFlag = &ready; + if (*type == 5) ready = true; + Game_StartingConsoleCommand(A, type); +} + + +static void ConsoleRun(const char* cmd) { + char command[512]; + strncpy_s(command, sizeof(command), cmd, sizeof(command)); + HookedConsoleRun(NULL, command); +} + + +static bool* GetGamePausedFlag() { + static bool* flag_ptr = nullptr; + if (flag_ptr) return flag_ptr; + + static bool func_addr_initialized = false; + static unsigned char* func_addr = nullptr; + + if (func_addr_initialized && func_addr == nullptr) return nullptr; + + DEBUG("Hooking is_game_paused function"); + func_addr = (unsigned char*)GetHookAPI()->AOBScanEXE( + "48 8b 0d ?? ?? ?? ?? " // RCX,QWORD PTR [rip+0x????????] + "80 79 ?? 00 " // CMP BYTE PTR [rcx+0x??],0x00 + "0f 94 c0 " // SETZ AL (AL = ZF) + "88 41 ?? " // MOV BYTE PTR [RCX + 0x??],AL + "b0 01 " // MOV AL,0x1 + "C3" // RET + ); + func_addr_initialized = true; + + if (!func_addr) { + DEBUG("is_game_paused function not found"); + return nullptr; + } + + uint32_t offset; + uint8_t displacement; + memcpy(&offset, &func_addr[3], sizeof(uint32_t)); + memcpy(&displacement, &func_addr[9], sizeof(uint8_t)); + // 7 is because RIP relative displacement is calculated from the following instruction + const auto ptr1 = (unsigned char**)(func_addr + offset + 7); + + if (*ptr1 == nullptr) { + DEBUG("is_game_paused data not ready"); + return nullptr; + } + + flag_ptr = (bool*)(*ptr1 + displacement); + return flag_ptr; +} + +extern void GameHook_Init() { + char path_tmp[260]; + HookData.ConsoleInput = LogBuffer->Restore("ConsoleInput", GetPathInDllDir(path_tmp, "BetterConsoleInput.txt")); + HookData.ConsoleOutput = LogBuffer->Create("ConsoleOutput", GetPathInDllDir(path_tmp, "BetterConsoleOutput.txt")); + + DEBUG("Hooking ExecuteCommand"); + auto OldConsoleRun = (FUNC_PTR) HookAPI->AOBScanEXE( + "48 8b c4 " // MOV RAX, RSP + "48 89 50 ?? " // MOV QWORD PTR [RAX+0x??], RDX + "4c 89 40 ?? " // MOV QWORD PTR [RAX+0x??], R8 + "4c 89 48 ?? " // MOV QWORD PTR [RAX+0x??], R9 + "55 " // PUSH RBP + "53 " // PUSH RBX + "56 " // PUSH RSI + "57 " // PUSH RDI + "41 55 " // PUSH R13 + "41 56 " // PUSH R14 + "41 57 " // PUSH R15 + "48 8d" // LEA + ); + if (!OldConsoleRun) { + DEBUG("Failed to find ExecuteCommand"); + } + else { + Game_ConsoleRun = (decltype(Game_ConsoleRun))HookAPI->HookFunction( + OldConsoleRun, + (FUNC_PTR)HookedConsoleRun + ); + HookData.ConsoleRun = &ConsoleRun; + } + + + DEBUG("Hooking ConsolePrint"); + auto OldConsolePrintV = (FUNC_PTR) HookAPI->AOBScanEXE( + "48 89 5c 24 ?? " // MOV QWORD PTR [RSP+0x??], RBX + "48 89 6c 24 ?? " // MOV QWORD PTR [RSP+0x??], RBP + "48 89 74 24 ?? " // MOV QWORD PTR [RSP+0x??], RSI + "57 " // PUSH RDI + "b8 30 10 00 00 " // MOV EAX, 0x1030 + "e8 ?? ?? ?? ?? " // CALL ?? ?? ?? ?? + "48 2b e0 " // SUB RSP, RAX + "49 8b f8 " // MOV RDI, R8 + ); + if (!OldConsolePrintV) { + DEBUG("Failed to find ConsolePrint"); + } + else { + Game_ConsolePrintV = (decltype(Game_ConsolePrintV))HookAPI->HookFunction( + OldConsolePrintV, + (FUNC_PTR)HookedConsolePrintV + ); + HookData.ConsoleOutputHooked = true; + } + + DEBUG("Hooking OnStartingConsoleCommand"); + auto OldStartingConsoleCommand = (FUNC_PTR)HookAPI->AOBScanEXE( + "40 53 " // PUSH RBX + "48 83 EC ?? " // SUB RSP,0x?? + "8B 02 " // MOV EAX,DWORD PTR [RDX] + "48 8B D9 " // MOV RBX,RCX + "83 F8 ?? " // CMP EAX,0x?? + "75 ?? " // JNE 0x?? + "48" // MOV + ); + + if (!OldStartingConsoleCommand) { + DEBUG("Failed to hook OnStartingConsoleCommand"); + } else { + Game_StartingConsoleCommand = (decltype(Game_StartingConsoleCommand)) HookAPI->HookFunction( + (FUNC_PTR)OldStartingConsoleCommand, + (FUNC_PTR)HookedStartingConsoleCommand + ); + } + + HookData.GetGamePausedFlag = &GetGamePausedFlag; + + DEBUG("Hooking GetFormByID"); + HookData.GetFormByID = (decltype(HookData.GetFormByID))GetHookAPI()->AOBScanEXE( + "88 54 24 ?? " // MOV BYTE PTR [RSP+0x??],DL + "55 " // PUSH RBP + "53 " // PUSH RBX + "56 " // PUSH RSI + "57 " // PUSH RDI + "48 8b ec " // MOV RBP,RSP + "48 83 ec ?? " // SUB RSP, 0x?? + "48 8b f9 " // MOV RDI,RCX + "33 f6 " // XOR ESI,ESI + "48 85 c9 " // TEST RCX,RCX + ); + + if (!HookData.GetFormByID) { + DEBUG("Failed to find GetFormByID"); + } + + + DEBUG("Hooking GetFormName"); + HookData.GetFormName = (decltype(HookData.GetFormName))GetHookAPI()->AOBScanEXE( + "48 89 4c 24 ?? " // MOV QWORD PTR [RSP+0x??], RCX + "53 " // PUSH RBX + "48 83 EC ?? " // SUB RSP, 0x?? + "48 8D 1D ?? ?? ?? ?? " // LEA RBX, QWORD PTR [rip+0x???] + "48 85 C9 " // TEST RCX, RCX + ); + if (!HookData.GetFormName) { + DEBUG("Failed to find GetFormName"); + } +} \ No newline at end of file diff --git a/src/game_hooks.h b/src/game_hooks.h new file mode 100644 index 0000000..269fc6c --- /dev/null +++ b/src/game_hooks.h @@ -0,0 +1,25 @@ +#pragma once +#include "main.h" + +typedef struct GameHookData { + void (*ConsoleRun)(const char* command); + void* (*GetFormByID)(const char* form_identifier); + + //Using this scheme: https://falloutck.uesp.net/wiki/Template:INI:Papyrus:sTraceStatusOfQuest + const char* (*GetFormName)(void* form); + + bool* (*GetGamePausedFlag)(); + + bool* ConsoleReadyFlag; + LogBufferHandle ConsoleInput; + LogBufferHandle ConsoleOutput; + bool ConsoleOutputHooked; +} GameHookData; + + + + +extern const GameHookData* GameHook_GetData(void); + +// when game launches: +extern void GameHook_Init(); \ No newline at end of file diff --git a/src/gui.cpp b/src/gui.cpp index 7faa981..06eed06 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -18,7 +18,7 @@ extern void draw_gui() { auto imgui_context = ImGui::GetCurrentContext(); ImGui::Begin("BetterConsole"); - ImGui::BeginTabBar("mod tabs"); + ImGui::BeginTabBar("mod tabs", ImGuiTabBarFlags_FittingPolicyScroll | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs); if (GetSettings()->FontScaleOverride == 0) { GetSettingsMutable()->FontScaleOverride = 100; @@ -75,14 +75,16 @@ extern void draw_gui() { ImGui::EndTabBar(); ImGui::EndTabItem(); } - + uint32_t draw_count = 0; const auto draw_callback = CallbackGetHandles(CALLBACKTYPE_DRAW, &draw_count); for (uint32_t i = 0; i < draw_count; ++i) { const auto handle = draw_callback[i]; ImGui::PushID(handle); - if (ImGui::BeginTabItem(CallbackGetName(handle))) { + static bool focus_tab = false; + if (ImGui::BeginTabItem(CallbackGetName(handle), nullptr, (!focus_tab)? ImGuiTabItemFlags_SetSelected : 0)) { + focus_tab = true; CallbackGetCallback(CALLBACKTYPE_DRAW, handle).draw_callback(imgui_context); ImGui::EndTabItem(); } diff --git a/src/log_buffer.cpp b/src/log_buffer.cpp index f928945..ba0c681 100644 --- a/src/log_buffer.cpp +++ b/src/log_buffer.cpp @@ -157,7 +157,8 @@ static constexpr struct log_buffer_api_t LogBufferAPI { &LogBufferGetLineCount, &LogBufferGetLine, &LogBufferAppend, - &LogBufferRestore + &LogBufferRestore, + &LogBufferClear }; diff --git a/src/main.cpp b/src/main.cpp index 03a2124..c8c5702 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include "std_api.h" #include "hotkeys.h" #include "std_api.h" +#include "game_hooks.h" #include "d3d11on12ui.h" @@ -47,6 +48,7 @@ static decltype(FAKE_Wndproc)* OLD_Wndproc = nullptr; static HINSTANCE self_module_handle = nullptr; static bool should_show_ui = false; static bool betterapi_load_selftest = false; +static uint32_t setting_pause_on_ui_open = false; #define EveryNFrames(N) []()->bool{static unsigned count=0;if(++count==(N)){count=0;}return !count;}() @@ -408,6 +410,7 @@ static void Callback_Config(ConfigAction action) { auto c = GetConfigAPI(); auto s = GetSettingsMutable(); c->ConfigU32(action, "FontScaleOverride", &s->FontScaleOverride); + c->ConfigU32(action, "Pause Game when BetterConsole opened", &setting_pause_on_ui_open); //do this last until i have this working with the official api if (action == ConfigAction_Write) { @@ -472,6 +475,9 @@ static void SetupModMenu() { ImGui::StyleColorsDark(); DEBUG("ImGui one time init completed!"); + // Setup all game-specific hooks + GameHook_Init(); + // Gather all my friends! BroadcastBetterAPIMessage(&API); ASSERT(betterapi_load_selftest == true); @@ -487,7 +493,6 @@ static int OnBetterConsoleLoad(const struct better_api_t* api) { ASSERT(api == &API && "Betterconsole already loaded?? Do you have multiple versions of BetterConsole installed?"); // The console part of better console is now minimally coupled to the mod menu - DEBUG("Console setup - crashing here is AOB issue"); setup_console(api); //should the hotkeys code be an internal plugin too? @@ -509,10 +514,7 @@ extern "C" BOOL WINAPI DllMain(HINSTANCE self, DWORD fdwReason, LPVOID) { /* lock the linker/dll loader until hooks are installed, TODO: make sure this code path is fast */ static bool RunHooksOnlyOnce = true; ASSERT(RunHooksOnlyOnce == true); //i want to know if this assert ever gets triggered - -#ifdef _DEBUG //while (!IsDebuggerPresent()) Sleep(100); -#endif // _DEBUG self_module_handle = self; @@ -634,6 +636,13 @@ static void OnHotheyActivate(uintptr_t) { should_show_ui = !should_show_ui; DEBUG("ui toggled"); + if (setting_pause_on_ui_open) { + const auto Hook = GameHook_GetData()->GetGamePausedFlag(); + if (Hook) { + *Hook = should_show_ui; + } + } + if (!should_show_ui) { //when you close the UI, settings are saved SaveSettingsRegistry(); diff --git a/src/main.h b/src/main.h index 92b0032..5aa5f20 100644 --- a/src/main.h +++ b/src/main.h @@ -60,12 +60,13 @@ extern void TraceImpl(const char* const filename, const char* const func, int li #include "../imgui/imgui.h" -#define BETTERCONSOLE_VERSION "1.4.0" +#define BETTERCONSOLE_VERSION "1.4.1" +#define COMPATIBLE_GAME_VERSION "1.12.36" // -------------------------------------------------------------------- // ---- Change these offsets for each game update ---- // -------------------------------------------------------------------- -constexpr uint32_t GAME_VERSION = MAKE_VERSION(1, 12, 32); +constexpr uint32_t GAME_VERSION = MAKE_VERSION(1, 12, 36);