From 2156f82184cda1eb3448e7da0e5e64b8875841d4 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sat, 17 Jun 2023 22:01:36 +0800 Subject: [PATCH] Backported the buffer overflow fix for debug log from 4.x Simplified ASM code in Premade.cpp a bit. --- artifacts/ddraw.ini | 9 +- sfall/ConsoleWindow.cpp | 140 +++++++++++++++++++++++++++++ sfall/ConsoleWindow.h | 55 ++++++++++++ sfall/Logging.cpp | 150 +------------------------------ sfall/Modules/DebugEditor.cpp | 43 +++++++++ sfall/Modules/Premade.cpp | 3 +- sfall/Modules/Scripting/Arrays.h | 2 +- sfall/SafeWrite.h | 4 + sfall/ddraw.vcxproj | 2 + sfall/ddraw.vcxproj.filters | 2 + sfall/main.cpp | 2 + sfall/main.h | 4 - 12 files changed, 261 insertions(+), 155 deletions(-) create mode 100644 sfall/ConsoleWindow.cpp create mode 100644 sfall/ConsoleWindow.h diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 5a6c7624..99eb6a78 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -895,8 +895,13 @@ Criticals=1 Fixes=1 ;Duplicates logs to a dedicated console window alongside the game window -;Set to 1 to display debug output (requires DebugMode to be enabled), 2 to display sfall log, or 3 for both -ConsoleWindow=0 +;This option uses bit flags to control the types of messages to be shown +;All types other than sfall log (bit 1) require DebugMode to be enabled +;1 (bit 0) - debug output from the engine +;2 (bit 1) - sfall log +;4 (bit 2) - messages from debug_msg script function +;8 (bit 3) - messages from display_msg script function +ConsoleWindow=0b0000 ;Console window position and size data. Do not modify ConsoleWindowData= diff --git a/sfall/ConsoleWindow.cpp b/sfall/ConsoleWindow.cpp new file mode 100644 index 00000000..b866f2ff --- /dev/null +++ b/sfall/ConsoleWindow.cpp @@ -0,0 +1,140 @@ +/* + * sfall + * Copyright (C) 2008-2023 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ConsoleWindow.h" + +#include "FalloutEngine\Fallout2.h" +#include "IniReader.h" +#include "Logging.h" +#include "SafeWrite.h" +#include "Utils.h" + +#include +#include + +namespace sfall +{ + +ConsoleWindow ConsoleWindow::_instance; + +bool ConsoleWindow::tryGetWindow(HWND* wnd) { + *wnd = GetConsoleWindow(); + if (!*wnd) { + dlogr("Error getting console window.", DL_MAIN); + return false; + } + return true; +} + +void ConsoleWindow::loadPosition() { + std::string windowDataStr = IniReader::GetStringDefaultConfig("Debugging", "ConsoleWindowData", ""); + std::vector windowDataSplit = split(windowDataStr, ','); + if (windowDataSplit.size() < 4) return; + + HWND wnd; + if (!tryGetWindow(&wnd)) return; + + int windowData[4]; + for (size_t i = 0; i < 4; i++) { + windowData[i] = atoi(windowDataSplit.at(i).c_str()); + } + if (!SetWindowPos(wnd, HWND_TOP, windowData[0], windowData[1], windowData[2], windowData[3], 0)) { + dlog_f("Error repositioning console window: 0x%x\n", DL_MAIN, GetLastError()); + } +} + +void ConsoleWindow::savePosition() { + HWND wnd; + if (!tryGetWindow(&wnd)) return; + + RECT wndRect; + if (!GetWindowRect(wnd, &wndRect)) { + dlog_f("Error getting console window position: 0x%x\n", DL_MAIN, GetLastError()); + } + int width = wndRect.right - wndRect.left; + int height = wndRect.bottom - wndRect.top; + std::ostringstream ss; + ss << wndRect.left << "," << wndRect.top << "," << width << "," << height; + auto wndDataStr = ss.str(); + dlog_f("Saving console window position & size: %s\n", DL_MAIN, wndDataStr.c_str()); + + IniReader::SetDefaultConfigString("Debugging", "ConsoleWindowData", wndDataStr.c_str()); +} + +static void __fastcall WriteGameLog(const char* a) { + ConsoleWindow::instance().write(a, ConsoleWindow::Source::GAME); +} + +static void __declspec(naked) debug_printf_hook() { + __asm { + call fo::funcoffs::vsprintf_; + pushadc; + lea ecx, [esp + 16]; + call WriteGameLog; + popadc; + retn; + } +} + +void ConsoleWindow::init() { + _mode = IniReader::GetIntDefaultConfig("Debugging", "ConsoleWindow", 0); + if (_mode == 0) return; + if (!AllocConsole()) { + dlog_f("Failed to allocate console: 0x%x\n", DL_MAIN, GetLastError()); + return; + } + int cp = IniReader::GetIntDefaultConfig("Debugging", "ConsoleCodePage", 0); + if (cp > 0) SetConsoleOutputCP(cp); + + freopen("CONOUT$", "w", stdout); // this allows to print to console via std::cout + + if (_mode & Source::GAME) { + std::cout << "Displaying debug_printf output.\n"; + HookCall(0x4C6F77, debug_printf_hook); + } + if (_mode & Source::SFALL) { + std::cout << "Displaying sfall debug output.\n"; + } + if (_mode & Source::DEBUG_MSG) { + std::cout << "Displaying debug_msg output.\n"; + } + if (_mode & Source::DISPLAY_MSG) { + std::cout << "Displaying display_msg output.\n"; + } + std::cout << std::endl; + + loadPosition(); +} + +ConsoleWindow::~ConsoleWindow() { + if (_mode == 0) return; + + savePosition(); +} + +void ConsoleWindow::write(const char* message, ConsoleWindow::Source source) { + if (!(_mode & source)) return; + + if (source == Source::SFALL && _lastSource != Source::SFALL) { + std::cout << "\n"; // To make logs prettier, because debug_msg places newline before the message. + } + std::cout << message; + _lastSource = source; +} + +} diff --git a/sfall/ConsoleWindow.h b/sfall/ConsoleWindow.h new file mode 100644 index 00000000..3983d828 --- /dev/null +++ b/sfall/ConsoleWindow.h @@ -0,0 +1,55 @@ +/* + * sfall + * Copyright (C) 2008-2023 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +namespace sfall +{ + +class ConsoleWindow { +public: + + enum Source : int { + GAME = 1, + SFALL = 2, + DEBUG_MSG = 4, + DISPLAY_MSG = 8, + }; + + static ConsoleWindow& instance() { return _instance; } + + ConsoleWindow() : _mode(0) {} + ~ConsoleWindow(); + + void init(); + + void loadPosition(); + void savePosition(); + + void write(const char* message, Source source); + +private: + static ConsoleWindow _instance; + + int _mode; + Source _lastSource; + + bool tryGetWindow(HWND* wnd); +}; + +} diff --git a/sfall/Logging.cpp b/sfall/Logging.cpp index d1a9e445..41ebc405 100644 --- a/sfall/Logging.cpp +++ b/sfall/Logging.cpp @@ -16,162 +16,24 @@ * along with this program. If not, see . */ -#include "main.h" #include "Logging.h" + +#include "main.h" #include "FalloutEngine\Fallout2.h" +#include "ConsoleWindow.h" #include "Utils.h" -#ifndef NO_SFALL_DEBUG - #include -#include -#include namespace sfall { -enum ConsoleSource : int { - GAME = 1, - SFALL = 2 -}; - static int DebugTypes = 0; static std::ofstream Log; static int LastType = -1; static int LastNewLine; -class ConsoleWindow { -public: - static ConsoleWindow& instance() { return _instance; } - - ConsoleWindow() : _mode(0) {} - ~ConsoleWindow(); - - void init(); - - void loadPosition(); - void savePosition(); - - void sfallLog(const std::string& a, int type); - void falloutLog(const char* a); - -private: - static ConsoleWindow _instance; - - int _mode; - ConsoleSource _lastSource; - - bool tryGetWindow(HWND* wnd); -}; - -ConsoleWindow ConsoleWindow::_instance; - -bool ConsoleWindow::tryGetWindow(HWND* wnd) { - *wnd = GetConsoleWindow(); - if (!*wnd) { - dlogr("Error getting console window.", DL_MAIN); - return false; - } - return true; -} - -void ConsoleWindow::loadPosition() { - std::string windowDataStr = IniReader::GetStringDefaultConfig("Debugging", "ConsoleWindowData", ""); - std::vector windowDataSplit = split(windowDataStr, ','); - if (windowDataSplit.size() < 4) return; - - HWND wnd; - if (!tryGetWindow(&wnd)) return; - - int windowData[4]; - for (size_t i = 0; i < 4; i++) { - windowData[i] = atoi(windowDataSplit.at(i).c_str()); - } - if (!SetWindowPos(wnd, HWND_TOP, windowData[0], windowData[1], windowData[2], windowData[3], 0)) { - dlog_f("Error repositioning console window: 0x%x\n", DL_MAIN, GetLastError()); - } -} - -void ConsoleWindow::savePosition() { - HWND wnd; - if (!tryGetWindow(&wnd)) return; - - RECT wndRect; - if (!GetWindowRect(wnd, &wndRect)) { - dlog_f("Error getting console window position: 0x%x\n", DL_MAIN, GetLastError()); - } - int width = wndRect.right - wndRect.left; - int height = wndRect.bottom - wndRect.top; - std::ostringstream ss; - ss << wndRect.left << "," << wndRect.top << "," << width << "," << height; - auto wndDataStr = ss.str(); - dlog_f("Saving console window position & size: %s\n", DL_MAIN, wndDataStr.c_str()); - - IniReader::SetDefaultConfigString("Debugging", "ConsoleWindowData", wndDataStr.c_str()); -} - -static void __fastcall PrintToConsole(const char* a) { - ConsoleWindow::instance().falloutLog(a); -} - -static void __declspec(naked) debug_printf_hook() { - __asm { - call fo::funcoffs::vsprintf_; - pushadc; - lea ecx, [esp + 16]; - call PrintToConsole; - popadc; - retn; - } -} - -void ConsoleWindow::init() { - _mode = IniReader::GetIntDefaultConfig("Debugging", "ConsoleWindow", 0); - if (_mode == 0) return; - if (!AllocConsole()) { - dlog_f("Failed to allocate console: 0x%x\n", DL_MAIN, GetLastError()); - return; - } - int cp = IniReader::GetIntDefaultConfig("Debugging", "ConsoleCodePage", 0); - if (cp > 0) SetConsoleOutputCP(cp); - - freopen("CONOUT$", "w", stdout); // this allows to print to console via std::cout - - if (_mode & ConsoleSource::GAME) { - std::cout << "Displaying debug_printf output.\n"; - HookCall(0x4C6F77, debug_printf_hook); - } - if (_mode & ConsoleSource::SFALL) { - std::cout << "Displaying sfall debug output.\n"; - } - std::cout << std::endl; - - loadPosition(); -} - -ConsoleWindow::~ConsoleWindow() { - if (_mode == 0) return; - - savePosition(); -} - -void ConsoleWindow::falloutLog(const char* a) { - std::cout << a; - _lastSource = ConsoleSource::GAME; -} - -void ConsoleWindow::sfallLog(const std::string& a, int type) { - if (!(_mode & ConsoleSource::SFALL)) return; - - if (_lastSource == ConsoleSource::GAME) { - std::cout << "\n"; // To make logs prettier, because debug_msg places newline before the message. - } - std::cout << a; - _lastSource = ConsoleSource::SFALL; -} - - template static void OutLog(T a, int type, bool newLine = false) { std::ostringstream ss; @@ -182,7 +44,7 @@ static void OutLog(T a, int type, bool newLine = false) { if (newLine) ss << "\n"; std::string str = ss.str(); - ConsoleWindow::instance().sfallLog(str, type); + ConsoleWindow::instance().write(str.c_str(), ConsoleWindow::Source::SFALL); Log << str; Log.flush(); @@ -275,10 +137,6 @@ void LoggingInit() { if (IniReader::GetIntDefaultConfig("Debugging", "Fixes", 0)) { DebugTypes |= DL_FIX; } - - ConsoleWindow::instance().init(); } } - -#endif diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index 73143b33..918ff75c 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -19,6 +19,7 @@ #include #include "..\main.h" +#include "..\ConsoleWindow.h" #include "..\FalloutEngine\Fallout2.h" #include "..\InputFuncs.h" //#include "Graphics.h" @@ -394,6 +395,45 @@ static void __declspec(naked) combat_load_hack() { } } +static void __fastcall DuplicateLogToConsole(const char* a, unsigned long displayMsg) { + ConsoleWindow& console = ConsoleWindow::instance(); + ConsoleWindow::Source source = displayMsg ? ConsoleWindow::Source::DISPLAY_MSG : ConsoleWindow::Source::DEBUG_MSG; + console.write("\n", source); + console.write(a, source); +} + +static void __declspec(naked) op_display_debug_msg_hack() { + __asm { + mov eax, 0x505224; // "\n" + call ds:[FO_VAR_debug_func]; + mov eax, esi; // actual message + call ds:[FO_VAR_debug_func]; + pushadc; + mov ecx, esi; + mov edx, [esp + 12]; + call DuplicateLogToConsole; // duplicate messages to console window + popadc; + add esp, 4; // eat displayMsg flag + pop eax; + add eax, 17; // skip to the end of functions + jmp eax; + } +} + +static void __declspec(naked) op_display_msg_hack() { + __asm { + push 1; // displayMsg = true + jmp op_display_debug_msg_hack; + } +} + +static void __declspec(naked) op_debug_msg_hack() { + __asm { + push 0; // displayMsg = false + jmp op_display_debug_msg_hack; + } +} + // Shifts the string one character to the right and inserts a newline control character at the beginning static void MoveDebugString(char* messageAddr) { int i = 0; @@ -445,6 +485,9 @@ static void DebugModePatch() { MakeCall(0x4C703F, debug_log_hack); BlockCall(0x4C7044); // just nop code } + // replace calling debug_printf_ with _debug_func, to avoid buffer overflow with messages longer than 260 bytes + MakeCall(0x45540F, op_display_msg_hack); + MakeCall(0x45CB4E, op_debug_msg_hack); // set the position of the debug window SafeWrite8(0x4DC34D, 15); diff --git a/sfall/Modules/Premade.cpp b/sfall/Modules/Premade.cpp index 7341e757..10001940 100644 --- a/sfall/Modules/Premade.cpp +++ b/sfall/Modules/Premade.cpp @@ -92,9 +92,8 @@ static void __declspec(naked) select_display_stats_hook() { jz skip; retn; skip: - mov eax, [esp]; + pop eax; add eax, 94; // offset to next section (0x4A8A60, 0x4A8AC9) - add esp, 4; jmp eax; } } diff --git a/sfall/Modules/Scripting/Arrays.h b/sfall/Modules/Scripting/Arrays.h index 5d5292bd..c5a285dd 100644 --- a/sfall/Modules/Scripting/Arrays.h +++ b/sfall/Modules/Scripting/Arrays.h @@ -29,7 +29,7 @@ namespace sfall namespace script { -#define ARRAY_MAX_STRING (255) // maximum length of string to be stored as array key or value +#define ARRAY_MAX_STRING (1024) // maximum length of string to be stored as array key or value (including null terminator) #define ARRAY_MAX_SIZE (100000) // maximum number of array elements, // so total maximum memory/disk footprint of one array is: 16 + (ARRAY_MAX_STRING + 8) * ARRAY_MAX_SIZE diff --git a/sfall/SafeWrite.h b/sfall/SafeWrite.h index 196f8c8e..305e80d5 100644 --- a/sfall/SafeWrite.h +++ b/sfall/SafeWrite.h @@ -14,6 +14,10 @@ enum CodeType : BYTE { JumpZ = 0x74, // 0x74 [jz short ...] }; +// Macros for quick replacement of assembler opcodes pushad/popad +#define pushadc __asm push eax __asm push edx __asm push ecx +#define popadc __asm pop ecx __asm pop edx __asm pop eax + template void __stdcall SafeWrite(DWORD addr, T data) { DWORD oldProtect; diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 769b9fe8..30ade62b 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -229,6 +229,7 @@ + @@ -351,6 +352,7 @@ + diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index 68d581df..0a2f60e4 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -359,6 +359,7 @@ Modules\SubModules + @@ -654,6 +655,7 @@ Modules\SubModules + diff --git a/sfall/main.cpp b/sfall/main.cpp index 7b4363dc..12e80755 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -74,6 +74,7 @@ #include "Modules\Unarmed.h" #include "Modules\Worldmap.h" +#include "ConsoleWindow.h" #include "CRC.h" #include "InputFuncs.h" #include "Logging.h" @@ -337,6 +338,7 @@ static HMODULE SfallInit() { if (!CRC(filepath)) return 0; LoggingInit(); + ConsoleWindow::instance().init(); HookCall(0x4DE7D2, WinMain_hook); diff --git a/sfall/main.h b/sfall/main.h index fe53a9bb..dc8bd06c 100644 --- a/sfall/main.h +++ b/sfall/main.h @@ -78,10 +78,6 @@ namespace sfall // Trap for Debugger #define BREAKPOINT __asm int 3 -// Macros for quick replacement of assembler opcodes pushad/popad -#define pushadc __asm push eax __asm push edx __asm push ecx -#define popadc __asm pop ecx __asm pop edx __asm pop eax - extern bool hrpIsEnabled; extern bool hrpVersionValid;