diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index e61bcc9..daef13f 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -610,6 +610,7 @@ set(SOURCE_FILES ${SOURCE_FILES} util/lz77.cpp util/tapeled.cpp util/execexe.cpp + util/dependencies.cpp ) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES}) diff --git a/src/spice2x/util/dependencies.cpp b/src/spice2x/util/dependencies.cpp new file mode 100644 index 0000000..772f324 --- /dev/null +++ b/src/spice2x/util/dependencies.cpp @@ -0,0 +1,151 @@ +#include +#include "logging.h" +#include "libutils.h" +#include "scope_guard.h" +#include "dependencies.h" + +using loader_hint = std::tuple; + +namespace { + // list of commonly missing dependencies and tips on where to get them + std::vector 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 failed = {}; + + auto read_imports(const std::filesystem::path& path) -> std::vector { + auto result = std::vector {}; + 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(view); + + if (dos->e_magic != IMAGE_DOS_SIGNATURE) { + return result; + } + + auto const base = static_cast(view); + auto const nt = reinterpret_cast(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(base + libutils::rva2offset(nt, imports.VirtualAddress)); + + while (desc->Name != 0) { + auto const filename = reinterpret_cast(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; + } +} \ No newline at end of file diff --git a/src/spice2x/util/dependencies.h b/src/spice2x/util/dependencies.h new file mode 100644 index 0000000..db8f8b0 --- /dev/null +++ b/src/spice2x/util/dependencies.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace dependencies { + auto walk(const std::filesystem::path& path, const std::string& prefix = "") -> bool; +} \ No newline at end of file diff --git a/src/spice2x/util/libutils.cpp b/src/spice2x/util/libutils.cpp index 495361c..2cfbaf4 100644 --- a/src/spice2x/util/libutils.cpp +++ b/src/spice2x/util/libutils.cpp @@ -8,6 +8,7 @@ #include "utils.h" #include "peb.h" #include "util/fileutils.h" +#include "util/dependencies.h" std::filesystem::path libutils::module_file_name(HMODULE module) { std::wstring buf; @@ -65,6 +66,7 @@ HMODULE libutils::load_library(const std::filesystem::path &path, bool fatal) { if (!module) { log_warning("libutils", "'{}' couldn't be loaded: {}", path.string(), get_last_error_string()); + dependencies::walk(path); load_library_fail(path.filename().string(), fatal); } diff --git a/src/spice2x/util/scope_guard.h b/src/spice2x/util/scope_guard.h new file mode 100644 index 0000000..e7dec97 --- /dev/null +++ b/src/spice2x/util/scope_guard.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class scope_guard { +private: + std::function f; +public: + explicit scope_guard(std::function&& 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; +}; \ No newline at end of file