mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
Backported the buffer overflow fix for debug log from 4.x
Simplified ASM code in Premade.cpp a bit.
This commit is contained in:
+7
-2
@@ -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=
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ConsoleWindow.h"
|
||||
|
||||
#include "FalloutEngine\Fallout2.h"
|
||||
#include "IniReader.h"
|
||||
#include "Logging.h"
|
||||
#include "SafeWrite.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
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<std::string> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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);
|
||||
};
|
||||
|
||||
}
|
||||
+4
-146
@@ -16,162 +16,24 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "Logging.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "FalloutEngine\Fallout2.h"
|
||||
#include "ConsoleWindow.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#ifndef NO_SFALL_DEBUG
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
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<std::string> 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 <class T>
|
||||
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
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <vector>
|
||||
|
||||
#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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <typename T>
|
||||
void __stdcall SafeWrite(DWORD addr, T data) {
|
||||
DWORD oldProtect;
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ConsoleWindow.h" />
|
||||
<ClInclude Include="CRC.h" />
|
||||
<ClInclude Include="FalloutEngine\AsmMacros.h" />
|
||||
<ClInclude Include="FalloutEngine\EngineUtils.h" />
|
||||
@@ -351,6 +352,7 @@
|
||||
<ClInclude Include="WinProc.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ConsoleWindow.cpp" />
|
||||
<ClCompile Include="CRC.cpp" />
|
||||
<ClCompile Include="ddraw.cpp" />
|
||||
<ClCompile Include="FalloutEngine\EngineUtils.cpp" />
|
||||
|
||||
@@ -359,6 +359,7 @@
|
||||
<ClInclude Include="Modules\SubModules\WindowRender.h">
|
||||
<Filter>Modules\SubModules</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ConsoleWindow.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
@@ -654,6 +655,7 @@
|
||||
<ClCompile Include="Modules\SubModules\WindowRender.cpp">
|
||||
<Filter>Modules\SubModules</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ConsoleWindow.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="exports.def" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user