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;