Update to spice2x-25-10-07 (pre-apply)

> broken commit
This commit is contained in:
[ ]
2025-11-11 04:57:26 +09:00
parent 359716b850
commit 652ad41845
97 changed files with 3164 additions and 1275 deletions
+75
View File
@@ -0,0 +1,75 @@
#include <mutex>
#include <atomic>
#include "build/defs.h"
#include "deferlog.h"
#include "util/logging.h"
namespace deferredlogs {
const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE = {
"audio initialization error was previously detected during boot!",
" this crash is most likely related to audio init failure",
" * check if the default audio device has changed",
" * fix your audio device settings (e.g., sample rate)",
" * double check your spice audio options and patches"
};
std::mutex deferred_errors_mutex;
std::vector<std::vector<std::string>> deferred_errors;
void defer_error_messages(std::initializer_list<std::string> messages) {
std::lock_guard<std::mutex> lock(deferred_errors_mutex);
deferred_errors.emplace_back(messages);
}
void dump_to_logger(bool is_crash) {
static std::once_flag printed;
std::call_once(printed, [is_crash]() {
// move to a local vector under lock first
// this is to avoid holding a lock while emitting to the logger, which may deadlock
// due to recursive calls (e.g., via log hooks used for failure detection which then
// again calls into defer_error_messages), and std::mutex cannot be acquired recursively
std::vector<std::vector<std::string>> errors;
{
std::lock_guard<std::mutex> lock(deferred_errors_mutex);
if (deferred_errors.empty() && !is_crash) {
return;
}
errors = std::move(deferred_errors);
}
std::string msg;
msg += "\n\n";
msg += "/-------------------------- spice2x auto-troubleshooter -----------------------\\\n";
msg += "\n";
msg += " spice2x version: " + to_string(VERSION_STRING_CFG) + "\n";
msg += "\n";
if (is_crash) {
msg += " the game has crashed\n";
msg += " share this entire log file with someone for troubleshooting (log.txt)\n";
msg += " spice will also attempt to create a minidump (minidump.dmp)\n";
msg += "\n";
}
for (auto messages : errors) {
for (auto message : messages) {
msg += " " + message + "\n";
}
msg += "\n";
}
msg += " unsure what to do next?\n";
msg += " * update to the latest version:\n";
msg += " https://github.com/spice2x/spice2x.github.io/releases/latest\n";
msg += " * check the FAQ:\n";
msg += " https://github.com/spice2x/spice2x.github.io/wiki/Known-issues\n";
msg += "\n";
msg += "\\------------------------- spice2x auto-troubleshooter ------------------------/\n";
log_warning("troubleshooter", "{}", msg);
});
}
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <vector>
#include <initializer_list>
namespace deferredlogs {
// some shared error messages
extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE;
void defer_error_messages(std::initializer_list<std::string> messages);
void dump_to_logger(bool is_crash=false);
}
+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 VisualCppRedist_AIO_x86_x64.exe from https://github.com/abbodi1406/vcredist/releases/latest",
},
{
"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;
}
+4
View File
@@ -106,8 +106,10 @@ bool detour::inline_restore(void *address, char *data) {
#endif
}
#ifdef __clang__
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCDFAInspection"
#endif
static void *pe_offset(void *ptr, size_t offset) {
if (offset == 0) {
@@ -282,7 +284,9 @@ void **detour::iat_find_proc(const char *iid_name, void *proc, HMODULE module) {
return nullptr;
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
void *detour::iat_try(const char *function, void *new_func, HMODULE module, const char *iid_name) {
+30
View File
@@ -14,11 +14,13 @@ namespace execexe {
static decltype(&CreateFileA) execexe_CreateFileA = nullptr;
static decltype(&CreateFileW) execexe_CreateFileW = nullptr;
static decltype(&CloseHandle) execexe_CloseHandle = nullptr;
static uint64_t (*execexe_PreLoadLibraries)(const char *) = nullptr;
static std::wstring plugins_dir;
static acioemu::ACIOHandle *acio = nullptr;
static std::wstring port_name;
static bool port_opened = false;
static std::function<void()> deferred_function = nullptr;
static HANDLE WINAPI execexe_CreateFileA_hook(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
@@ -62,6 +64,24 @@ namespace execexe {
return execexe_CloseHandle(hObject);
}
static uint64_t execexe_PreLoadLibraries_hook(const char *libs) {
static bool init = false;
uint64_t result = execexe_PreLoadLibraries(libs);
if (init) {
return result;
}
init = true;
log_info("execexe", "execexe_PreLoadLibraries hook hit");
if (deferred_function) {
deferred_function();
}
return result;
}
HMODULE init() {
execexe_module = libutils::load_library("execexe.dll");
execexe_LoadLibraryW = libutils::get_proc<decltype(&LoadLibraryW)>(execexe_module, MAKEINTRESOURCE(34));
@@ -70,6 +90,7 @@ namespace execexe {
execexe_CloseHandle = libutils::get_proc<decltype(&CloseHandle)>(execexe_module, MAKEINTRESOURCE(7));
execexe_CreateFileA = libutils::get_proc<decltype(&CreateFileA)>(execexe_module, MAKEINTRESOURCE(9));
execexe_CreateFileW = libutils::get_proc<decltype(&CreateFileW)>(execexe_module, MAKEINTRESOURCE(11));
execexe_PreLoadLibraries = libutils::get_proc<decltype(execexe_PreLoadLibraries)>(execexe_module, MAKEINTRESOURCE(48));
auto module_path = libutils::module_file_name(nullptr);
module_path = module_path.replace_extension("");
@@ -79,6 +100,15 @@ namespace execexe {
return execexe_module;
}
void init_deferred(std::function<void()> init_func) {
if (deferred_function) {
log_fatal("execexe", "deferred init function is already set");
}
deferred_function = std::move(init_func);
detour::trampoline("execexe.dll", MAKEINTRESOURCE(48),
execexe_PreLoadLibraries_hook, &execexe_PreLoadLibraries);
}
void init_port_hook(const std::wstring &portName, acioemu::ACIOHandle *acioHandle) {
static bool init = false;
if (init)
+2
View File
@@ -1,11 +1,13 @@
#pragma once
#include <string>
#include <functional>
#include <windows.h>
#include "acioemu/handle.h"
namespace execexe {
HMODULE init();
void init_deferred(std::function<void()> init_func);
void init_port_hook(const std::wstring &portName, acioemu::ACIOHandle *acioHandle);
HMODULE load_library(const char *module_name, bool fatal = true);
FARPROC get_proc(HMODULE module, const char *proc_name, bool fatal = true);
+25 -13
View File
@@ -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;
@@ -29,19 +30,17 @@ std::filesystem::path libutils::module_file_name(HMODULE module) {
static inline void load_library_fail(const std::string &file_name, bool fatal) {
std::string info_str { fmt::format(
"\n\nPlease check if {} exists and the permissions are fine.\n"
"\n"
"* If the problem still persists, try installing things on this list:\n"
" https://github.com/spice2x/spice2x.github.io/wiki/DLL-Dependencies \n"
"\n"
"* Still have problems after installing from above and rebooting PC?\n"
" Avoid manually specifying DLL path (-exec) and module directory (-modules); let spice2x auto-detect unless you have a good reason not to\n"
" Ensure you do NOT have multiple copies of the game DLLs (e.g., in contents and in contents\\modules)\n"
" Certain games require specific NVIDIA DLLs when running with AMD/Intel GPUs (hint: look inside stub directory for DLLs)\n"
"\n"
"* (For advanced users) if none of the above helps, find the missing dependency using:\n"
" https://github.com/lucasg/Dependencies (recommended for most) \n"
" http://www.dependencywalker.com/ (for old OS) \n"
"DLL failed to load - this is a common error. Please carefully read ALL of the following steps for a fix:\n"
" 1. Confirm if the file ({}) exists on the disk and check the file permissions.\n"
" 2. Follow this link and install DLL prerequisites on this list:\n"
" https://github.com/spice2x/spice2x.github.io/wiki/DLL-Dependencies \n"
" 3. Still have problems after installing from above and rebooting PC?\n"
" a. Avoid manually specifying DLL path (-exec) and module directory (-modules); let spice2x auto-detect unless you have a good reason not to\n"
" b. Ensure you do NOT have multiple copies of the game DLLs (e.g., in contents and in contents\\modules)\n"
" c. Certain games require specific NVIDIA DLLs when running with AMD/Intel GPUs (hint: look inside stub directory for DLLs)\n"
" 4. (For advanced users) if none of the above helps, find the missing dependency using:\n"
" a. https://github.com/lucasg/Dependencies (recommended for most) \n"
" b. http://www.dependencywalker.com/ (for old OS) \n"
, file_name) };
if (fatal) {
log_fatal("libutils", "{}", info_str);
@@ -67,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);
}
@@ -355,4 +355,16 @@ void libutils::check_duplicate_dlls() {
(MODULE_PATH / filename).string());
}
}
}
void 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);
return;
}
const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path();
if (fileutils::file_exists(spice_bin_path / file_name)) {
log_info("libutils", "found user-supplied {} next to spice executable path", file_name);
return;
}
}
+1
View File
@@ -25,6 +25,7 @@ namespace libutils {
}
void check_duplicate_dlls();
void warn_if_dll_exists(const std::string &file_name);
// get module handle helpers
HMODULE get_module(const char *module_name);
+1 -1
View File
@@ -77,7 +77,7 @@ std::string peb::entry_name(const LDR_DATA_TABLE_ENTRY* entry) {
}
const PEB* peb::peb_get() {
#ifdef SPICE64
#ifdef _WIN64
return reinterpret_cast<const PEB *>(__readgsqword(0x60));
#else
return reinterpret_cast<const PEB *>(__readfsdword(0x30));
+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;
};
+26 -7
View File
@@ -215,23 +215,42 @@ namespace sysutils {
}
std::string prefix("device");
if (is_monitor) {
prefix = " adapter";
prefix = " adapter";
}
log_misc("gpuinfo", "{} {} device name : {}", prefix.c_str(), index, adapter->DeviceName);
log_misc("gpuinfo", "{} {} device string : {}", prefix.c_str(), index, adapter->DeviceString);
log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags);
if (!is_monitor) {
DEVMODEA devmode = {};
devmode.dmSize = sizeof(devmode);
if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) {
log_misc(
"gpuinfo",
"{} {} resolution : {}px * {}px @ {}Hz",
prefix.c_str(),
index,
devmode.dmPelsWidth, devmode.dmPelsHeight,
devmode.dmDisplayFrequency);
} else {
log_misc("gpuinfo", "EnumDisplaySettingsA failed");
}
}
log_misc("gpuinfo", "{} {} name : {}", prefix.c_str(), index, adapter->DeviceName);
log_misc("gpuinfo", "{} {} string : {}", prefix.c_str(), index, adapter->DeviceString);
log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags);
}
void print_gpus() {
DWORD device_index = 0;
DISPLAY_DEVICEA device;
DISPLAY_DEVICEA device = {};
device.cb = sizeof(device);
log_misc("smbios", "dumping GPU/monitor information...");
log_misc(
"gpuinfo",
"dumping GPU/monitor information... "
"(note: these are current values **before** launching the game)");
while (EnumDisplayDevicesA(nullptr, device_index, &device, 0)) {
print_adapter(device_index, &device, false);
DWORD monitor_index = 0;
DISPLAY_DEVICEA monitor;
DISPLAY_DEVICEA monitor = {};
monitor.cb = sizeof(monitor);
while (EnumDisplayDevicesA((PCHAR)device.DeviceName, monitor_index, &monitor, 0)) {
print_adapter(monitor_index, &monitor, true);
+11
View File
@@ -1,6 +1,8 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace tapeledutils {
@@ -20,6 +22,15 @@ namespace tapeledutils {
float b;
} rgb_float3_t;
struct tape_led {
std::vector<rgb_float3_t> data;
int index_r, index_g, index_b; // Averaged RGB light output indexes
std::string lightName;
tape_led(size_t data_size, int index_r, int index_g, int index_b, std::string lightName)
: data(std::vector<rgb_float3_t>(data_size)), index_r(index_r), index_g(index_g), index_b(index_b), lightName(std::move(lightName)) {}
};
bool is_enabled();
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size);
size_t get_led_index_using_avg_algo(size_t data_size);
+31 -2
View File
@@ -227,6 +227,7 @@ static inline std::vector<HWND> find_windows_beginning_with(const std::string &t
return windows;
}
// exists for compat only; prefer to use FindProcessWindowBeginsWith instead
static inline HWND FindWindowBeginsWith(std::string title) {
// get all windows
@@ -248,6 +249,28 @@ static inline HWND FindWindowBeginsWith(std::string title) {
return nullptr;
}
static inline HWND FindProcessWindowBeginsWith(const std::string &title) {
// try foreground window first
HWND fg_win = GetForegroundWindow();
if (string_begins_with(get_window_title(fg_win), title)) {
DWORD fg_pid;
GetWindowThreadProcessId(fg_win, &fg_pid);
if (fg_pid == GetCurrentProcessId()) {
return fg_win;
}
}
// try different windows
for (const auto window : find_windows_beginning_with(title)) {
DWORD fg_pid;
GetWindowThreadProcessId(window, &fg_pid);
if (fg_pid == GetCurrentProcessId()) {
return window;
}
}
return nullptr;
}
static inline std::string get_last_error_string() {
// get error
@@ -260,7 +283,8 @@ static inline std::string get_last_error_string() {
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_MAX_WIDTH_MASK,
nullptr,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
@@ -269,7 +293,12 @@ static inline std::string get_last_error_string() {
nullptr);
// return as string
std::string message(messageBuffer, size);
std::string message;
if (size == 0) {
message = fmt::format("(Win32 error {})", error);
} else {
message = fmt::format("{}(Win32 error {})", messageBuffer, error);
}
LocalFree(messageBuffer);
return message;