Print missing DLLs on library load failure (#387)

## Description of change
When the game library fails to load, recursively iterate its import
table to narrow down the missing library.

If the dependency is a commonly missing one, give the user a hint on
where to find it.

## Testing
System with an AMD GPU and LDJ-010 IIDX:
```
[2025/10/01 00:32:41] M:dependencies: bm2dx.dll
[2025/10/01 00:32:41] M:dependencies:   |-- nvEncodeAPI64.dll
[2025/10/01 00:32:41] W:dependencies:   | |-- [!] NVIDIA Graphics Driver
[2025/10/01 00:32:41] W:dependencies:   | |--     For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.
[2025/10/01 00:32:41] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

Same system, but with SDVX:
```
[2025/10/01 00:33:53] M:dependencies: soundvoltex.dll
[2025/10/01 00:33:53] M:dependencies:   |-- nvcuda.dll
[2025/10/01 00:33:53] W:dependencies:   | |-- [!] NVIDIA Graphics Driver
[2025/10/01 00:33:53] W:dependencies:   | |--     For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.
[2025/10/01 00:33:53] M:dependencies:   |-- nvcuvid.dll
[2025/10/01 00:33:53] W:dependencies:   | |-- [!] NVIDIA Graphics Driver
[2025/10/01 00:33:53] W:dependencies:   | |--     For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.
[2025/10/01 00:33:53] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

Contrived example of a missing dependency of a dependency:
```
[2025/10/01 00:30:59] M:dependencies: bm2dx.dll
[2025/10/01 00:30:59] M:dependencies:   |-- libaio.dll
[2025/10/01 00:30:59] W:dependencies:   | |-- [!] The library could not be loaded. (126)
[2025/10/01 00:30:59] M:dependencies:   |-- libaio-iob.dll
[2025/10/01 00:30:59] M:dependencies:   | |-- libaio.dll
[2025/10/01 00:30:59] M:dependencies:   |-- libaio-iob2_video.dll
[2025/10/01 00:30:59] M:dependencies:   | |-- libaio.dll
[2025/10/01 00:30:59] M:dependencies:   | |-- libaio-iob.dll
[2025/10/01 00:30:59] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

Another example of a missing file, but with 32-bit IIDX 24:
```
[2025/10/01 00:42:13] M:dependencies: bm2dx.dll
[2025/10/01 00:42:13] M:dependencies:   |-- libafp-win32.dll
[2025/10/01 00:42:13] W:dependencies:   | |-- [!] The library could not be loaded. (126)
[2025/10/01 00:42:13] M:dependencies:   |-- libafputils-win32.dll
[2025/10/01 00:42:13] M:dependencies:   | |-- libafp-win32.dll
[2025/10/01 00:42:13] F:libutils: DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:
```

---------

Co-authored-by: aixxe <me@aixxe.net>
This commit is contained in:
llm96
2025-10-01 06:12:08 +01:00
committed by GitHub
parent 0fc3aa5818
commit b487cf2bcf
5 changed files with 181 additions and 0 deletions
+1
View File
@@ -610,6 +610,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/lz77.cpp util/lz77.cpp
util/tapeled.cpp util/tapeled.cpp
util/execexe.cpp util/execexe.cpp
util/dependencies.cpp
) )
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
+151
View File
@@ -0,0 +1,151 @@
#include <set>
#include "logging.h"
#include "libutils.h"
#include "scope_guard.h"
#include "dependencies.h"
using loader_hint = std::tuple<std::string, std::string, std::string>;
namespace {
// list of commonly missing dependencies and tips on where to get them
std::vector<loader_hint> hints = {
{
"msvcr100.dll",
"Visual Studio 2010 (VC++ 10.0) SP1 Redistributable",
"Download and install from https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist",
},
{
"d3dx9_43.dll",
"DirectX End-User Runtimes",
"Download and install from https://www.microsoft.com/en-us/download/details.aspx?id=35"
},
{
"nvEncodeAPI64.dll",
"NVIDIA Graphics Driver",
"For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.",
},
{
"nvcuda.dll",
"NVIDIA Graphics Driver",
"For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.",
},
{
"nvcuvid.dll",
"NVIDIA Graphics Driver",
"For non-NVIDIA GPUs, copy the stub file from the spice2x release .zip.",
},
{
"cpusbxpkm.dll",
"LovePlus Printer DLL",
"Copy the stub file from the spice2x release .zip.",
},
};
std::set<std::wstring> failed = {};
auto read_imports(const std::filesystem::path& path) -> std::vector<std::filesystem::path> {
auto result = std::vector<std::filesystem::path> {};
auto const file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (file == INVALID_HANDLE_VALUE) {
return result;
}
auto const file_ = scope_guard { [file] { CloseHandle(file); } };
auto const mapping = CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (!mapping) {
return result;
}
auto const mapping_ = scope_guard { [mapping] { CloseHandle(mapping); } };
auto const view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
if (!view) {
return result;
}
auto const view_ = scope_guard { [view] { UnmapViewOfFile(view); } };
auto const dos = static_cast<PIMAGE_DOS_HEADER>(view);
if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
return result;
}
auto const base = static_cast<std::uint8_t*>(view);
auto const nt = reinterpret_cast<PIMAGE_NT_HEADERS>(base + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) {
return result;
}
auto const imports = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (imports.VirtualAddress == 0) {
return result;
}
auto desc = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(base + libutils::rva2offset(nt, imports.VirtualAddress));
while (desc->Name != 0) {
auto const filename = reinterpret_cast<char*>(base + libutils::rva2offset(nt, desc->Name));
auto entry_path = path.parent_path().append(filename);
// try to use library in the same directory if one exists
// otherwise, assume system library and use filename only
if (!std::filesystem::exists(entry_path)) {
entry_path = filename;
}
result.emplace_back(entry_path);
desc++;
}
return result;
}
}
namespace dependencies {
auto walk(const std::filesystem::path& path, const std::string& prefix) -> bool {
// try to load the library -- skip walking if it loads successfully
if (auto const module = LoadLibraryW(path.c_str())) {
FreeLibrary(module);
return true;
}
auto const error = GetLastError();
auto const filename = path.filename().string();
log_misc("dependencies", "{}{}", prefix + (prefix.empty() ? "": "|-- "), filename);
if (failed.contains(path)) {
return false;
}
failed.insert(path);
auto const dependencies = read_imports(path);
auto const next_prefix = prefix + (prefix.empty() ? " ": "| ");
if (!dependencies.empty()) {
for (auto const& item : dependencies) {
walk(item, next_prefix);
}
return false;
}
for (auto const& [dll, name, hint] : hints) {
if (_stricmp(dll.c_str(), filename.c_str()) != 0) {
continue;
}
log_warning("dependencies", "{}|-- [!] {}", next_prefix, name.c_str());
log_warning("dependencies", "{}|-- {}", next_prefix, hint.c_str());
return false;
}
log_warning("dependencies", "{}|-- [!] The library could not be loaded. ({})", next_prefix, error);
return false;
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <filesystem>
namespace dependencies {
auto walk(const std::filesystem::path& path, const std::string& prefix = "") -> bool;
}
+2
View File
@@ -8,6 +8,7 @@
#include "utils.h" #include "utils.h"
#include "peb.h" #include "peb.h"
#include "util/fileutils.h" #include "util/fileutils.h"
#include "util/dependencies.h"
std::filesystem::path libutils::module_file_name(HMODULE module) { std::filesystem::path libutils::module_file_name(HMODULE module) {
std::wstring buf; std::wstring buf;
@@ -65,6 +66,7 @@ HMODULE libutils::load_library(const std::filesystem::path &path, bool fatal) {
if (!module) { if (!module) {
log_warning("libutils", "'{}' couldn't be loaded: {}", path.string(), get_last_error_string()); log_warning("libutils", "'{}' couldn't be loaded: {}", path.string(), get_last_error_string());
dependencies::walk(path);
load_library_fail(path.filename().string(), fatal); load_library_fail(path.filename().string(), fatal);
} }
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <functional>
class scope_guard {
private:
std::function<void()> f;
public:
explicit scope_guard(std::function<void()>&& f) : f(std::move(f)) {}
~scope_guard() {
f();
}
scope_guard(const scope_guard&) = delete;
scope_guard& operator=(const scope_guard&) = delete;
scope_guard(scope_guard&&) = delete;
scope_guard& operator=(scope_guard&&) = delete;
};