launcher: better DLL logging and remove ucrt hack (#919)

## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Remove workaround implemented in #264 that added an ugly hack to work
around DLL hooks that rely on spice using MSVCRT (failure to obtain
argc/argv).

Update the logic that prints info about DLL overrides so that it tries
to print out as much info as possible, instead of bailing out early if
something is missing.

## Testing
This commit is contained in:
bicarus
2026-10-03 14:14:22 -07:00
committed by GitHub
parent 7aba7da3b6
commit ed0ccdd6e3
3 changed files with 122 additions and 97 deletions
+22 -52
View File
@@ -172,8 +172,6 @@ static bool check_dll(const std::string &model) {
} }
} }
void update_msvcrt_args(int argc, char *argv[]);
void dump_button_bindings(std::vector<Button> *buttons); void dump_button_bindings(std::vector<Button> *buttons);
void dump_analog_bindings(); void dump_analog_bindings();
@@ -2528,7 +2526,23 @@ int main_implementation(int argc, char *argv[]) {
// log some DLLs found in path (purely for troubleshooting purposes to detect // log some DLLs found in path (purely for troubleshooting purposes to detect
// dxvk, ForceD3D9On12, ifs_layeredfs, etc) // dxvk, ForceD3D9On12, ifs_layeredfs, etc)
libutils::warn_if_dll_exists("d3d8.dll"); libutils::warn_if_dll_exists("d3d8.dll");
libutils::warn_if_dll_exists("d3d9.dll"); if (libutils::warn_if_dll_exists("d3d9.dll")) {
#if SPICE64
// dx9 subscreen games
if (avs::game::is_model({"LDJ", "KFC", "M39", "M32"})) {
log_warning(
"launcher",
"custom d3d9.dll detected - may cause graphics and touch-emulation issues");
deferredlogs::defer_error_messages({
"found custom d3d9.dll",
" custom d3d9.dll wrappers may cause subscreen and touch-emulation issues",
" if you encounter problems, close the game, remove the custom d3d9.dll and retry"
});
}
#endif
}
libutils::warn_if_dll_exists("d3d10core.dll"); libutils::warn_if_dll_exists("d3d10core.dll");
libutils::warn_if_dll_exists("d3d11.dll"); libutils::warn_if_dll_exists("d3d11.dll");
libutils::warn_if_dll_exists("d3d12.dll"); libutils::warn_if_dll_exists("d3d12.dll");
@@ -2745,7 +2759,11 @@ int main_implementation(int argc, char *argv[]) {
networkhook_init(); networkhook_init();
} }
update_msvcrt_args(argc, argv); #if defined(_UCRT) || (defined(_MSC_VER) && _MSC_VER >= 1900)
log_info("launcher", "C runtime: UCRT");
#else
log_info("launcher", "C runtime: MSVCRT");
#endif
// load hooks // load hooks
for (auto &hook : game_hooks) { for (auto &hook : game_hooks) {
@@ -2986,54 +3004,6 @@ int main_implementation(int argc, char *argv[]) {
return 0; return 0;
} }
// https://github.com/spice2x/spice2x.github.io/issues/264
// huge ugly hack to work around things that broke when MinGW switched from msvcrt to ucrt
// this is done to ensure that any DLL hooks that rely on msvcrt continue to work
void update_msvcrt_args(int argc, char *argv[]) {
#if defined(_UCRT)
auto msvc = LoadLibraryA("msvcrt.dll");
if (!msvc) {
log_warning("launcher", "failed to load msvcrt.dll");
return;
}
// get __argc
PINT32 argc_addr = (PINT32)GetProcAddress(msvc, "__argc");
if (!argc_addr) {
log_warning("launcher", "failed to find msvcrt!__argc");
return;
}
try {
if (*argc_addr == argc) {
log_warning("launcher", "msvcrt!__argc is already set");
return;
}
} catch (const std::exception &e) {
log_warning("launcher", "exception while reading msvcrt!_argc: {}", e.what());
}
// get __argv
PCHAR **argv_addr = (PCHAR **)GetProcAddress(msvc, "__argv");
if (!argv_addr) {
log_warning("launcher", "failed to find msvcrt!__argv");
return;
}
// update them
try {
log_info("launcher", "msvcrt!__argc value before: {}", *argc_addr);
*argc_addr = argc;
log_info("launcher", "msvcrt!__argc value after: {}", *argc_addr);
*argv_addr = argv;
} catch (const std::exception &e) {
log_warning("launcher", "exception while messing with msvcrt!_argc and _argv: {}", e.what());
}
#else
log_misc("launcher", "not UCRT, skipping msvcrt!_argc / _argv hacks");
#endif
}
void dump_button_bindings(std::vector<Button> *buttons) { void dump_button_bindings(std::vector<Button> *buttons) {
if (!buttons) { if (!buttons) {
return; return;
+99 -44
View File
@@ -369,61 +369,106 @@ void libutils::check_duplicate_dlls() {
} }
} }
void libutils::warn_if_dll_exists(const std::string &file_name) { bool libutils::warn_if_dll_exists(const std::string &file_name) {
if (fileutils::file_exists(MODULE_PATH / file_name)) {
log_info("libutils", "found user-supplied {} in modules directory", file_name);
libutils::print_dll_info(MODULE_PATH / file_name);
return;
}
const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path(); const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path();
bool found = false;
if (fileutils::file_exists(spice_bin_path / file_name)) { if (fileutils::file_exists(spice_bin_path / file_name)) {
log_info("libutils", "found user-supplied {} next to spice executable path", file_name); log_info("libutils", "found user-supplied {} next to spice executable path", file_name);
libutils::print_dll_info(spice_bin_path / file_name); libutils::print_dll_info(spice_bin_path / file_name);
return; found = true;
} }
if (MODULE_PATH != spice_bin_path && fileutils::file_exists(MODULE_PATH / file_name)) {
log_info("libutils", "found user-supplied {} in modules directory", file_name);
libutils::print_dll_info(MODULE_PATH / file_name);
found = true;
}
return found;
} }
void libutils::print_dll_info(std::filesystem::path filename) { void libutils::print_dll_info(std::filesystem::path filename) {
DWORD handle; std::string company_name;
std::string product_name;
std::string version_str;
auto print_summary = [&]() {
log_info(
"libutils",
"DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}, Path = {}",
filename.filename(),
company_name.empty() ? "?" : company_name,
product_name.empty() ? "?" : product_name,
version_str.empty() ? "?" : version_str,
filename);
};
DWORD handle = 0;
const auto size = GetFileVersionInfoSizeW(filename.wstring().c_str(), &handle); const auto size = GetFileVersionInfoSizeW(filename.wstring().c_str(), &handle);
if (size == 0) { if (size == 0) {
log_debug( const auto error = GetLastError();
const auto error_message = get_last_error_string();
log_misc(
"libutils", "libutils",
"GetFileVersionInfoSizeA failed for {}: {}", "GetFileVersionInfoSizeW failed for {}: {} (Win32 error {}); "
filename.filename(), "DLL may have no version resource",
get_last_error_string()); filename,
error_message,
error);
print_summary();
return; return;
} }
auto data = util::make_unique_plain<VOID>(size); auto data = util::make_unique_plain<VOID>(size);
if (!GetFileVersionInfoW(filename.wstring().c_str(), handle, size, data.get())) { if (!GetFileVersionInfoW(filename.wstring().c_str(), handle, size, data.get())) {
log_debug( const auto error = GetLastError();
const auto error_message = get_last_error_string();
log_misc(
"libutils", "libutils",
"GetFileVersionInfoA failed for {}: {}", "GetFileVersionInfoW failed for {}: {} (Win32 error {})",
filename.filename(), filename,
get_last_error_string()); error_message,
error);
print_summary();
return; return;
} }
VS_FIXEDFILEINFO *fixed_info = nullptr;
UINT fixed_size = 0;
if (VerQueryValueW(data.get(), L"\\", (LPVOID*)&fixed_info, &fixed_size) &&
fixed_info != nullptr && fixed_size >= sizeof(*fixed_info) &&
fixed_info->dwSignature == 0xFEEF04BD) {
version_str = fmt::format(
"{}.{}.{}.{}",
HIWORD(fixed_info->dwFileVersionMS),
LOWORD(fixed_info->dwFileVersionMS),
HIWORD(fixed_info->dwFileVersionLS),
LOWORD(fixed_info->dwFileVersionLS));
log_info("libutils", "Fixed file version for {}: {}", filename.filename(), version_str);
} else {
log_misc("libutils", "Missing or invalid fixed version info for {}", filename);
}
struct LANGANDCODEPAGE { struct LANGANDCODEPAGE {
WORD wLanguage; WORD wLanguage;
WORD wCodePage; WORD wCodePage;
} *lpTranslate = nullptr; } *lpTranslate = nullptr;
UINT cbTranslate = 0; UINT cbTranslate = 0;
if (!VerQueryValueA(data.get(), "\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate)) { if (!VerQueryValueW(
log_debug( data.get(), L"\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate)) {
log_misc(
"libutils", "libutils",
"VerQueryValueA failed for {}: {}", "VerQueryValueW(\\VarFileInfo\\Translation) failed for {}",
filename.filename(), filename);
get_last_error_string()); print_summary();
return; return;
} }
if (cbTranslate == 0 || lpTranslate == nullptr) { if (cbTranslate < sizeof(*lpTranslate) ||
log_debug( cbTranslate % sizeof(*lpTranslate) != 0 || lpTranslate == nullptr) {
log_misc(
"libutils", "libutils",
"VerQueryValueA returned invalid results for {}", "Invalid version translation table for {} ({} bytes)",
filename.filename()); filename,
cbTranslate);
print_summary();
return; return;
} }
@@ -442,36 +487,46 @@ void libutils::print_dll_info(std::filesystem::path filename) {
} }
// StringFileInfo helper // StringFileInfo helper
auto query_string = [&](const char* key) -> std::string { auto query_string = [&](const char* key, WORD language, WORD page) -> std::string {
std::string subBlock = fmt::format( std::string subBlock = fmt::format(
"\\StringFileInfo\\{:04x}{:04x}\\{}", "\\StringFileInfo\\{:04x}{:04x}\\{}",
lang, codepage, key language, page, key
); );
char* value = nullptr; wchar_t *value = nullptr;
UINT size_out = 0; UINT size_out = 0;
if (VerQueryValueA(data.get(), subBlock.c_str(), (LPVOID*)&value, &size_out) && value) { const auto wide_sub_block = s2ws(subBlock);
return value; if (VerQueryValueW(data.get(), wide_sub_block.c_str(), (LPVOID*)&value, &size_out) &&
value != nullptr && size_out > 0 && value[size_out - 1] == L'\0') {
return fmt::detail::to_utf8<wchar_t>(
std::wstring_view(value, size_out - 1),
fmt::detail::to_utf8_error_policy::replace).str();
} }
log_debug( log_misc(
"libutils", "libutils",
"VerQueryValueA({}) failed for {}: {}", "Missing or invalid version string {} for {}",
subBlock, subBlock,
filename.filename(), filename);
get_last_error_string());
return ""; return "";
}; };
const auto company_name = query_string("CompanyName"); auto query_translations = [&](const char *key) {
const auto product_name = query_string("ProductName"); auto value = query_string(key, lang, codepage);
const auto version_str = query_string("FileVersion"); for (UINT i = 0; value.empty() && i < cbTranslate / sizeof(*lpTranslate); i++) {
const auto &translation = lpTranslate[i];
if (translation.wLanguage != lang || translation.wCodePage != codepage) {
value = query_string(key, translation.wLanguage, translation.wCodePage);
}
}
return value;
};
log_info( company_name = query_translations("CompanyName");
"libutils", product_name = query_translations("ProductName");
"DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}", const auto string_version = query_translations("FileVersion");
filename.filename(), if (!string_version.empty()) {
company_name.empty() ? "?" : company_name, version_str = string_version;
product_name.empty() ? "?" : product_name, }
version_str.empty() ? "?" : version_str); print_summary();
} }
+1 -1
View File
@@ -25,7 +25,7 @@ namespace libutils {
} }
void check_duplicate_dlls(); void check_duplicate_dlls();
void warn_if_dll_exists(const std::string &file_name); bool warn_if_dll_exists(const std::string &file_name);
// get module handle helpers // get module handle helpers
HMODULE get_module(const char *module_name); HMODULE get_module(const char *module_name);