mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Offer to restart game from VEH exception handler (#936)
This commit is contained in:
parent
98e421a227
commit
58ceb1dc87
16 changed files with 1279 additions and 616 deletions
|
|
@ -33,10 +33,12 @@
|
|||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(LibraryPath)</LibraryPath>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
|
|
@ -53,7 +55,7 @@
|
|||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalDependencies>dbghelp.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ struct DalamudStartInfo {
|
|||
BeforeDalamudConstruct = 1 << 2,
|
||||
};
|
||||
friend void from_json(const nlohmann::json&, WaitMessageboxFlags&);
|
||||
friend WaitMessageboxFlags operator &(WaitMessageboxFlags a, WaitMessageboxFlags b) {
|
||||
return static_cast<WaitMessageboxFlags>(static_cast<int>(a) & static_cast<int>(b));
|
||||
}
|
||||
|
||||
enum class DotNetOpenProcessHookMode : int {
|
||||
ImportHooks = 0,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "windows.h"
|
||||
#include <cinttypes>
|
||||
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
struct exception_info
|
||||
{
|
||||
void* ExceptionPointers; // Cannot dereference!
|
||||
DWORD ThreadId;
|
||||
DWORD ProcessId;
|
||||
BOOL DoFullDump;
|
||||
wchar_t DumpPath[1000];
|
||||
|
||||
// For metrics
|
||||
DWORD ExceptionCode;
|
||||
long long Lifetime;
|
||||
LPEXCEPTION_POINTERS pExceptionPointers;
|
||||
EXCEPTION_POINTERS ExceptionPointers;
|
||||
EXCEPTION_RECORD ExceptionRecord;
|
||||
CONTEXT ContextRecord;
|
||||
uint64_t nLifetime;
|
||||
HANDLE hThreadHandle;
|
||||
DWORD dwStackTraceLength;
|
||||
};
|
||||
|
||||
constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare";
|
||||
constexpr wchar_t CRASHDUMP_EVENT_NAME[] = L"Global\\DalamudRequestWriteDump";
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
|
||||
logging::I("Built at: " __DATE__ "@" __TIME__);
|
||||
|
||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize))
|
||||
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
|
||||
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
|
||||
|
||||
if (minHookLoaded) {
|
||||
|
|
|
|||
|
|
@ -11,15 +11,16 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
|
||||
// Windows Header Files
|
||||
#include <windows.h>
|
||||
#include <DbgHelp.h>
|
||||
// Windows Header Files (1)
|
||||
#include <Windows.h>
|
||||
|
||||
// Windows Header Files (2)
|
||||
#include <Dbt.h>
|
||||
#include <PathCch.h>
|
||||
#include <Psapi.h>
|
||||
#include <Shlobj.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <Dbt.h>
|
||||
#include <ShlObj.h>
|
||||
#include <SubAuth.h>
|
||||
#include <TlHelp32.h>
|
||||
|
||||
// MSVC Compiler Intrinsic
|
||||
#include <intrin.h>
|
||||
|
|
@ -33,11 +34,11 @@
|
|||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
// https://www.akenotsuki.com/misc/srell/en/
|
||||
|
|
@ -50,8 +51,8 @@
|
|||
#include "../lib/Nomade040-nmd/nmd_assembly.h"
|
||||
|
||||
// https://github.com/dotnet/coreclr
|
||||
#include "../lib/CoreCLR/CoreCLR.h"
|
||||
#include "../lib/CoreCLR/boot.h"
|
||||
#include "../lib/CoreCLR/CoreCLR.h"
|
||||
|
||||
// https://github.com/nlohmann/json
|
||||
#include "../lib/nlohmann-json/json.hpp"
|
||||
|
|
|
|||
|
|
@ -520,3 +520,36 @@ void utils::wait_for_game_window() {
|
|||
};
|
||||
SendMessageW(game_window, WM_NULL, 0, 0);
|
||||
}
|
||||
|
||||
std::wstring utils::escape_shell_arg(const std::wstring& arg) {
|
||||
// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
|
||||
|
||||
std::wstring res;
|
||||
if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
|
||||
res.append(arg);
|
||||
} else {
|
||||
res.push_back(L'"');
|
||||
for (auto it = arg.begin(); ; ++it) {
|
||||
size_t bsCount = 0;
|
||||
|
||||
while (it != arg.end() && *it == L'\\') {
|
||||
++it;
|
||||
++bsCount;
|
||||
}
|
||||
|
||||
if (it == arg.end()) {
|
||||
res.append(bsCount * 2, L'\\');
|
||||
break;
|
||||
} else if (*it == L'"') {
|
||||
res.append(bsCount * 2 + 1, L'\\');
|
||||
res.push_back(*it);
|
||||
} else {
|
||||
res.append(bsCount, L'\\');
|
||||
res.push_back(*it);
|
||||
}
|
||||
}
|
||||
|
||||
res.push_back(L'"');
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -260,4 +260,6 @@ namespace utils {
|
|||
HWND try_find_game_window();
|
||||
|
||||
void wait_for_game_window();
|
||||
|
||||
std::wstring escape_shell_arg(const std::wstring& arg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#include "veh.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
|
|
@ -10,6 +8,7 @@
|
|||
#include "utils.h"
|
||||
|
||||
#include "crashhandler_shared.h"
|
||||
#include "DalamudStartInfo.h"
|
||||
|
||||
#pragma comment(lib, "comctl32.lib")
|
||||
|
||||
|
|
@ -25,9 +24,8 @@
|
|||
|
||||
PVOID g_veh_handle = nullptr;
|
||||
bool g_veh_do_full_dump = false;
|
||||
|
||||
exception_info* g_crashhandler_shared_info;
|
||||
HANDLE g_crashhandler_event;
|
||||
HANDLE g_crashhandler_process = nullptr;
|
||||
HANDLE g_crashhandler_pipe_write = nullptr;
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> g_time_start;
|
||||
|
||||
|
|
@ -73,7 +71,6 @@ bool is_whitelist_exception(const DWORD code)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::filesystem::path& module_file)
|
||||
{
|
||||
HMODULE handle;
|
||||
|
|
@ -91,7 +88,6 @@ bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
|
||||
{
|
||||
DWORD64 module_base;
|
||||
|
|
@ -100,369 +96,223 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool get_sym_from_addr(const DWORD64 address, DWORD64& displacement, std::wstring& symbol_name)
|
||||
static void append_injector_launch_args(std::vector<std::wstring>& args)
|
||||
{
|
||||
union {
|
||||
char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]{};
|
||||
SYMBOL_INFOW symbol;
|
||||
};
|
||||
symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol.MaxNameLen = MAX_SYM_NAME;
|
||||
args.emplace_back(L"-g");
|
||||
args.emplace_back(utils::loaded_module::current_process().path().wstring());
|
||||
if (g_startInfo.BootShowConsole)
|
||||
args.emplace_back(L"--console");
|
||||
if (g_startInfo.BootEnableEtw)
|
||||
args.emplace_back(L"--etw");
|
||||
if (g_startInfo.BootVehEnabled)
|
||||
args.emplace_back(L"--veh");
|
||||
if (g_startInfo.BootVehFull)
|
||||
args.emplace_back(L"--veh-full");
|
||||
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
|
||||
args.emplace_back(L"--msgbox1");
|
||||
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint) != DalamudStartInfo::WaitMessageboxFlags::None)
|
||||
args.emplace_back(L"--msgbox2");
|
||||
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None)
|
||||
args.emplace_back(L"--msgbox3");
|
||||
|
||||
if (SymFromAddrW(GetCurrentProcess(), address, &displacement, &symbol) && symbol.Name[0])
|
||||
{
|
||||
symbol_name = symbol.Name;
|
||||
return true;
|
||||
args.emplace_back(L"--");
|
||||
|
||||
if (int nArgs; LPWSTR * szArgList = CommandLineToArgvW(GetCommandLineW(), &nArgs)) {
|
||||
for (auto i = 1; i < nArgs; i++)
|
||||
args.emplace_back(szArgList[i]);
|
||||
LocalFree(szArgList);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = true)
|
||||
{
|
||||
DWORD64 module_base;
|
||||
std::filesystem::path module_path;
|
||||
bool is_mod_addr = get_module_file_and_base(address, module_base, module_path);
|
||||
|
||||
DWORD64 value = 0;
|
||||
if(try_ptrderef && address > 0x10000 && address < 0x7FFFFFFE0000)
|
||||
{
|
||||
ReadProcessMemory(GetCurrentProcess(), reinterpret_cast<void*>(address), &value, sizeof value, nullptr);
|
||||
}
|
||||
|
||||
std::wstring addr_str = is_mod_addr ?
|
||||
std::format(L"{}+{:X}", module_path.filename().c_str(), address - module_base) :
|
||||
std::format(L"{:X}", address);
|
||||
|
||||
DWORD64 displacement;
|
||||
if (std::wstring symbol; get_sym_from_addr(address, displacement, symbol))
|
||||
return std::format(L"{}\t({})", addr_str, displacement != 0 ? std::format(L"{}+0x{:X}", symbol, displacement) : std::format(L"{}", symbol));
|
||||
return value != 0 ? std::format(L"{} [{}]", addr_str, to_address_string(value, false)) : addr_str;
|
||||
}
|
||||
|
||||
void print_exception_info_extended(const EXCEPTION_POINTERS* ex, std::wostringstream& log)
|
||||
{
|
||||
CONTEXT ctx = *ex->ContextRecord;
|
||||
|
||||
log << L"\nRegisters\n{";
|
||||
|
||||
log << std::format(L"\n RAX:\t{}", to_address_string(ctx.Rax));
|
||||
log << std::format(L"\n RBX:\t{}", to_address_string(ctx.Rbx));
|
||||
log << std::format(L"\n RCX:\t{}", to_address_string(ctx.Rcx));
|
||||
log << std::format(L"\n RDX:\t{}", to_address_string(ctx.Rdx));
|
||||
log << std::format(L"\n R8:\t{}", to_address_string(ctx.R8));
|
||||
log << std::format(L"\n R9:\t{}", to_address_string(ctx.R9));
|
||||
log << std::format(L"\n R10:\t{}", to_address_string(ctx.R10));
|
||||
log << std::format(L"\n R11:\t{}", to_address_string(ctx.R11));
|
||||
log << std::format(L"\n R12:\t{}", to_address_string(ctx.R12));
|
||||
log << std::format(L"\n R13:\t{}", to_address_string(ctx.R13));
|
||||
log << std::format(L"\n R14:\t{}", to_address_string(ctx.R14));
|
||||
log << std::format(L"\n R15:\t{}", to_address_string(ctx.R15));
|
||||
|
||||
log << std::format(L"\n RSI:\t{}", to_address_string(ctx.Rsi));
|
||||
log << std::format(L"\n RDI:\t{}", to_address_string(ctx.Rdi));
|
||||
log << std::format(L"\n RBP:\t{}", to_address_string(ctx.Rbp));
|
||||
log << std::format(L"\n RSP:\t{}", to_address_string(ctx.Rsp));
|
||||
log << std::format(L"\n RIP:\t{}", to_address_string(ctx.Rip));
|
||||
|
||||
log << L"\n}" << std::endl;
|
||||
|
||||
if(0x10000 < ctx.Rsp && ctx.Rsp < 0x7FFFFFFE0000)
|
||||
{
|
||||
log << L"\nStack\n{";
|
||||
|
||||
for(DWORD64 i = 0; i < 16; i++)
|
||||
log << std::format(L"\n [RSP+{:X}]\t{}", i * 8, to_address_string(*reinterpret_cast<DWORD64*>(ctx.Rsp + i * 8ull)));
|
||||
|
||||
log << L"\n}\n";
|
||||
}
|
||||
|
||||
log << L"\nModules\n{";
|
||||
|
||||
if(HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetCurrentProcessId()); snap != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
MODULEENTRY32 mod;
|
||||
mod.dwSize = sizeof MODULEENTRY32;
|
||||
if(Module32First(snap, &mod))
|
||||
{
|
||||
do
|
||||
{
|
||||
log << std::format(L"\n {:08X}\t{}", reinterpret_cast<DWORD64>(mod.modBaseAddr), mod.szExePath);
|
||||
}
|
||||
while (Module32Next(snap, &mod));
|
||||
}
|
||||
CloseHandle(snap);
|
||||
}
|
||||
|
||||
log << L"\n}\n";
|
||||
}
|
||||
|
||||
void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log)
|
||||
{
|
||||
size_t rec_index = 0;
|
||||
for (auto rec = ex->ExceptionRecord; rec; rec = rec->ExceptionRecord)
|
||||
{
|
||||
log << std::format(L"\nException Info #{}\n", ++rec_index);
|
||||
log << std::format(L"Address: {:X}\n", rec->ExceptionCode);
|
||||
log << std::format(L"Flags: {:X}\n", rec->ExceptionFlags);
|
||||
log << std::format(L"Address: {:X}\n", reinterpret_cast<size_t>(rec->ExceptionAddress));
|
||||
if (!rec->NumberParameters)
|
||||
continue;
|
||||
log << L"Parameters: ";
|
||||
for (DWORD i = 0; i < rec->NumberParameters; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
log << L", ";
|
||||
log << std::format(L"{:X}", rec->ExceptionInformation[i]);
|
||||
}
|
||||
}
|
||||
|
||||
log << L"\nCall Stack\n{";
|
||||
|
||||
STACKFRAME64 sf;
|
||||
sf.AddrPC.Offset = ex->ContextRecord->Rip;
|
||||
sf.AddrPC.Mode = AddrModeFlat;
|
||||
sf.AddrStack.Offset = ex->ContextRecord->Rsp;
|
||||
sf.AddrStack.Mode = AddrModeFlat;
|
||||
sf.AddrFrame.Offset = ex->ContextRecord->Rbp;
|
||||
sf.AddrFrame.Mode = AddrModeFlat;
|
||||
CONTEXT ctx = *ex->ContextRecord;
|
||||
int frame_index = 0;
|
||||
|
||||
log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false));
|
||||
|
||||
do
|
||||
{
|
||||
if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
|
||||
break;
|
||||
|
||||
log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false));
|
||||
|
||||
} while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset);
|
||||
|
||||
log << L"\n}\n";
|
||||
}
|
||||
|
||||
HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd,
|
||||
UINT uNotification,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LONG_PTR dwRefData)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
switch (uNotification)
|
||||
{
|
||||
case TDN_CREATED:
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
LONG exception_handler(EXCEPTION_POINTERS* ex)
|
||||
{
|
||||
static std::recursive_mutex s_exception_handler_mutex;
|
||||
|
||||
if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
||||
{
|
||||
// pass
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
|
||||
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
|
||||
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// block any other exceptions hitting the veh while the messagebox is open
|
||||
const auto lock = std::lock_guard(s_exception_handler_mutex);
|
||||
|
||||
const auto module_path = utils::loaded_module(g_hModule).path().parent_path();
|
||||
#ifndef NDEBUG
|
||||
const auto dmp_path = (module_path / L"dalamud_appcrashd.dmp").wstring();
|
||||
#else
|
||||
const auto dmp_path = (module_path / L"dalamud_appcrash.dmp").wstring();
|
||||
#endif
|
||||
const auto log_path = (module_path / L"dalamud_appcrash.log").wstring();
|
||||
exception_info exinfo{};
|
||||
exinfo.pExceptionPointers = ex;
|
||||
exinfo.ExceptionPointers = *ex;
|
||||
exinfo.ContextRecord = *ex->ContextRecord;
|
||||
exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
|
||||
const auto time_now = std::chrono::system_clock::now();
|
||||
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
time_now.time_since_epoch()).count()
|
||||
- std::chrono::duration_cast<std::chrono::seconds>(
|
||||
g_time_start.time_since_epoch()).count();
|
||||
exinfo.nLifetime = lifetime;
|
||||
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||
|
||||
std::wostringstream log;
|
||||
log << std::format(L"Unhandled native exception occurred at {}", to_address_string(ex->ContextRecord->Rip, false)) << std::endl;
|
||||
log << std::format(L"Code: {:X}", ex->ExceptionRecord->ExceptionCode) << std::endl;
|
||||
log << std::format(L"Dump at: {}", dmp_path) << std::endl;
|
||||
log << L"Time: " << std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::system_clock::now() } << std::endl;
|
||||
|
||||
SymRefreshModuleList(GetCurrentProcess());
|
||||
print_exception_info(ex, log);
|
||||
auto window_log_str = log.str();
|
||||
print_exception_info_extended(ex, log);
|
||||
|
||||
if (g_crashhandler_shared_info && g_crashhandler_event)
|
||||
{
|
||||
memset(g_crashhandler_shared_info, 0, sizeof(exception_info));
|
||||
|
||||
wcsncpy_s(g_crashhandler_shared_info->DumpPath, dmp_path.c_str(), 1000);
|
||||
g_crashhandler_shared_info->ThreadId = GetThreadId(GetCurrentThread());
|
||||
g_crashhandler_shared_info->ProcessId = GetCurrentProcessId();
|
||||
g_crashhandler_shared_info->ExceptionPointers = ex;
|
||||
g_crashhandler_shared_info->DoFullDump = g_veh_do_full_dump;
|
||||
g_crashhandler_shared_info->ExceptionCode = ex->ExceptionRecord->ExceptionCode;
|
||||
|
||||
const auto time_now = std::chrono::system_clock::now();
|
||||
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
time_now.time_since_epoch()).count()
|
||||
- std::chrono::duration_cast<std::chrono::seconds>(
|
||||
g_time_start.time_since_epoch()).count();
|
||||
|
||||
g_crashhandler_shared_info->Lifetime = lifetime;
|
||||
|
||||
SetEvent(g_crashhandler_event);
|
||||
}
|
||||
|
||||
std::wstring message;
|
||||
void* fn;
|
||||
if (const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
|
||||
std::wstring stackTrace;
|
||||
if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
|
||||
L"Dalamud.EntryPoint, Dalamud",
|
||||
L"VehCallback",
|
||||
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
|
||||
nullptr, nullptr, &fn)))
|
||||
{
|
||||
message = std::format(
|
||||
L"An error within the game has occurred.\n\n"
|
||||
L"This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n"
|
||||
L"Please try \"Start Over\" or \"Download Index Backup\" in TexTools, an integrity check in the XIVLauncher settings, and disabling plugins you don't need.\n\n"
|
||||
L"The log file is located at:\n"
|
||||
L"{1}\n\n"
|
||||
L"Press OK to exit the application.\n\nFailed to read stack trace: 0x{2:08x}",
|
||||
dmp_path, log_path, err);
|
||||
stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto pMessage = ((wchar_t*(__stdcall*)(const void*, const void*, const void*))fn)(dmp_path.c_str(), log_path.c_str(), log.str().c_str());
|
||||
message = pMessage;
|
||||
stackTrace = static_cast<wchar_t*(*)()>(fn)();
|
||||
// Don't free it, as the program's going to be quit anyway
|
||||
}
|
||||
|
||||
exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size());
|
||||
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
logging::E(std::format(L"Trapped in VEH handler: {}", message));
|
||||
|
||||
// show in another thread to prevent messagebox from pumping messages of current thread
|
||||
std::thread([&]()
|
||||
{
|
||||
int nButtonPressed = 0;
|
||||
TASKDIALOGCONFIG config = {0};
|
||||
const TASKDIALOG_BUTTON buttons[] = {
|
||||
{IDOK, L"Disable all plugins"},
|
||||
{IDABORT, L"Open help page"},
|
||||
};
|
||||
config.cbSize = sizeof(config);
|
||||
config.hInstance = g_hModule;
|
||||
config.dwCommonButtons = TDCBF_CLOSE_BUTTON;
|
||||
config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1);
|
||||
//config.hMainIcon = dalamud_icon;
|
||||
config.pszMainInstruction = L"An error occurred";
|
||||
config.pszContent = message.c_str();
|
||||
config.pButtons = buttons;
|
||||
config.cButtons = ARRAYSIZE(buttons);
|
||||
config.pszExpandedInformation = window_log_str.c_str();
|
||||
config.pszWindowTitle = L"Dalamud Error";
|
||||
config.nDefaultButton = IDCLOSE;
|
||||
config.cxWidth = 300;
|
||||
|
||||
// Can't do this, xiv stops pumping messages here
|
||||
//config.hwndParent = FindWindowA("FFXIVGAME", NULL);
|
||||
|
||||
config.pfCallback = TaskDialogCallbackProc;
|
||||
|
||||
TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL);
|
||||
switch (nButtonPressed)
|
||||
{
|
||||
case IDOK:
|
||||
TCHAR szPath[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, szPath)))
|
||||
{
|
||||
auto appdata = std::filesystem::path(szPath);
|
||||
auto safemode_file_path = ( appdata / "XIVLauncher" / ".dalamud_safemode" );
|
||||
|
||||
std::ofstream ofs(safemode_file_path);
|
||||
ofs << "STAY SAFE!!!";
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
break;
|
||||
case IDABORT:
|
||||
ShellExecute(0, 0, L"https://goatcorp.github.io/faq?utm_source=vectored", 0, 0 , SW_SHOW );
|
||||
break;
|
||||
case IDCANCEL:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}).join();
|
||||
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &stackTrace[0], static_cast<DWORD>(std::span(stackTrace).size_bytes()), &written, nullptr) || std::span(stackTrace).size_bytes() != written)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
SuspendThread(GetCurrentThread());
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool veh::add_handler(bool doFullDump, std::string workingDirectory)
|
||||
bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
||||
{
|
||||
if (g_veh_handle)
|
||||
return false;
|
||||
|
||||
g_veh_handle = AddVectoredExceptionHandler(1, exception_handler);
|
||||
SetUnhandledExceptionFilter(nullptr);
|
||||
|
||||
g_veh_do_full_dump = doFullDump;
|
||||
g_time_start = std::chrono::system_clock::now();
|
||||
|
||||
auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME);
|
||||
if (!file_mapping) {
|
||||
std::cout << "Could not map info share file.\n";
|
||||
g_crashhandler_shared_info = nullptr;
|
||||
std::optional<std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>> hWritePipe;
|
||||
std::optional<std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>> hReadPipeInheritable;
|
||||
if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536))
|
||||
{
|
||||
hWritePipe.emplace(hWritePipeRaw, &CloseHandle);
|
||||
|
||||
if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
|
||||
{
|
||||
hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(1) error 0x{:x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_crashhandler_shared_info = (exception_info*)MapViewOfFile(file_mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(exception_info));
|
||||
if (!g_crashhandler_shared_info) {
|
||||
std::cout << "Could not map view of info share file.\n";
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: CreatePipe error 0x{:x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// additional information
|
||||
STARTUPINFOEXW siex{};
|
||||
PROCESS_INFORMATION pi{};
|
||||
|
||||
siex.StartupInfo.cb = sizeof siex;
|
||||
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
||||
#ifdef NDEBUG
|
||||
siex.StartupInfo.wShowWindow = SW_HIDE;
|
||||
#else
|
||||
siex.StartupInfo.wShowWindow = SW_SHOW;
|
||||
#endif
|
||||
|
||||
// set up list of handles to inherit to child process
|
||||
std::vector<char> attributeListBuf;
|
||||
if (SIZE_T size = 0; !InitializeProcThreadAttributeList(nullptr, 1, 0, &size))
|
||||
{
|
||||
if (const auto err = GetLastError(); err != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(1) error 0x{:x}", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
attributeListBuf.resize(size);
|
||||
siex.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(&attributeListBuf[0]);
|
||||
if (!InitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &size))
|
||||
{
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(2) error 0x{:x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
g_crashhandler_event = CreateEvent(
|
||||
NULL, // default security attributes
|
||||
TRUE, // manual-reset event
|
||||
FALSE, // initial state is nonsignaled
|
||||
CRASHDUMP_EVENT_NAME // object name
|
||||
);
|
||||
|
||||
if (!g_crashhandler_event)
|
||||
else
|
||||
{
|
||||
std::cout << "Couldn't acquire event handle\n";
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(0) was supposed to fail");
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<std::remove_pointer_t<LPPROC_THREAD_ATTRIBUTE_LIST>, decltype(&DeleteProcThreadAttributeList)> cleanAttributeList(siex.lpAttributeList, &DeleteProcThreadAttributeList);
|
||||
|
||||
std::vector<HANDLE> handles;
|
||||
|
||||
HANDLE hInheritableCurrentProcess;
|
||||
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &hInheritableCurrentProcess, 0, TRUE, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(2) error 0x{:x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
handles.push_back(hInheritableCurrentProcess);
|
||||
handles.push_back(hReadPipeInheritable->get());
|
||||
|
||||
std::vector<std::wstring> args;
|
||||
std::wstring argstr;
|
||||
args.emplace_back((std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe").wstring());
|
||||
args.emplace_back(std::format(L"--process-handle={}", reinterpret_cast<size_t>(hInheritableCurrentProcess)));
|
||||
args.emplace_back(std::format(L"--exception-info-pipe-read-handle={}", reinterpret_cast<size_t>(hReadPipeInheritable->get())));
|
||||
args.emplace_back(std::format(L"--asset-directory={}", unicode::convert<std::wstring>(g_startInfo.AssetDirectory)));
|
||||
args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
|
||||
? utils::loaded_module(g_hModule).path().parent_path().wstring()
|
||||
: std::filesystem::path(unicode::convert<std::wstring>(g_startInfo.BootLogPath)).parent_path().wstring()));
|
||||
args.emplace_back(L"--");
|
||||
append_injector_launch_args(args);
|
||||
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
argstr.append(utils::escape_shell_arg(arg));
|
||||
argstr.push_back(L' ');
|
||||
}
|
||||
argstr.pop_back();
|
||||
|
||||
if (!handles.empty() && !UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles[0], std::span(handles).size_bytes(), nullptr, nullptr))
|
||||
{
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: UpdateProcThreadAttribute error 0x{:x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto handler_path = std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe";
|
||||
|
||||
// additional information
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
if (!CreateProcessW(
|
||||
args[0].c_str(), // The path
|
||||
&argstr[0], // Command line
|
||||
nullptr, // Process handle not inheritable
|
||||
nullptr, // Thread handle not inheritable
|
||||
TRUE, // Set handle inheritance to FALSE
|
||||
EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W)
|
||||
nullptr, // Use parent's environment block
|
||||
nullptr, // Use parent's starting directory
|
||||
&siex.StartupInfo, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
|
||||
))
|
||||
{
|
||||
logging::W("Failed to launch DalamudCrashHandler.exe: CreateProcessW error 0x{:x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the size of the structures
|
||||
ZeroMemory( &si, sizeof(si) );
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory( &pi, sizeof(pi) );
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
CreateProcess( handler_path.c_str(), // the path
|
||||
NULL, // Command line
|
||||
NULL, // Process handle not inheritable
|
||||
NULL, // Thread handle not inheritable
|
||||
FALSE, // Set handle inheritance to FALSE
|
||||
0, // No creation flags
|
||||
NULL, // Use parent's environment block
|
||||
NULL, // Use parent's starting directory
|
||||
&si, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
|
||||
);
|
||||
|
||||
// Close process and thread handles.
|
||||
CloseHandle( pi.hProcess );
|
||||
CloseHandle( pi.hThread );
|
||||
|
||||
return g_veh_handle != nullptr;
|
||||
g_crashhandler_process = pi.hProcess;
|
||||
g_crashhandler_pipe_write = hWritePipe->release();
|
||||
logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool veh::remove_handler()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
namespace veh
|
||||
{
|
||||
bool add_handler(bool doFullDump, std::string workingDirectory);
|
||||
bool add_handler(bool doFullDump, const std::string& workingDirectory);
|
||||
bool remove_handler();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue