Compare commits

...

3 Commits

Author SHA1 Message Date
sp2xdev 548b441f1f update build script 2025-11-01 17:33:40 -07:00
bicarus-dev 98bc285d98 logger: show message popup for log_fatal (#412)
## Link to GitHub Issue, if one exists
#410 

## Description of change
When raising fatal errors via `log_fatal`, also show a modal popup.

Update instances of `log_fatal` where the message was too long to fit.

## Testing
Tested various failure conditions.
2025-11-01 13:41:58 -07:00
bicarus-dev 553c180bee signal: force minimize widnows before showing crash handler dialog (#411)
## Link to GitHub Issue, if one exists
#410 

## Description of change
Forcibly minimize all windows before showing the crash dialog. Without
this, the dialog shows "behind" the game when launching multi-monitor
fullscreen games (TDJ, VM)

## Testing
Tested TDJ and VM in fullscreen.
2025-11-01 02:08:14 -07:00
15 changed files with 126 additions and 62 deletions
+2 -1
View File
@@ -1503,7 +1503,8 @@ namespace avs {
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n" " * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n" "\n"
, DLL_NAME, MODULE_PATH.string()) }; , DLL_NAME, MODULE_PATH.string()) };
log_fatal("avs-ea3", "{}", info_str); log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
} }
} }
+3 -1
View File
@@ -150,7 +150,9 @@ namespace avs {
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n" " * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
"\n" "\n"
, DLL_NAME, MODULE_PATH.string()) }; , DLL_NAME, MODULE_PATH.string()) };
log_fatal("avs-ea3", "{}", info_str);
log_warning("avs-ea3", "{}", info_str);
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
} }
} }
+2 -1
View File
@@ -98,7 +98,7 @@ namespace avs {
// ddr gamemdx.dll user error // ddr gamemdx.dll user error
if (avs::game::is_model("MDX") && DLL_NAME == "gamemdx.dll") { if (avs::game::is_model("MDX") && DLL_NAME == "gamemdx.dll") {
log_fatal( log_warning(
"ddr", "ddr",
"BAD GAME DLL ERROR\n\n" "BAD GAME DLL ERROR\n\n"
"!!! !!!\n" "!!! !!!\n"
@@ -107,6 +107,7 @@ namespace avs {
"!!! remove -exec argument and try again. !!!\n" "!!! remove -exec argument and try again. !!!\n"
"!!! !!!\n" "!!! !!!\n"
); );
log_fatal("ddr", "BAD GAME DLL ERROR (don't use -exec gamemdx.dll, that's the wrong DLL)");
} }
// file not found on disk // file not found on disk
+1
View File
@@ -1,2 +1,3 @@
del /s /q .ccache del /s /q .ccache
del /s /q dist
call build_docker.bat call build_docker.bat
+3 -1
View File
@@ -125,7 +125,7 @@ namespace games::ddr {
void DDRGame::pre_attach() { void DDRGame::pre_attach() {
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDX")) { if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDX")) {
log_fatal( log_warning(
"ddr", "ddr",
"BAD MODEL NAME ERROR\n\n\n" "BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to TDX, this is WRONG and will break your game !!!\n" "!!! model name set to TDX, this is WRONG and will break your game !!!\n"
@@ -136,6 +136,8 @@ namespace games::ddr {
"!!! !!!\n" "!!! !!!\n"
"!!! model name set to TDX, this is WRONG and will break your game !!!\n\n\n" "!!! model name set to TDX, this is WRONG and will break your game !!!\n\n\n"
); );
log_fatal("ddr", "BAD MODEL NAME ERROR - TDX specified, should be MDX instead");
} }
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::DEST[0] == 'U') { if (!cfg::CONFIGURATOR_STANDALONE && avs::game::DEST[0] == 'U') {
+3 -1
View File
@@ -428,7 +428,7 @@ namespace games::iidx {
// check bad model name // check bad model name
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDJ")) { if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDJ")) {
log_fatal( log_warning(
"iidx", "iidx",
"BAD MODEL NAME ERROR\n\n\n" "BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n" "!!! model name set to TDJ, this is WRONG and will break your game !!!\n"
@@ -446,6 +446,8 @@ namespace games::iidx {
"!!! !!!\n" "!!! !!!\n"
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n\n\n" "!!! model name set to TDJ, this is WRONG and will break your game !!!\n\n\n"
); );
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
} }
} }
+5 -1
View File
@@ -169,7 +169,7 @@ namespace games::jb {
const auto current_path = std::filesystem::current_path(); const auto current_path = std::filesystem::current_path();
log_misc("jubeat", "current working directory: {}", current_path.string()); log_misc("jubeat", "current working directory: {}", current_path.string());
if (current_path.parent_path() == current_path.root_path()) { if (current_path.parent_path() == current_path.root_path()) {
log_fatal( log_warning(
"jubeat", "jubeat",
"\n\nInvalid path error; jubeat cannot run from a directory in the drive root\n" "\n\nInvalid path error; jubeat cannot run from a directory in the drive root\n"
"The game will overflow the stack and silently fail to boot\n\n" "The game will overflow the stack and silently fail to boot\n\n"
@@ -179,6 +179,10 @@ namespace games::jb {
"To fix this, create a new directory and move ALL game files there.\n\n" "To fix this, create a new directory and move ALL game files there.\n\n"
"Your current working directory: {}\n", "Your current working directory: {}\n",
current_path.string()); current_path.string());
log_fatal(
"jubeat",
"Invalid path error; jubeat cannot run from a directory in the drive root");
} }
} }
} }
+4 -1
View File
@@ -268,7 +268,7 @@ namespace games::sdvx {
AUTO_INSERT_CARD_COOLDOWN = 15.f; AUTO_INSERT_CARD_COOLDOWN = 15.f;
// check bad model name // check bad model name
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("UFC")) { if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("UFC")) {
log_fatal( log_warning(
"sdvx", "sdvx",
"BAD MODEL NAME ERROR\n\n\n" "BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to UFC, this is WRONG and will break your game !!!\n" "!!! model name set to UFC, this is WRONG and will break your game !!!\n"
@@ -278,6 +278,9 @@ namespace games::sdvx {
"!!! !!!\n" "!!! !!!\n"
"!!! model name set to UFC, this is WRONG and will break your game !!!\n\n\n" "!!! model name set to UFC, this is WRONG and will break your game !!!\n\n\n"
); );
log_fatal(
"sdvx",
"BAD MODEL NAME ERROR - model name set to UFC, must be KFC instead");
} }
} }
+22 -17
View File
@@ -299,22 +299,6 @@ int main_implementation(int argc, char *argv[]) {
cfg::CONFIGURATOR_TYPE = cfg::ConfigType::Config; cfg::CONFIGURATOR_TYPE = cfg::ConfigType::Config;
cfg_run = true; cfg_run = true;
} }
if (options[launcher::Options::EAmusementEmulation].value_bool() &&
options[launcher::Options::ServiceURL].is_active() &&
!cfg::CONFIGURATOR_STANDALONE) {
log_fatal(
"launcher",
"BAD EAMUSE SETTINGS ERROR\n\n\n"
"-------------------------------------------------------------------\n"
"WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING\n"
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
"A service URL is set **AND** E-Amusement emulation is enabled.\n"
"Either remove the service URL, or disable E-Amusement emulation.\n"
"Otherwise you may experience problems logging in.\n"
"-------------------------------------------------------------------\n\n\n"
);
}
if (options[launcher::Options::EAmusementEmulation].value_bool()) { if (options[launcher::Options::EAmusementEmulation].value_bool()) {
avs::ea3::URL_SLASH = 1; avs::ea3::URL_SLASH = 1;
easrv_port = 8080; easrv_port = 8080;
@@ -1318,6 +1302,25 @@ int main_implementation(int argc, char *argv[]) {
); );
} }
if (options[launcher::Options::EAmusementEmulation].value_bool() &&
options[launcher::Options::ServiceURL].is_active() &&
!cfg::CONFIGURATOR_STANDALONE) {
log_warning(
"launcher",
"BAD EAMUSE SETTINGS ERROR\n\n\n"
"-------------------------------------------------------------------\n"
"WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING\n"
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
"A service URL is set **AND** E-Amusement emulation is enabled.\n"
"Either remove the service URL, or disable E-Amusement emulation.\n"
"Otherwise you may experience problems logging in.\n"
"-------------------------------------------------------------------\n\n\n"
);
log_fatal(
"launcher",
"BAD EAMUSE SETTINGS ERROR: both -ea and -url are set");
}
if (options[launcher::Options::FullscreenResolution].is_active()) { if (options[launcher::Options::FullscreenResolution].is_active()) {
std::pair<uint32_t, uint32_t> result; std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) { if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
@@ -1817,7 +1820,7 @@ int main_implementation(int argc, char *argv[]) {
// usage error // usage error
if (!cfg::CONFIGURATOR_STANDALONE if (!cfg::CONFIGURATOR_STANDALONE
&& (!CHECK_DLL_IGNORE_ARCH)) { && (!CHECK_DLL_IGNORE_ARCH)) {
log_fatal( log_warning(
"launcher", "launcher",
"module auto detection failed!\n\n" "module auto detection failed!\n\n"
"This usually means one of the following:\n\n" "This usually means one of the following:\n\n"
@@ -1825,6 +1828,8 @@ int main_implementation(int argc, char *argv[]) {
" 2. XML files in prop directory are malformed or missing, or\n" " 2. XML files in prop directory are malformed or missing, or\n"
" 3. You passed in invalid options (usually, path overrides like -exec)\n\n" " 3. You passed in invalid options (usually, path overrides like -exec)\n\n"
); );
log_fatal("launcher", "module auto detection failed, see log.txt for troubleshooting");
} }
break; break;
+1 -10
View File
@@ -175,17 +175,8 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
log_warning("signal", "minidump creation function not available, skipping"); log_warning("signal", "minidump creation function not available, skipping");
} }
// reduce sleep time since we will wait for the user to acknowledge the popup
LOG_FATAL_SLEEP = 1;
// in 30 seconds, shut down (in case user can't dismiss popup)
const auto f = std::async(std::launch::async, [] {
std::this_thread::sleep_for(std::chrono::seconds(30));
log_fatal("signal", "end");
});
// this will stall all UI threads for this process // this will stall all UI threads for this process
deferredlogs::show_popup_for_crash(); show_popup_for_crash();
log_fatal("signal", "end"); log_fatal("signal", "end");
} }
-21
View File
@@ -72,25 +72,4 @@ namespace deferredlogs {
log_warning("troubleshooter", "{}", msg); log_warning("troubleshooter", "{}", msg);
}); });
} }
void show_popup_for_crash() {
static std::once_flag shown;
std::call_once(shown, []() {
const std::string title = "spice2x (" + to_string(VERSION_STRING_CFG) + ") - crash handler";
std::string text;
text += "Game has crashed.\n\n";
text += "Check log.txt and look for error messages near the end of file.\n\n";
text += "Unsure what to do next?\n";
text += " * update spice2x to the latest version\n";
text += " * check the FAQ on spice2x wiki on github\n";
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
MessageBox(
nullptr,
text.c_str(),
title.c_str(),
MB_OK | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND | MB_TOPMOST);
});
}
} }
-1
View File
@@ -11,5 +11,4 @@ namespace deferredlogs {
void defer_error_messages(std::initializer_list<std::string> messages); void defer_error_messages(std::initializer_list<std::string> messages);
void dump_to_logger(bool is_crash=false); void dump_to_logger(bool is_crash=false);
void show_popup_for_crash();
} }
+2 -1
View File
@@ -43,7 +43,8 @@ static inline void load_library_fail(const std::string &file_name, bool fatal) {
" b. http://www.dependencywalker.com/ (for old OS) \n" " b. http://www.dependencywalker.com/ (for old OS) \n"
, file_name) }; , file_name) };
if (fatal) { if (fatal) {
log_fatal("libutils", "{}", info_str); log_warning("libutils", "{}", info_str);
log_fatal("libutils", "DLL failed to load: {}, see log.txt for troubleshooting", file_name);
} else { } else {
log_warning("libutils", "{}", info_str); log_warning("libutils", "{}", info_str);
} }
+69 -2
View File
@@ -1,6 +1,10 @@
#include "logging.h" #include <atomic>
#include <chrono>
#include <future>
DWORD LOG_FATAL_SLEEP = 10000; #include "logging.h"
#include "build/defs.h"
#include "hooks/graphics/graphics.h"
std::string_view log_get_datetime() { std::string_view log_get_datetime() {
return log_get_datetime(time(nullptr)); return log_get_datetime(time(nullptr));
@@ -14,3 +18,66 @@ std::string_view log_get_datetime(std::time_t now) {
return buf; return buf;
} }
static void show_popup(const std::string text) {
static std::once_flag shown;
std::call_once(shown, [text]() {
// minimize all windows
// only needed because in multi-monitor full screen games, MessageBox fails to show above the game
for (auto &hwnd : GRAPHICS_WINDOWS) {
ShowWindow(hwnd, SW_FORCEMINIMIZE);
}
// schedule a worker to terminate the game in 30 seconds in case user can't dismiss UI
std::atomic_bool cancel_popup = false;
auto worker = std::async(std::launch::async, [&cancel_popup]{
for (int ms = 300; ms > 0; ms--) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (cancel_popup) {
return;
}
}
launcher::stop_subsystems();
launcher::kill();
std::terminate();
});
// MessageBox will block until user presses OK
const std::string title = "spice2x (" + to_string(VERSION_STRING_CFG) + ")";
MessageBox(
nullptr,
text.c_str(),
title.c_str(),
MB_OK | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND | MB_TOPMOST);
// cancel the worker since the user acknowledged the popup
cancel_popup = true;
});
}
void show_popup_for_crash() {
std::string text;
text += "Game has crashed.\n\n";
text += "Check log.txt and look for error messages near the end of file.\n\n";
text += "Unsure what to do next?\n";
text += " * update spice2x to the latest version\n";
text += " * check the FAQ on spice2x github wiki\n";
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
show_popup(text);
}
void show_popup_for_fatal_error(std::string message) {
std::string text;
text += "A fatal error was encountered. For details, check log.txt.\n\n";
text += "----------------------------------------------------------\n";
text += message;
text += "----------------------------------------------------------\n\n";
text += "Unsure what to do next?\n";
text += " * update spice2x to the latest version\n";
text += " * check the FAQ on spice2x github wiki\n";
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
show_popup(text);
}
+9 -3
View File
@@ -71,22 +71,28 @@ struct fmt::formatter<fmt_hresult> {
} }
}; };
extern DWORD LOG_FATAL_SLEEP; void show_popup_for_crash();
void show_popup_for_fatal_error(std::string message);
// misc log // misc log
#define LOG_FORMAT(level, module, fmt_str, ...) fmt::format(FMT_COMPILE("{}" fmt_str "\n"), \ #define LOG_FORMAT(level, module, fmt_str, ...) fmt::format(FMT_COMPILE("{}" fmt_str "\n"), \
fmt_log { std::time(nullptr), level, module }, ## __VA_ARGS__) fmt_log { std::time(nullptr), level, module }, ## __VA_ARGS__)
#define LOG_FORMAT_POPUP(module, fmt_str, ...) fmt::format(FMT_COMPILE("{}: " fmt_str "\n"), module, ## __VA_ARGS__)
#define log_misc(module, format_str, ...) logger::push( \ #define log_misc(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY) LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#define log_info(module, format_str, ...) logger::push( \ #define log_info(module, format_str, ...) logger::push( \
LOG_FORMAT("I", module, format_str, ## __VA_ARGS__), logger::Style::DEFAULT) LOG_FORMAT("I", module, format_str, ## __VA_ARGS__), logger::Style::DEFAULT)
#define log_warning(module, format_str, ...) logger::push( \ #define log_warning(module, format_str, ...) logger::push( \
LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::YELLOW) LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::YELLOW)
#define log_fatal(module, format_str, ...) { \ #define log_fatal(module, format_str, ...) { \
logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \ logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \
logger::push(LOG_FORMAT("F", "spice", "encountered a fatal error, you can close the window or press ctrl + c"), logger::Style::RED); \ show_popup_for_fatal_error(LOG_FORMAT_POPUP(module, format_str, ## __VA_ARGS__)); \
launcher::stop_subsystems(); \ launcher::stop_subsystems(); \
Sleep(LOG_FATAL_SLEEP); \
launcher::kill(); \ launcher::kill(); \
std::terminate(); \ std::terminate(); \
} ((void) 0 ) } ((void) 0 )