Merge branch 'main' into nac-spice22x

Merge official spice2x 'main', up to b53447b
Notes: dc82c98  is where the nixac fork starts.

- Upstream's OBJECT-library + forbidden-static-DLL-import build refactor
  merged with nixAC's blob/dsdmo resources
- SPICE_XP / runtime large-address-aware detection builds have been
  integrated from upstream
- README.md grabs from upstream; .gitignore keeps nixAC's fork-specific
  entries
This commit is contained in:
2026-08-07 00:21:58 +02:00
359 changed files with 71032 additions and 27946 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+62 -5
View File
@@ -20,13 +20,16 @@ namespace launcher {
Player2Card,
Player1PinMacro,
Player2PinMacro,
AutoPinMacroTrigger0,
AutoPinMacroTrigger1,
WindowedMode,
InjectHook,
EarlyInjectHook,
ExecuteScript,
CaptureCursor,
ShowCursor,
DisplayAdapter,
PrimaryMonitor,
DXDisplayAdapter,
GraphicsForceSingleAdapter,
GraphicsForceRefresh,
FullscreenResolution,
@@ -37,6 +40,7 @@ namespace launcher {
spice2x_Dx9On12,
NoLegacy,
RichPresence,
DiscordAppID,
SmartEAmusement,
EAmusementMaintenance,
spice2x_EAmusementMaintenance,
@@ -49,9 +53,12 @@ namespace launcher {
SOFTID,
VREnable,
DisableOverlay,
OverlayKeyboardNavigation,
OverlayScaling,
NotificationPosition,
spice2x_FpsAutoShow,
spice2x_FpsOpposite,
FpsLocation,
spice2x_SubScreenAutoShow,
spice2x_IOPanelAutoShow,
spice2x_KeypadAutoShow,
@@ -90,22 +97,33 @@ namespace launcher {
spice2x_SDVXDigitalKnobSensitivity,
SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver,
SDVXAsioTwoChannel,
SDVXDisableLive2D,
spice2x_SDVXSubPos,
SDVXSubMonitorOverride,
LoadDDRModule,
DDR43Mode,
DDRSkipCodecRegisteration,
LoadPopnMusicModule,
PopnMusicForceHDMode,
PopnMusicForceSDMode,
PopnNoSub,
PopnSubMonitorOverride,
PopnNativeTouch,
PopnSubRedraw,
LoadHelloPopnMusicModule,
LoadGitaDoraModule,
GitaDoraTwoChannelAudio,
GitaDoraDisableFrameLimiter,
GitaDoraCabinetType,
GitaDoraArenaSingleWindow,
GitaDoraLefty,
GitaDoraWailHold,
GitaDoraPickAlgo,
GitaDoraSubOverlaySize,
GitaDoraArenaSingleWindow,
GitaDoraArenaWindowLayout,
GitaDoraArenaAsioDriver,
GitaDoraArenaRealtekAccess,
LoadJubeatModule,
LoadReflecBeatModule,
LoadShogikaiModule,
@@ -163,6 +181,7 @@ namespace launcher {
ForceWinTouch,
ForceTouchEmulation,
InvertTouchCoordinates,
RawInputTouchAspectRatio,
DisableTouchCardInsert,
spice2x_TouchCardInsert,
ICCAReaderPort,
@@ -185,18 +204,26 @@ namespace launcher {
spice2x_NvapiProfile,
DisableAudioHooks,
spice2x_DisableVolumeHook,
AudioShared,
spice2x_LowLatencySharedAudio,
AudioBackend,
AsioDriverId,
AsioDriverName,
AudioDummy,
DownmixAudioToStereo,
VolumeBoost,
AudioResample,
AudioExclusiveBuffer,
AsioDownmixToStereo,
DelayBy5Seconds,
spice2x_DelayByNSeconds,
LoadStubs,
AdjustOrientation,
spice2x_AutoOrientation,
ChangeResolution,
LogLevel,
EAAutomap,
EANetdump,
DiscordAppID,
BlockingLogger,
DebugCreateFile,
VerboseGraphicsLogging,
@@ -227,12 +254,25 @@ namespace launcher {
spice2x_WindowPosition,
spice2x_WindowAlwaysOnTop,
WindowForceScaling,
WindowDisableRoundedCorners,
GitaDoraWindowedMainMonitor,
GitaDoraWindowedLeftMonitor,
GitaDoraWindowedRightMonitor,
GitaDoraWindowedSmallMonitor,
GitaDoraWindowedSmallSize,
GitaDoraWindowedSmallPosition,
spice2x_IIDXWindowedSubscreenSize,
spice2x_IIDXWindowedSubscreenPosition,
IIDXWindowedSubscreenBorderless,
IIDXWindowedSubscreenAlwaysOnTop,
spice2x_JubeatLegacyTouch,
JubeatTouchAlgo,
JubeatTouchDebug,
JubeatTouchDebounce,
RBTouchDebug,
spice2x_RBTouchScale,
RBTouchSize,
RBTouchPollRate,
spice2x_AsioForceUnload,
spice2x_IIDXNoESpec,
spice2x_IIDXWindowedTDJ,
@@ -241,9 +281,9 @@ namespace launcher {
DRSRGBCameraHook,
spice2x_IIDXNativeTouch,
spice2x_IIDXNoSub,
IIDXSubMonitorOverride,
spice2x_IIDXEmulateSubscreenKeypadTouch,
spice2x_AutoCard,
spice2x_LowLatencySharedAudio,
spice2x_TapeLedAlgorithm,
spice2x_NoNVAPI,
spice2x_NoD3D9DeviceHook,
@@ -258,6 +298,7 @@ namespace launcher {
DDRP4IOBufferMode,
InputRequiresFocus,
NostalgiaPoke,
NostalgiaTouchMode,
ForceBackBufferCount,
SDVXWindowedSubscreenSize,
SDVXWindowedSubscreenPosition,
@@ -270,11 +311,25 @@ namespace launcher {
LovePlusPrinterOutputFormat,
LovePlusPrinterJPGQuality,
OptionConflictResolution,
OtocaCamHook,
DisableHighResTimer,
EnableICMPHook,
AutoElevate,
CfgForceSoftwareRender,
OBSWebSocketEnabled,
OBSWebSocketHost,
OBSWebSocketPort,
OBSWebSocketPassword,
OBSWebSocketDebug
};
enum class OptionsCategory {
Everything,
Basic,
GameOptions,
Display,
Audio,
Network,
Overlay,
Advanced,
Dev,
API
@@ -284,7 +339,9 @@ namespace launcher {
extern bool USE_CMD_OVERRIDE;
const std::vector<std::string> &get_categories(Options::OptionsCategory category);
const std::vector<std::string> &get_quick_setting_categories();
const std::vector<OptionDefinition> &get_option_definitions();
void validate_option_categories();
std::unique_ptr<std::vector<Option>> parse_options(int argc, char *argv[]);
std::vector<Option> merge_options(const std::vector<Option> &options, const std::vector<Option> &overrides);
+15 -4
View File
@@ -7,12 +7,15 @@
#include "easrv/easrv.h"
#include "rawinput/rawinput.h"
#include "hooks/audio/audio.h"
#include "hooks/graphics/graphics.h"
#include "hooks/graphics/backends/d3d11/d3d11_backend.h"
#include "util/deferlog.h"
#include "util/logging.h"
#include "launcher.h"
#include "logger.h"
#include "nvapi/nvapi.h"
#include "sdk/sdk.h"
namespace launcher {
@@ -24,6 +27,15 @@ namespace launcher {
// therefore, subsystems need to be guarded against multiple unload attempts
log_info("launcher", "stopping subsystems");
sdk::fini_sdk_modules();
// stop dx11 background workers (poll thread, LDR notification)
// before anything else, so they can't race against the teardown.
graphics_d3d11_shutdown();
// reset monitor settings
reset_monitor_on_exit();
// before shutting down logger, dump any deferred log messages
deferredlogs::dump_to_logger();
@@ -93,13 +105,12 @@ namespace launcher {
if (LAUNCHER_ARGC > 0) {
// build cmd line
std::stringstream cmd_line;
cmd_line << "START \"\" ";
std::string cmd_line = "START \"\" ";
for (int i = 0; i < LAUNCHER_ARGC; i++)
cmd_line << " \"" << LAUNCHER_ARGV[i] << "\"";
cmd_line += " \"" + std::string(LAUNCHER_ARGV[i]) + "\"";
// run command
system(cmd_line.str().c_str());
system(cmd_line.c_str());
}
}
+402 -42
View File
@@ -1,5 +1,6 @@
#include "signal.h"
#include <algorithm>
#include <future>
#include <exception>
@@ -14,6 +15,7 @@
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/memutils.h"
#include "cfg/configurator.h"
#include "logger.h"
@@ -24,6 +26,7 @@
#endif
static decltype(MiniDumpWriteDump) *MiniDumpWriteDump_local = nullptr;
static LONG EXCEPTION_HANDLER_ACTIVE = 0;
namespace launcher::signal {
@@ -46,7 +49,7 @@ static std::string control_code(DWORD dwCtrlType) {
}
}
static std::string exception_code(struct _EXCEPTION_RECORD *ExceptionRecord) {
static std::string exception_code(const struct _EXCEPTION_RECORD *ExceptionRecord) {
switch (ExceptionRecord->ExceptionCode) {
V(EXCEPTION_ACCESS_VIOLATION);
V(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
@@ -76,6 +79,388 @@ static std::string exception_code(struct _EXCEPTION_RECORD *ExceptionRecord) {
#undef V
static const char *access_operation(ULONG_PTR operation) {
switch (operation) {
case 0:
return "read";
case 1:
return "write";
case 8:
return "execute";
default:
return "unknown";
}
}
static const char *memory_state(DWORD state) {
switch (state) {
case MEM_COMMIT:
return "committed";
case MEM_FREE:
return "free";
case MEM_RESERVE:
return "reserved";
default:
return "unknown";
}
}
static const char *memory_type(DWORD type) {
switch (type) {
case MEM_IMAGE:
return "image";
case MEM_MAPPED:
return "mapped";
case MEM_PRIVATE:
return "private";
default:
return "none";
}
}
static const char *memory_protection(DWORD protection) {
switch (protection & 0xff) {
case PAGE_EXECUTE:
return "execute";
case PAGE_EXECUTE_READ:
return "execute-read";
case PAGE_EXECUTE_READWRITE:
return "execute-read-write";
case PAGE_EXECUTE_WRITECOPY:
return "execute-write-copy";
case PAGE_NOACCESS:
return "no-access";
case PAGE_READONLY:
return "read-only";
case PAGE_READWRITE:
return "read-write";
case PAGE_WRITECOPY:
return "write-copy";
default:
return "none";
}
}
static void log_exception_parameters(const struct _EXCEPTION_RECORD *record) {
const auto parameter_count = record->NumberParameters <= EXCEPTION_MAXIMUM_PARAMETERS ?
record->NumberParameters : EXCEPTION_MAXIMUM_PARAMETERS;
log_warning("signal", "exception flags: 0x{:08x}, parameters: {}",
record->ExceptionFlags, parameter_count);
for (DWORD parameter = 0; parameter < parameter_count; parameter++) {
#ifdef _WIN64
log_warning("signal", "exception parameter[{}]: {:016x}",
parameter, record->ExceptionInformation[parameter]);
#else
log_warning("signal", "exception parameter[{}]: {:08x}",
parameter, record->ExceptionInformation[parameter]);
#endif
}
}
static bool query_memory(const void *address, MEMORY_BASIC_INFORMATION *memory) {
return VirtualQuery(address, memory, sizeof(*memory)) == sizeof(*memory);
}
static void log_memory_information(const char *label, const MEMORY_BASIC_INFORMATION &memory) {
log_warning("signal",
"{}: base={}, size=0x{:x}, allocation_base={}, state={} (0x{:x}), "
"protect={} (0x{:x}), type={} (0x{:x})",
label,
fmt::ptr(memory.BaseAddress),
memory.RegionSize,
fmt::ptr(memory.AllocationBase),
memory_state(memory.State),
memory.State,
memory_protection(memory.Protect),
memory.Protect,
memory_type(memory.Type),
memory.Type);
}
static void log_memory_region(const char *label, const void *address) {
MEMORY_BASIC_INFORMATION memory {};
if (!query_memory(address, &memory)) {
log_warning("signal", "{}: VirtualQuery failed for {}: 0x{:08x}",
label, fmt::ptr(address), GetLastError());
return;
}
log_memory_information(label, memory);
}
static void log_adjacent_memory_regions(const void *address) {
MEMORY_BASIC_INFORMATION current {};
if (!query_memory(address, &current)) {
return;
}
const auto current_base = reinterpret_cast<uintptr_t>(current.BaseAddress);
if (current_base > 0) {
MEMORY_BASIC_INFORMATION previous {};
if (query_memory(reinterpret_cast<const void *>(current_base - 1), &previous)) {
log_memory_information("fault memory previous", previous);
}
}
const auto current_end = current_base + current.RegionSize;
if (current_end > current_base) {
MEMORY_BASIC_INFORMATION next {};
if (query_memory(reinterpret_cast<const void *>(current_end), &next)) {
log_memory_information("fault memory next", next);
}
}
}
static void log_exception_module(const void *address) {
MEMORY_BASIC_INFORMATION memory {};
if (!query_memory(address, &memory) || memory.Type != MEM_IMAGE || memory.AllocationBase == nullptr) {
log_warning("signal", "exception module: unavailable");
return;
}
char module_path[MAX_PATH] {};
const auto module = static_cast<HMODULE>(memory.AllocationBase);
const auto module_base = reinterpret_cast<uintptr_t>(memory.AllocationBase);
const auto exception_address = reinterpret_cast<uintptr_t>(address);
const auto module_length = GetModuleFileNameA(module, module_path, sizeof(module_path));
if (module_length > 0) {
const std::string module_name(module_path, std::min<size_t>(module_length, sizeof(module_path)));
log_warning("signal", "exception module: '{}' base={}, rva=0x{:x}",
module_name, fmt::ptr(memory.AllocationBase), exception_address - module_base);
} else {
log_warning("signal", "exception module: base={}, rva=0x{:x}",
fmt::ptr(memory.AllocationBase), exception_address - module_base);
}
}
static void log_instruction_bytes(const void *address) {
MEMORY_BASIC_INFORMATION memory {};
if (!query_memory(address, &memory) ||
memory.State != MEM_COMMIT ||
(memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) != 0) {
log_warning("signal", "instruction bytes: unavailable");
return;
}
constexpr size_t max_instruction_bytes = 16;
const auto region_end = reinterpret_cast<uintptr_t>(memory.BaseAddress) + memory.RegionSize;
const auto instruction_address = reinterpret_cast<uintptr_t>(address);
const auto available = region_end > instruction_address ? region_end - instruction_address : 0;
const auto byte_count = std::min(max_instruction_bytes, static_cast<size_t>(available));
uint8_t bytes[max_instruction_bytes] {};
SIZE_T bytes_read = 0;
if (byte_count == 0 ||
!ReadProcessMemory(GetCurrentProcess(), address, bytes, byte_count, &bytes_read)) {
log_warning("signal", "instruction bytes: read failed at {}: 0x{:08x}",
fmt::ptr(address), GetLastError());
return;
}
std::string byte_string;
byte_string.reserve(bytes_read * 3);
for (size_t index = 0; index < bytes_read; index++) {
fmt::format_to(std::back_inserter(byte_string), "{:02x}{}",
bytes[index], index + 1 < bytes_read ? " " : "");
}
log_warning("signal", "instruction bytes: {}", byte_string);
}
static void log_stack_context(const CONTEXT *context) {
const auto *tib = reinterpret_cast<const NT_TIB *>(NtCurrentTeb());
const auto stack_base = reinterpret_cast<uintptr_t>(tib->StackBase);
const auto stack_limit = reinterpret_cast<uintptr_t>(tib->StackLimit);
#ifdef _WIN64
const auto stack_pointer = static_cast<uintptr_t>(context->Rsp);
#else
const auto stack_pointer = static_cast<uintptr_t>(context->Esp);
#endif
const auto stack_used = stack_base > stack_pointer ? stack_base - stack_pointer : 0;
const auto stack_remaining = stack_pointer > stack_limit ? stack_pointer - stack_limit : 0;
log_warning("signal", "stack: base={}, limit={}, pointer={}, used=0x{:x}, remaining=0x{:x}",
fmt::ptr(tib->StackBase),
fmt::ptr(tib->StackLimit),
fmt::ptr(reinterpret_cast<const void *>(stack_pointer)),
stack_used,
stack_remaining);
}
static void log_exception_context(struct _EXCEPTION_POINTERS *ExceptionInfo, DWORD last_error) {
const auto *record = ExceptionInfo->ExceptionRecord;
const auto *context = ExceptionInfo->ContextRecord;
log_warning("signal", "thread id: {}", GetCurrentThreadId());
log_warning("signal", "thread last error (GetLastError()) at exception: 0x{:08x}", last_error);
log_warning("signal", "exception address: {}", fmt::ptr(record->ExceptionAddress));
log_exception_module(record->ExceptionAddress);
log_instruction_bytes(record->ExceptionAddress);
log_exception_parameters(record);
if ((record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) &&
record->NumberParameters >= 2) {
const auto operation = record->ExceptionInformation[0];
const auto address = reinterpret_cast<const void *>(record->ExceptionInformation[1]);
log_warning("signal", "invalid memory access: {} at {}",
access_operation(operation), fmt::ptr(address));
log_memory_region("fault memory", address);
log_adjacent_memory_regions(address);
}
if (record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR && record->NumberParameters >= 3) {
log_warning("signal", "in-page error status: 0x{:08x}",
static_cast<DWORD>(record->ExceptionInformation[2]));
}
if (context == nullptr) {
return;
}
log_stack_context(context);
#ifdef _WIN64
log_warning("signal", "registers: rax={:016x} rbx={:016x} rcx={:016x} rdx={:016x}",
context->Rax, context->Rbx, context->Rcx, context->Rdx);
log_warning("signal", "registers: rsi={:016x} rdi={:016x} rbp={:016x} rsp={:016x}",
context->Rsi, context->Rdi, context->Rbp, context->Rsp);
log_warning("signal", "registers: r8={:016x} r9={:016x} r10={:016x} r11={:016x}",
context->R8, context->R9, context->R10, context->R11);
log_warning("signal", "registers: r12={:016x} r13={:016x} r14={:016x} r15={:016x}",
context->R12, context->R13, context->R14, context->R15);
log_warning("signal", "registers: rip={:016x} eflags={:08x}",
context->Rip, context->EFlags);
#else
log_warning("signal", "registers: eax={:08x} ebx={:08x} ecx={:08x} edx={:08x}",
context->Eax, context->Ebx, context->Ecx, context->Edx);
log_warning("signal", "registers: esi={:08x} edi={:08x} ebp={:08x} esp={:08x}",
context->Esi, context->Edi, context->Ebp, context->Esp);
log_warning("signal", "registers: eip={:08x} eflags={:08x}",
context->Eip, context->EFlags);
#endif
}
static void write_minidump(struct _EXCEPTION_POINTERS *ExceptionInfo) {
if (MiniDumpWriteDump_local == nullptr) {
log_warning("signal", "minidump creation function not available, skipping");
return;
}
HANDLE minidump_file = CreateFileA(
"minidump.dmp",
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (minidump_file == INVALID_HANDLE_VALUE) {
log_warning("signal", "failed to create 'minidump.dmp' for minidump: 0x{:08x}",
GetLastError());
return;
}
MINIDUMP_EXCEPTION_INFORMATION ExceptionParam {};
ExceptionParam.ThreadId = GetCurrentThreadId();
ExceptionParam.ExceptionPointers = ExceptionInfo;
ExceptionParam.ClientPointers = FALSE;
constexpr auto extended_type = static_cast<MINIDUMP_TYPE>(
MiniDumpWithUnloadedModules |
MiniDumpWithIndirectlyReferencedMemory |
MiniDumpWithProcessThreadData |
MiniDumpWithFullMemoryInfo |
MiniDumpWithThreadInfo |
MiniDumpIgnoreInaccessibleMemory);
auto written_type = extended_type;
auto minidump_written = MiniDumpWriteDump_local(
GetCurrentProcess(),
GetCurrentProcessId(),
minidump_file,
extended_type,
&ExceptionParam,
nullptr,
nullptr);
auto minidump_error = minidump_written ? ERROR_SUCCESS : GetLastError();
if (!minidump_written) {
log_warning("signal", "failed to write extended minidump: 0x{:08x}; retrying MiniDumpNormal",
minidump_error);
LARGE_INTEGER file_start {};
if (SetFilePointerEx(minidump_file, file_start, nullptr, FILE_BEGIN) &&
SetEndOfFile(minidump_file)) {
written_type = MiniDumpNormal;
minidump_written = MiniDumpWriteDump_local(
GetCurrentProcess(),
GetCurrentProcessId(),
minidump_file,
MiniDumpNormal,
&ExceptionParam,
nullptr,
nullptr);
minidump_error = minidump_written ? ERROR_SUCCESS : GetLastError();
} else {
minidump_error = GetLastError();
}
}
LARGE_INTEGER minidump_size {};
const auto minidump_size_available = minidump_written &&
GetFileSizeEx(minidump_file, &minidump_size);
CloseHandle(minidump_file);
if (minidump_written) {
if (minidump_size_available) {
log_info("signal", "wrote minidump to 'minidump.dmp' (type=0x{:08x}, size={} bytes)",
static_cast<unsigned>(written_type), minidump_size.QuadPart);
} else {
log_info("signal", "wrote minidump to 'minidump.dmp' (type=0x{:08x})",
static_cast<unsigned>(written_type));
}
} else {
log_warning("signal", "failed to write 'minidump.dmp': 0x{:08x}", minidump_error);
}
}
static void log_exception_chain(const struct _EXCEPTION_RECORD *record) {
constexpr size_t max_exception_chain_depth = 8;
auto *record_cause = record->ExceptionRecord;
size_t depth = 0;
while (record_cause != nullptr && depth < max_exception_chain_depth) {
struct _EXCEPTION_RECORD cause {};
SIZE_T bytes_read = 0;
if (!ReadProcessMemory(
GetCurrentProcess(),
record_cause,
&cause,
sizeof(cause),
&bytes_read) ||
bytes_read != sizeof(cause)) {
log_warning("signal", "failed to read exception chain record {} at {}: 0x{:08x}",
depth, fmt::ptr(record_cause), GetLastError());
return;
}
log_warning("signal", "caused by: {} at {}",
exception_code(&cause), fmt::ptr(cause.ExceptionAddress));
log_exception_parameters(&cause);
record_cause = cause.ExceptionRecord;
depth++;
}
if (record_cause != nullptr) {
log_warning("signal", "exception chain truncated after {} records", max_exception_chain_depth);
}
}
static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
log_info("signal", "console ctrl handler called: {}", control_code(dwCtrlType));
@@ -89,15 +474,23 @@ static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
}
static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo) {
const auto last_error = GetLastError();
// ignore signal if disabled or no exception info provided
if (!launcher::signal::DISABLE && ExceptionInfo != nullptr) {
if (!launcher::signal::DISABLE &&
ExceptionInfo != nullptr &&
ExceptionInfo->ExceptionRecord != nullptr &&
InterlockedCompareExchange(&EXCEPTION_HANDLER_ACTIVE, 1, 0) == 0) {
// get exception record
struct _EXCEPTION_RECORD *ExceptionRecord = ExceptionInfo->ExceptionRecord;
// print signal
log_warning("signal", "exception raised: {}", exception_code(ExceptionRecord));
log_exception_context(ExceptionInfo, last_error);
// write the minidump before other diagnostics inspect potentially damaged state
write_minidump(ExceptionInfo);
switch (ExceptionRecord->ExceptionCode) {
case EXCEPTION_ILLEGAL_INSTRUCTION:
@@ -127,12 +520,11 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
// (e.g., ACIO init hang due to RTSS)
deferredlogs::dump_to_logger(true);
// walk the exception chain
struct _EXCEPTION_RECORD *record_cause = ExceptionRecord->ExceptionRecord;
while (record_cause != nullptr) {
log_warning("signal", "caused by: {}", exception_code(record_cause));
record_cause = record_cause->ExceptionRecord;
}
// dump memory information before inspecting potentially damaged exception and stack data
memutils::show_available_memory();
// walk the exception chain with bounded, checked reads
log_exception_chain(ExceptionRecord);
// print stacktrace
StackWalker sw;
@@ -141,44 +533,12 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
log_warning("signal", "failed to print callstack");
}
if (MiniDumpWriteDump_local != nullptr) {
HANDLE minidump_file = CreateFileA(
"minidump.dmp",
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (minidump_file != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION ExceptionParam {};
ExceptionParam.ThreadId = GetCurrentThreadId();
ExceptionParam.ExceptionPointers = ExceptionInfo;
ExceptionParam.ClientPointers = FALSE;
MiniDumpWriteDump_local(
GetCurrentProcess(),
GetCurrentProcessId(),
minidump_file,
MiniDumpNormal,
&ExceptionParam,
nullptr,
nullptr);
CloseHandle(minidump_file);
} else {
log_warning("signal", "failed to create 'minidump.dmp' for minidump: 0x{:08x}",
GetLastError());
}
} else {
log_warning("signal", "minidump creation function not available, skipping");
}
// this will stall all UI threads for this process
show_popup_for_crash();
log_fatal("signal", "end");
InterlockedExchange(&EXCEPTION_HANDLER_ACTIVE, 0);
}
return EXCEPTION_CONTINUE_SEARCH;
+3 -1
View File
@@ -67,7 +67,9 @@ namespace superexit {
}
}
bool async_key_exit = GetAsyncKeyState(VK_MENU) && GetAsyncKeyState(VK_F4);
bool async_key_exit =
(GetAsyncKeyState(VK_MENU) & 0x8000) != 0 &&
(GetAsyncKeyState(VK_F4) & 0x8000) != 0;
bool overlay_exit = false;
auto buttons = games::get_buttons_overlay(eamuse_get_game());