From 1a6a9e5ec1c63c0ca9a4e4c60ce02e2c80fdd097 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 15 Jun 2023 13:27:56 +0800 Subject: [PATCH] Backported ConsoleWindow option from 4.x --- artifacts/ddraw.ini | 13 +- sfall/FalloutEngine/FunctionOffsets_def.h | 1 + sfall/IniReader.cpp | 4 + sfall/IniReader.h | 4 +- sfall/Logging.cpp | 192 ++++++++++++++++-- sfall/Modules/DebugEditor.cpp | 3 - sfall/Modules/Scripting/Handlers/Metarule.cpp | 4 + sfall/Modules/Scripting/Handlers/Metarule.h | 1 - 8 files changed, 200 insertions(+), 22 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 6ed417a5..9ee0ab44 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -525,7 +525,7 @@ ScienceOnCritters=0 ;Default is 166 (lower - faster; valid range: 0..1000) SpeedInventoryPCRotation=166 -;Modify the number of the extra interface boxes available to modders. (Default is 5, and the maximum is 95) +;Modify the number of the extra interface boxes available to modders (Default is 5, and the maximum is 95) BoxBarCount=5 ;Uncomment to set the text colour of the extra interface boxes @@ -538,7 +538,7 @@ BonusHtHDamageFix=1 ;Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory DisplayBonusDamage=0 -;Modify the maximum number of animations allowed to run on a map. (Default is 32, and the maximum is 127) +;Modify the maximum number of animations allowed to run on a map (Default is 32, and the maximum is 127) AnimationsAtOnceLimit=64 ;Set to 1 to remove the limits that stop the game from rolling critical successes/failures in the first few days of game time @@ -837,7 +837,7 @@ AllowUnsafeScripting=0 SkipCompatModeCheck=0 ;Fallout 2 Debug Patch -;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both the screen and debug.log +;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both ;Does not require sfall debugging mode ;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg ;------- @@ -885,3 +885,10 @@ Script=0 Criticals=1 ;Prints messages relating to engine fixes 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 + +;Console window position and size data. Do not modify +ConsoleWindowData= diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index d97e0e00..2fff7040 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -718,6 +718,7 @@ FUNC(trait_set_, 0x4B3B48) FUNC(trans_buf_to_buf_, 0x4D3704) FUNC(trans_cscale_, 0x4D3560) FUNC(use_inventory_on_, 0x4717E4) +FUNC(vsprintf_, 0x4F143B) FUNC(win_add_, 0x4D6238) FUNC(win_clip_, 0x4D75B0) FUNC(win_delete_, 0x4D6468) diff --git a/sfall/IniReader.cpp b/sfall/IniReader.cpp index c8c53f72..8e7cb4e1 100644 --- a/sfall/IniReader.cpp +++ b/sfall/IniReader.cpp @@ -116,6 +116,10 @@ int IniReader::SetConfigInt(const char* section, const char* setting, int value) return setInt(section, setting, value, ini); } +int IniReader::SetConfigString(const char* section, const char* setting, const char* value) { + return WritePrivateProfileStringA(section, setting, value, ini); +} + int IniReader::SetDefaultConfigInt(const char* section, const char* setting, int value) { return setInt(section, setting, value, ddrawIni); } diff --git a/sfall/IniReader.h b/sfall/IniReader.h index 29ce1217..901cffb3 100644 --- a/sfall/IniReader.h +++ b/sfall/IniReader.h @@ -34,7 +34,7 @@ public: // Gets the integer value from the default config (i.e. ddraw.ini) static int GetIntDefaultConfig(const char* section, const char* setting, int defaultValue); - static std::string GetStringDefaultConfig(const char* section, const char* setting, const char* defaultValue, size_t bufSize); + static std::string GetStringDefaultConfig(const char* section, const char* setting, const char* defaultValue, size_t bufSize = 128); // Gets a list of values separated by the delimiter from the default config (i.e. ddraw.ini) static std::vector GetListDefaultConfig(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter); @@ -65,6 +65,8 @@ public: static int SetConfigInt(const char* section, const char* setting, int value); + static int SetConfigString(const char* section, const char* setting, const char* value); + static int SetDefaultConfigInt(const char* section, const char* setting, int value); static int SetDefaultConfigString(const char* section, const char* setting, const char* value); diff --git a/sfall/Logging.cpp b/sfall/Logging.cpp index a1e2a24f..b69aa2af 100644 --- a/sfall/Logging.cpp +++ b/sfall/Logging.cpp @@ -18,50 +18,208 @@ #include "main.h" #include "Logging.h" +#include "FalloutEngine\Fallout2.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; -template -static void OutLog(T a) { - Log << a; - Log.flush(); +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; + } + 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 OutLogN(T a) { - Log << a << "\n"; +static void OutLog(T a, int type, bool newLine = false) { + std::ostringstream ss; + if (LastNewLine || type != LastType) { + ss << "[" << DebugTypeToStr(type) << "] "; + } + ss << a; + if (newLine) ss << "\n"; + std::string str = ss.str(); + + ConsoleWindow::instance().sfallLog(str, type); + + Log << str; Log.flush(); + + LastType = type; + LastNewLine = str.back() == '\n'; +} + +const char* DebugTypeToStr(int type) { + switch (type) { + case DL_INIT: return "Init"; + case DL_HOOK: return "Hook"; + case DL_SCRIPT: return "Script"; + case DL_CRITICALS: return "Crits"; + case DL_FIX: return "Fix"; + default: return "Main"; + } } void dlog(const char* a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLog(a); + OutLog(a, type); } } void dlog(const std::string& a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLog(a); + OutLog(a, type); } } void dlogr(const char* a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLogN(a); + OutLog(a, type, true); } } void dlogr(const std::string& a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLogN(a); + OutLog(a, type, true); } } @@ -70,8 +228,10 @@ void dlog_f(const char* fmt, int type, ...) { va_list args; va_start(args, type); char buf[1024]; - vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); - OutLog(buf); + int written = vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); + if (written > 0) { + OutLog(buf, type); + } va_end(args); } } @@ -83,8 +243,10 @@ void devlog_f(const char* fmt, int type, ...) { va_list args; va_start(args, type); char buf[1024]; - vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); - OutLog(buf); + int written = vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); + if (written > 0) { + OutLog(buf, type); + } va_end(args); } } @@ -110,6 +272,8 @@ void LoggingInit() { if (IniReader::GetIntDefaultConfig("Debugging", "Fixes", 0)) { DebugTypes |= DL_FIX; } + + ConsoleWindow::instance().init(); } } diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index 110b55a4..73143b33 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -445,9 +445,6 @@ static void DebugModePatch() { MakeCall(0x4C703F, debug_log_hack); BlockCall(0x4C7044); // just nop code } - // replace calling debug_printf_ with _debug_func - __int64 data = 0x51DF0415FFF08990; // mov eax, esi; call ds:_debug_func - SafeWriteBytes(0x455419, (BYTE*)&data, 8); // op_display_msg_ // set the position of the debug window SafeWrite8(0x4DC34D, 15); diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 729a713e..135cc489 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -35,6 +35,10 @@ #include "Metarule.h" +#ifndef NDEBUG +#include +#endif + namespace sfall { namespace script diff --git a/sfall/Modules/Scripting/Handlers/Metarule.h b/sfall/Modules/Scripting/Handlers/Metarule.h index b115dd5b..e394e33e 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.h +++ b/sfall/Modules/Scripting/Handlers/Metarule.h @@ -19,7 +19,6 @@ #pragma once #include -#include #include #include "..\..\..\main.h"