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);