From f0633137594147912121aa32e516d981174a22f6 Mon Sep 17 00:00:00 2001 From: linuxversion <1660477+SomeCrazyGuy@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:54:17 -0400 Subject: [PATCH] Update game hooks api, fix pushed imgui ids, font size fixes, parser improvements --- VersionInfo.rc | Bin 4730 -> 4730 bytes betterapi.h | 82 ++++++++++++++------- src/console.cpp | 88 +++++++--------------- src/csv_parser.cpp | 54 +++++++------- src/game_hooks.cpp | 177 +++++++++++++++++++++++++++++++-------------- src/game_hooks.h | 20 ++--- src/gui.cpp | 14 ++-- src/hotkeys.cpp | 3 +- src/log_buffer.cpp | 3 + src/main.cpp | 9 +-- src/main.h | 2 +- src/parser.cpp | 99 ++++++++++++++++++------- src/settings.cpp | 2 +- src/simpledraw.cpp | 17 ++--- 14 files changed, 335 insertions(+), 235 deletions(-) diff --git a/VersionInfo.rc b/VersionInfo.rc index 65d4be065437b0e76bba2603f464d325fad64eeb..dbf08813da487e4639810e55ee6e8638b42f345c 100644 GIT binary patch delta 42 wcmeyR@=ImIBo0R7$&)z@8I3m|<;Z0Qa(wxuCmZmJZBF5N!3Y%f< SearchOutputLines{}; static std::vector SearchHistoryLines{}; @@ -42,65 +44,21 @@ struct Command { static void draw_console_window(void*) { - if (!(GameHook->ConsoleRun && GameHook->ConsoleOutputHooked)) { - if (!GameHook->ConsoleOutputHooked) { - SimpleDraw->Text("Cannot hook console print function."); - } - - if (!GameHook->ConsoleRun) { - SimpleDraw->Text("Cannot hook console run function."); - } - - SimpleDraw->Text("Incompatible mod loaded or incompatible game version"); - SimpleDraw->Text("BetterConsole '%s' is compatible with game version '%s'", BETTERCONSOLE_VERSION, COMPATIBLE_GAME_VERSION); - return; - } - - - 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 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; - } - - - if(GameHook->ConsoleReadyFlag && !*GameHook->ConsoleReadyFlag) { + if(!GameHook->IsConsoleReady()) { 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; - } + const char* const GamePauseText = GameHook->IsGamePaused()? "Resume Game" : "Pause Game"; + if (SimpleDraw->Button(GamePauseText)) { + GameHook->SetGamePaused(!GameHook->IsGamePaused()); } ImGui::SameLine(); ImGui::SetNextItemWidth(-(ImGui::GetFontSize() * 12.0f)); static uint32_t line_count = 0; - const auto cur_lines = LogBuffer->GetLineCount(GameHook->ConsoleOutput); + const auto cur_lines = LogBuffer->GetLineCount(ConsoleOutput); if (line_count != cur_lines) { line_count = cur_lines; UpdateScroll = true; @@ -113,47 +71,47 @@ static void draw_console_window(void*) { if (CommandMode == InputMode::Command) { if (ImGui::InputText("Command Mode ", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_cmdline)) { - HistoryIndex = LogBuffer->GetLineCount(GameHook->ConsoleInput); + HistoryIndex = LogBuffer->GetLineCount(ConsoleInput); GameHook->ConsoleRun(IOBuffer); IOBuffer[0] = 0; UpdateFocus = true; } SimpleDraw->SameLine(); if (SimpleDraw->Button("Clear")) { - LogBuffer->Clear(GameHook->ConsoleOutput); + LogBuffer->Clear(ConsoleOutput); } - SimpleDraw->ShowLogBuffer(GameHook->ConsoleOutput, UpdateScroll); + SimpleDraw->ShowLogBuffer(ConsoleOutput, UpdateScroll); } else if (CommandMode == InputMode::SearchOutput) { if (ImGui::InputText("Search Output ", IOBuffer, sizeof(IOBuffer), ImGuiInputTextFlags_CallbackCompletion, CALLBACK_inputtext_switch_mode)) { SearchOutputLines.clear(); - for (uint32_t i = 0; i < LogBuffer->GetLineCount(GameHook->ConsoleOutput); ++i) { - if (strcasestr(LogBuffer->GetLine(GameHook->ConsoleOutput, i), IOBuffer)) { + for (uint32_t i = 0; i < LogBuffer->GetLineCount(ConsoleOutput); ++i) { + if (strcasestr(LogBuffer->GetLine(ConsoleOutput, i), IOBuffer)) { SearchOutputLines.push_back(i); } } } SimpleDraw->SameLine(); if (SimpleDraw->Button("Clear")) { - LogBuffer->Clear(GameHook->ConsoleOutput); + LogBuffer->Clear(ConsoleOutput); } - SimpleDraw->ShowFilteredLogBuffer(GameHook->ConsoleOutput, SearchOutputLines.data(), (uint32_t)SearchOutputLines.size(), UpdateScroll); + SimpleDraw->ShowFilteredLogBuffer(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(GameHook->ConsoleInput); ++i) { - if (strcasestr(LogBuffer->GetLine(GameHook->ConsoleInput, i), IOBuffer)) { + for (uint32_t i = 0; i < LogBuffer->GetLineCount(ConsoleInput); ++i) { + if (strcasestr(LogBuffer->GetLine(ConsoleInput, i), IOBuffer)) { SearchHistoryLines.push_back(i); } } } SimpleDraw->SameLine(); if (SimpleDraw->Button("Clear")) { - LogBuffer->Clear(GameHook->ConsoleInput); + LogBuffer->Clear(ConsoleInput); } - SimpleDraw->ShowFilteredLogBuffer(GameHook->ConsoleInput, SearchHistoryLines.data(), (uint32_t)SearchHistoryLines.size(), UpdateScroll); + SimpleDraw->ShowFilteredLogBuffer(ConsoleInput, SearchHistoryLines.data(), (uint32_t)SearchHistoryLines.size(), UpdateScroll); } else { ASSERT(false && "Invalid command mode"); @@ -168,7 +126,7 @@ static void draw_console_window(void*) { static int CALLBACK_inputtext_cmdline(ImGuiInputTextCallbackData* data) { if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) { - const auto HistoryMax = (int)LogBuffer->GetLineCount(GameHook->ConsoleInput); + const auto HistoryMax = (int)LogBuffer->GetLineCount(ConsoleInput); if (data->EventKey == ImGuiKey_UpArrow) { --HistoryIndex; @@ -187,7 +145,7 @@ static int CALLBACK_inputtext_cmdline(ImGuiInputTextCallbackData* data) { if (HistoryMax) { data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, LogBuffer->GetLine(GameHook->ConsoleInput, HistoryIndex)); + data->InsertChars(0, LogBuffer->GetLine(ConsoleInput, HistoryIndex)); } } else if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) { @@ -279,6 +237,10 @@ extern void setup_console(const BetterAPI* api) { HookAPI = API->Hook; SimpleDraw = API->SimpleDraw; Config = API->Config; + GameHook = API->GameHook; + + ConsoleInput = GameHook_GetConsoleInputHandle(); + ConsoleOutput = GameHook->GetConsoleOutputHandle(); IOBuffer[0] = 0; } \ No newline at end of file diff --git a/src/csv_parser.cpp b/src/csv_parser.cpp index 6db019e..c228eac 100644 --- a/src/csv_parser.cpp +++ b/src/csv_parser.cpp @@ -83,6 +83,30 @@ static CSVFile* CSV_Load(const char* filename) { } } + + if (*csv == '"') { + if (in_quotes) { + if (*(csv + 1) == '"') { + //DEBUG("Escaped quote"); + ++csv; + } + else { + //DEBUG("Closing quote"); + in_quotes = false; + } + } + else { + //DEBUG("Starting Quote"); + in_quotes = true; + } + } + else if (cell_start) { + const auto pos = (uint32_t)(csv - start); + //DEBUG("Cell Start at %u (%c)", pos, *csv); + cell_start = false; + cells.push_back(pos); + } + if (*csv == '\n') { if (!in_quotes) { *csv = '\0'; @@ -107,14 +131,7 @@ static CSVFile* CSV_Load(const char* filename) { else { //DEBUG("Quoted newline"); } - } - else if (cell_start) { - const auto pos = (uint32_t)(csv - start); - //DEBUG("Cell Start at %u (%c)", pos, *csv); - cell_start = false; - cells.push_back(pos); - } - else if (*csv == ',') { + } else if (*csv == ',') { if (!in_quotes) { //DEBUG("Unquoted comma"); *csv = '\0'; @@ -125,22 +142,6 @@ static CSVFile* CSV_Load(const char* filename) { //DEBUG("Quoted Comma"); } } - else if (*csv == '"') { - if (in_quotes) { - if (*(csv + 1) == '"') { - //DEBUG("Escaped quote"); - ++csv; - } - else { - //DEBUG("Closing quote"); - in_quotes = false; - } - } - else { - //DEBUG("Starting Quote"); - in_quotes = true; - } - } else { if (in_quotes) { //DEBUG("in_quotes: (%c)", *csv); @@ -164,7 +165,10 @@ static CSVFile* CSV_Load(const char* filename) { ret->line_count = lines; ret->cell_count = lines * ret->column_count; - ASSERT(cells.size() == ret->cell_count); + if (cells.size() != ret->cell_count) { + DEBUG("Cell count mismatch: cell_size(%u) != cell_count(%u)", cells.size(), ret->cell_count); + goto PARSE_ERROR; + } ret->cells = (uint32_t*)malloc(ret->cell_count * sizeof(uint32_t)); ASSERT(ret->cells != NULL); memcpy(ret->cells, &cells[0], ret->cell_count * sizeof(uint32_t)); diff --git a/src/game_hooks.cpp b/src/game_hooks.cpp index 01cba8e..feb4b84 100644 --- a/src/game_hooks.cpp +++ b/src/game_hooks.cpp @@ -5,56 +5,87 @@ #include static const auto HookAPI = GetHookAPI(); +static const auto LogBuffer = GetLogBufferAPI(); -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 LogBufferHandle ConsoleInput = 0; +static LogBufferHandle ConsoleOutput = 0; + +static void* ConsoleManager = nullptr; static unsigned char* is_game_paused_address = nullptr; +static bool is_console_ready = false; + +static void (*Game_ConsolePrint)(void* consolemgr, const char* message) = nullptr; +static void (*Game_ConsoleRun)(void* consolemgr, char* cmd) = nullptr; +static void (*Game_StartingConsoleCommand)(void* A, uint32_t* type) = nullptr; +static void* (*Game_GetFormByID)(const char* identifier) = nullptr; +static const char* (*Game_GetFormName)(void* form) = nullptr; -static const auto LogBuffer = GetLogBufferAPI(); -static struct gamehook_api_t GameHookAPI = {}; -static GameHookData HookData{}; -extern const GameHookData* GameHook_GetData(void) { - return &HookData; +extern LogBufferHandle GameHook_GetConsoleInputHandle() { + if (!ConsoleInput) { + ConsoleInput = LogBuffer->Restore("ConsoleInput", "BetterConsoleInput.txt"); + } + return ConsoleInput; +} + +extern LogBufferHandle GameHook_GetConsoleOutputHandle() { + if (!ConsoleOutput) { + ConsoleOutput = LogBuffer->Create("ConsoleOutput", "BetterConsoleOutput.txt"); + } + return ConsoleOutput; } static void HookedConsoleRun(void* consolemgr, char* cmd) { - if (HookData.ConsoleReadyFlag && !*HookData.ConsoleReadyFlag) { + if (!is_console_ready) { DEBUG("StartingConsoleCommand was not called, not ready for console command '%s'", cmd); } - - Game_ConsoleRun(consolemgr, cmd); - if (!HookData.ConsoleInput) { + if (!ConsoleInput) { DEBUG("ConsoleInput logbuffer not ready when running command: '%s'", cmd); return; } - LogBuffer->Append(HookData.ConsoleInput, cmd); + LogBuffer->Append(ConsoleInput, cmd); + + if (Game_ConsoleRun) { + Game_ConsoleRun(consolemgr, cmd); + } + else { + DEBUG("Game_ConsoleRun was null when running command: '%s'", cmd); + } } -static void HookedConsolePrintV(void* consolemgr, const char* fmt, va_list args) { - Game_ConsolePrintV(consolemgr, fmt, args); +static void HookedConsolePrint(void* consolemgr, const char* message) { + if (!ConsoleManager) { + ConsoleManager = consolemgr; + } - if (!HookData.ConsoleOutput) { - DEBUG("ConsoleOutput logbuffer not ready"); + if (!ConsoleOutput) { + DEBUG("ConsoleInput logbuffer not ready when printing '%s'", message); return; } + else { + LogBuffer->Append(ConsoleOutput, message); + } - char buffer[4096]; - vsnprintf(buffer, sizeof(buffer), fmt, args); - LogBuffer->Append(HookData.ConsoleOutput, buffer); + if (consolemgr) { + if (Game_ConsolePrint) { + Game_ConsolePrint(consolemgr, message); + } + else { + DEBUG("Game_ConsolePrint was null when printing '%s'", message); + } + } + else { + DEBUG("consolemgr was null when printing '%s'", message); + } } - static void HookedStartingConsoleCommand(void* A, uint32_t* type) { - static bool ready = false; - HookData.ConsoleReadyFlag = &ready; - if (*type == 5) ready = true; + if (*type == 5) is_console_ready = true; Game_StartingConsoleCommand(A, type); } @@ -66,6 +97,11 @@ static void ConsoleRun(const char* cmd) { } +static void ConsolePrint(const char* message) { + HookedConsolePrint(ConsoleManager, message); +} + + static bool* GetGamePausedFlag() { static bool* flag_ptr = nullptr; if (flag_ptr) return flag_ptr; @@ -88,10 +124,39 @@ static bool* GetGamePausedFlag() { return flag_ptr; } + +static bool IsGamePaused() { + if (!GetGamePausedFlag()) return false; + return *GetGamePausedFlag(); +} + + +static void SetGamePaused(bool paused) { + if (!GetGamePausedFlag()) return; + *GetGamePausedFlag() = paused; +} + + +static void* GetFormByID(const char* identifier) { + if (!Game_GetFormByID) return nullptr; + return Game_GetFormByID(identifier); +} + + +static const char* GetFormName(void* form) { + if (!Game_GetFormName) return nullptr; + return Game_GetFormName(form); +} + + +static bool IsConsoleReady() { + return is_console_ready; +} + + 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")); + //char path_tmp[260]; + DEBUG("Hooking ExecuteCommand"); auto OldConsoleRun = (FUNC_PTR) HookAPI->AOBScanEXE( @@ -116,30 +181,31 @@ extern void GameHook_Init() { 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 + "48 85 D2 " //TEST RDX, RDX + "0F 84 ?? ?? 00 00 " //JE 0X0000???? + "48 89 5C 24 ?? " //MOV QWORD PTR[RSP + 0X??], RBX + "55 " //PUSH RBP + "56 " //PUSH RSI + "57 " //PUSH RDI + "41 56 " //PUSH R14 + "41 57 " //PUSH R15 + "48 8B EC " //MOV RBP, RSP + "48 83 EC ?? " //SUB RSP, 0X?? + "4C 8B FA" //MOV R15, RDX ); if (!OldConsolePrintV) { DEBUG("Failed to find ConsolePrint"); } else { - Game_ConsolePrintV = (decltype(Game_ConsolePrintV))HookAPI->HookFunction( + Game_ConsolePrint = (decltype(Game_ConsolePrint))HookAPI->HookFunction( OldConsolePrintV, - (FUNC_PTR)HookedConsolePrintV + (FUNC_PTR)HookedConsolePrint ); - HookData.ConsoleOutputHooked = true; } @@ -152,6 +218,9 @@ extern void GameHook_Init() { "b0 01 " // MOV AL,0x1 "C3" // RET ); + if (!is_game_paused_address) { + DEBUG("Failed to find is_game_paused"); + } DEBUG("Hooking OnStartingConsoleCommand"); auto OldStartingConsoleCommand = (FUNC_PTR)HookAPI->AOBScanEXE( @@ -173,10 +242,8 @@ extern void GameHook_Init() { ); } - HookData.GetGamePausedFlag = &GetGamePausedFlag; - DEBUG("Hooking GetFormByID"); - HookData.GetFormByID = (decltype(HookData.GetFormByID))GetHookAPI()->AOBScanEXE( + Game_GetFormByID = (decltype(Game_GetFormByID))GetHookAPI()->AOBScanEXE( "88 54 24 ?? " // MOV BYTE PTR [RSP+0x??],DL "55 " // PUSH RBP "53 " // PUSH RBX @@ -188,32 +255,34 @@ extern void GameHook_Init() { "33 f6 " // XOR ESI,ESI "48 85 c9 " // TEST RCX,RCX ); - - if (!HookData.GetFormByID) { + if (!Game_GetFormByID) { DEBUG("Failed to find GetFormByID"); } DEBUG("Hooking GetFormName"); - HookData.GetFormName = (decltype(HookData.GetFormName))GetHookAPI()->AOBScanEXE( + Game_GetFormName = (decltype(Game_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) { + if (!Game_GetFormName) { DEBUG("Failed to find GetFormName"); } - - GameHookAPI = decltype(GameHookAPI) { - HookData.ConsoleRun, - HookData.GetFormByID, - HookData.GetFormName, - HookData.GetGamePausedFlag - }; } BC_EXPORT const struct gamehook_api_t* GetGameHookAPI() { + static constexpr const struct gamehook_api_t GameHookAPI = { + ConsoleRun, + ConsolePrint, + IsConsoleReady, + SetGamePaused, + IsGamePaused, + GetFormByID, + GetFormName, + GameHook_GetConsoleOutputHandle, + }; return &GameHookAPI; } diff --git a/src/game_hooks.h b/src/game_hooks.h index ff2cd9d..ca45de6 100644 --- a/src/game_hooks.h +++ b/src/game_hooks.h @@ -1,21 +1,11 @@ #pragma once #include "main.h" -typedef struct GameHookData { - void (*ConsoleRun)(const char* command); - void* (*GetFormByID)(const char* form_identifier); - const char* (*GetFormName)(void* form); - bool* (*GetGamePausedFlag)(); - bool* ConsoleReadyFlag; - LogBufferHandle ConsoleInput; - LogBufferHandle ConsoleOutput; - bool ConsoleOutputHooked; -} GameHookData; - - +//public api BC_EXPORT const struct gamehook_api_t* GetGameHookAPI(); -extern const GameHookData* GameHook_GetData(void); - // when game launches: -extern void GameHook_Init(); \ No newline at end of file +extern void GameHook_Init(); + +//internal api +extern LogBufferHandle GameHook_GetConsoleInputHandle(); \ No newline at end of file diff --git a/src/gui.cpp b/src/gui.cpp index 1737091..57c4da2 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -8,20 +8,24 @@ static const auto UI = GetSimpleDrawAPI(); extern void draw_gui() { static bool once = false; if (!once) { + once = true; const auto screen = ImGui::GetIO().DisplaySize; ImGui::SetNextWindowPos(ImVec2{ screen.x / 4, screen.y / 4 }); ImGui::SetNextWindowSize(ImVec2{ screen.x / 2 , screen.y / 2 }); - once = true; + if (GetSettings()->FontScaleOverride == 0) { + GetSettingsMutable()->FontScaleOverride = 100; + } + ImGui::GetIO().FontGlobalScale = GetSettings()->FontScaleOverride / 100.0f; } auto imgui_context = ImGui::GetCurrentContext(); ImGui::Begin("BetterConsole"); ImGui::BeginTabBar("mod tabs", ImGuiTabBarFlags_FittingPolicyScroll | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs); - if (GetSettings()->FontScaleOverride == 0) { - GetSettingsMutable()->FontScaleOverride = 100; - } - ImGui::SetWindowFontScale((float) GetSettings()->FontScaleOverride / 100.f); + + + + //ImGui::ShowDemoWindow(); //TODO: the internal modmenu tab could be part of the internal plugin // and thus called by the tab callback iteration routine like any other plugin diff --git a/src/hotkeys.cpp b/src/hotkeys.cpp index 859262b..3a6ad76 100644 --- a/src/hotkeys.cpp +++ b/src/hotkeys.cpp @@ -17,7 +17,6 @@ static const auto Config = GetConfigAPI(); -// if this is only data, why is it in the callback api? struct HotKey { unsigned owner; // handle to mod that owns hotkey unsigned set_key; //the key combo that this hotkey is set to @@ -79,7 +78,7 @@ static const char* GetHotkeyText(char* tmp_buffer, unsigned tmp_size, uint16_t h // its possible that several mods will all add several hotkeys at once, -// so we add them to the array now and do the heavy lifting later +// so we add them to the array now and set a dirty flag to rebuild the cache extern void HotkeyRequestNewHotkey(RegistrationHandle owner, const char* name, uintptr_t userdata, unsigned forced_hotkey) { HotKey hotkey{}; hotkey.owner = owner; diff --git a/src/log_buffer.cpp b/src/log_buffer.cpp index ba0c681..12d5e16 100644 --- a/src/log_buffer.cpp +++ b/src/log_buffer.cpp @@ -85,6 +85,9 @@ static void LogBufferAppend(LogBufferHandle handle, const char* line) { auto& l = Logs[handle]; auto offset = l.buffer.size(); l.buffer.append(line); + if (l.buffer.back() == '\n') { + l.buffer.pop_back(); + } l.buffer += '\0'; l.lines.push_back((uint32_t)offset); if (l.logfile) { diff --git a/src/main.cpp b/src/main.cpp index 4643571..2910851 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -339,6 +339,8 @@ static void Callback_Config(ConfigAction action) { if (action == ConfigAction_Write) { HotkeySaveSettings(); } + + ImGui::GetIO().FontGlobalScale = s->FontScaleOverride / 100.0f; } @@ -554,7 +556,7 @@ static LRESULT FAKE_Wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) if (wParam == VK_F2) { const auto Parser = GetParserAPI(); float f = 0; - Parser->ParseFloat(" \t \r \n \v +1.67e-4", &f); + Parser->ParseFloat(" \t \r \n \v +1.67e-4 \n \r \t \v", &f); DEBUG("f: %f", f); } } @@ -576,10 +578,7 @@ static void OnHotheyActivate(uintptr_t) { DEBUG("ui toggled"); if (setting_pause_on_ui_open) { - const auto Hook = GameHook_GetData()->GetGamePausedFlag(); - if (Hook) { - *Hook = should_show_ui; - } + GetGameHookAPI()->SetGamePaused(should_show_ui); } if (!should_show_ui) { diff --git a/src/main.h b/src/main.h index aa8ed59..a0bbe60 100644 --- a/src/main.h +++ b/src/main.h @@ -21,7 +21,7 @@ #include "../imgui/imgui.h" -#define BETTERCONSOLE_VERSION "1.4.2" +#define BETTERCONSOLE_VERSION "1.4.3" #define COMPATIBLE_GAME_VERSION "1.12.36" constexpr uint32_t GAME_VERSION = BC_MAKE_VERSION(1, 12, 36); diff --git a/src/parser.cpp b/src/parser.cpp index 5dc690d..bd91bd9 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -27,6 +27,17 @@ static inline double powerd(double x, int64_t y) { } } +static inline const char* skip_whitespace(const char* str) { + while ( + (*str == ' ') || + (*str == '\r') || + (*str == '\n') || + (*str == '\t') + ) ++str; + return str; +} + + enum NumberParseFlags : unsigned { NumberParseFlags_None = 0, NumberParseFlags_Float = 1 << 0, @@ -38,20 +49,9 @@ static bool ParseNumber(const char* str, void* out_val, unsigned flags) { if (!str) return false; if (!out_val) return false; - while (*str <= ' ') { - if ( - (*str == ' ') || - (*str == '\t') || - (*str == '\r') || - (*str == '\n') || - (*str == '\v') - ) { - ++str; - continue; - } - return false; - } + str = skip_whitespace(str); + if (*str < ' ') return false; const bool is_unsigned = flags & NumberParseFlags_Unsigned; const bool is_float = flags & NumberParseFlags_Float; @@ -105,19 +105,11 @@ static bool ParseNumber(const char* str, void* out_val, unsigned flags) { frac_len = 0; continue; } - else if ( - (c == ' ') || - (c == '\t') || - (c == '\r') || - (c == '\n') || - (c == '\v') - ) { - // break on whitespace - break; - } else { - // should we error here? - // maybe only if no characters were parsed? + else if (start == str) { + //no numbers or empty string goto INVALID_CHARACTER; + } else { + break; //no more characters } } @@ -165,7 +157,7 @@ static bool ParseNumber(const char* str, void* out_val, unsigned flags) { if (is_unsigned) { *(uint64_t*)out_val = num[CT_Integer]; - DEBUG("Parse '%s' as U64: %llu", start, num[CT_Integer]); + //DEBUG("Parse '%s' as U64: %llu", start, num[CT_Integer]); } else if (is_float) { double dval = (double)num[CT_Fraction]; @@ -175,11 +167,11 @@ static bool ParseNumber(const char* str, void* out_val, unsigned flags) { dval += num[CT_Integer]; dval *= powerd(10, exponent); *(double*)out_val = dval; - DEBUG("Parse '%s' as Double: %f", start, dval); + //DEBUG("Parse '%s' as Double: %f", start, dval); } else { *(int64_t*)out_val = num[CT_Integer] * (is_negative) ? -1 : 1; - DEBUG("Parse '%s' as I64: %lld", start, num[CT_Integer]); + //DEBUG("Parse '%s' as I64: %lld", start, num[CT_Integer]); } return true; @@ -296,6 +288,55 @@ static bool ParseFloat(const char* str, float* out_val) { } +static bool ParseBool(const char* str, bool* out) { + str = skip_whitespace(str); + if (*str == '0') { + *out = false; + } + else if (*str == '1') { + *out = true; + } + else { + return false; + } + return true; +} + + +static bool ParseStringCSV(const char* str, char* out, uint32_t out_size) { + out[0] = '\0'; + + const bool in_quotes = (*str == '"'); + if (in_quotes) ++str; + + int pos = 0; + while (pos < out_size) { + out[pos] = '\0'; + + if (!*str) break; + + if (*str == '"') { + if (in_quotes) { + ++str; + if (*str != '"') { + break; + } + } + else { + //parse error: can't have quotes without being in quotes + return false; + } + } + + out[pos] = *str; + ++str; + ++pos; + } + + return true; +} + + extern const struct parse_api_t* GetParserAPI() { static const struct parse_api_t api = { ParseU64, @@ -308,6 +349,8 @@ extern const struct parse_api_t* GetParserAPI() { ParseS8, ParseDouble, ParseFloat, + ParseBool, + ParseStringCSV }; return &api; } \ No newline at end of file diff --git a/src/settings.cpp b/src/settings.cpp index 19513ef..cceb95b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -427,7 +427,7 @@ static void ConfigWriteEscapedString(const char *in_unescaped_string) { else if (*in == '!') { str = ('\\' | ('e' << 8)); } - else if (*in == '\"') { + else if (*in == '"') { str = ('\\' | ('"' << 8)); } else { diff --git a/src/simpledraw.cpp b/src/simpledraw.cpp index b04b01a..8950034 100644 --- a/src/simpledraw.cpp +++ b/src/simpledraw.cpp @@ -34,16 +34,11 @@ static bool simple_input_text(const char* name, char* buffer, uint32_t buffer_si //NOTE: this uses em size which is roughly font size + imgui padding and border size // (min_size_em * font_size) + padding + border static void simple_hbox_left(float split, float min_size_em) { - auto size = ImGui::GetContentRegionAvail(); - auto w = size.x * split; - + const auto size = ImGui::GetContentRegionAvail(); const float min_size = (ImGui::GetFontSize() * min_size_em); + auto w = size.x * split; w = (w < min_size) ? min_size : w; - - // weird code, i use the width available as the unique id - // because nested hboxes will only reduce available width and thus be unique - ImGui::PushID((int)(size.x)); ImGui::BeginChild("##hbox left", ImVec2{ w, size.y }, true); } @@ -57,7 +52,6 @@ static void simple_hbox_right() { static void simple_hbox_end() { ImGui::EndChild(); - ImGui::PopID(); } @@ -68,7 +62,6 @@ static void simple_vbox_top(float split, float min_size_em) { const auto min_size = (ImGui::GetTextLineHeightWithSpacing() * min_size_em); h = (h < min_size) ? min_size : h; - ImGui::PushID((int)(size.y)); ImGui::BeginChild("##vbox top", ImVec2{ size.x, h }, true); } @@ -81,7 +74,6 @@ static void simple_vbox_bottom() { static void simple_vbox_end() { ImGui::EndChild(); - ImGui::PopID(); } @@ -160,10 +152,13 @@ static bool simple_selection_list(uint32_t* selection, const void* userdata, uin static void simple_draw_table(const char * const * const headers, uint32_t header_count, uintptr_t rows_userdata, uint32_t row_count, CALLBACK_TABLE draw_cell) { - if (ImGui::BeginTable("SimpleDrawTable", header_count, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { + if (ImGui::BeginTable("SimpleDrawTable", header_count, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) { + ImGui::PushID("TableHeaders"); for (uint32_t i = 0; i < header_count; ++i) { ImGui::TableSetupColumn(headers[i]); } + ImGui::PopID(); + ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); ImGuiListClipper clip;