Offer to restart game from VEH exception handler (#936)

This commit is contained in:
kizer 2022-07-27 04:14:48 +09:00 committed by GitHub
parent 98e421a227
commit 58ceb1dc87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1279 additions and 616 deletions

View file

@ -33,10 +33,12 @@
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <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>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <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> </PropertyGroup>
<ItemDefinitionGroup> <ItemDefinitionGroup>
<ClCompile> <ClCompile>
@ -53,7 +55,7 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC> <EnableUAC>false</EnableUAC>
<AdditionalDependencies>dbghelp.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> <AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>

View file

@ -8,6 +8,9 @@ struct DalamudStartInfo {
BeforeDalamudConstruct = 1 << 2, BeforeDalamudConstruct = 1 << 2,
}; };
friend void from_json(const nlohmann::json&, WaitMessageboxFlags&); 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 { enum class DotNetOpenProcessHookMode : int {
ImportHooks = 0, ImportHooks = 0,

View file

@ -1,19 +1,18 @@
#pragma once #pragma once
#include "windows.h" #include <cinttypes>
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
struct exception_info struct exception_info
{ {
void* ExceptionPointers; // Cannot dereference! LPEXCEPTION_POINTERS pExceptionPointers;
DWORD ThreadId; EXCEPTION_POINTERS ExceptionPointers;
DWORD ProcessId; EXCEPTION_RECORD ExceptionRecord;
BOOL DoFullDump; CONTEXT ContextRecord;
wchar_t DumpPath[1000]; uint64_t nLifetime;
HANDLE hThreadHandle;
// For metrics DWORD dwStackTraceLength;
DWORD ExceptionCode;
long long Lifetime;
}; };
constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare";
constexpr wchar_t CRASHDUMP_EVENT_NAME[] = L"Global\\DalamudRequestWriteDump";

View file

@ -87,7 +87,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors"); logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
logging::I("Built at: " __DATE__ "@" __TIME__); 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); MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
if (minHookLoaded) { if (minHookLoaded) {

View file

@ -11,15 +11,16 @@
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define NOMINMAX #define NOMINMAX
// Windows Header Files // Windows Header Files (1)
#include <windows.h> #include <Windows.h>
#include <DbgHelp.h>
// Windows Header Files (2)
#include <Dbt.h>
#include <PathCch.h> #include <PathCch.h>
#include <Psapi.h> #include <Psapi.h>
#include <Shlobj.h> #include <ShlObj.h>
#include <TlHelp32.h>
#include <Dbt.h>
#include <SubAuth.h> #include <SubAuth.h>
#include <TlHelp32.h>
// MSVC Compiler Intrinsic // MSVC Compiler Intrinsic
#include <intrin.h> #include <intrin.h>
@ -33,11 +34,11 @@
#include <fstream> #include <fstream>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <mutex>
#include <ranges> #include <ranges>
#include <set> #include <set>
#include <span> #include <span>
#include <string> #include <string>
#include <mutex>
#include <type_traits> #include <type_traits>
// https://www.akenotsuki.com/misc/srell/en/ // https://www.akenotsuki.com/misc/srell/en/
@ -50,8 +51,8 @@
#include "../lib/Nomade040-nmd/nmd_assembly.h" #include "../lib/Nomade040-nmd/nmd_assembly.h"
// https://github.com/dotnet/coreclr // https://github.com/dotnet/coreclr
#include "../lib/CoreCLR/CoreCLR.h"
#include "../lib/CoreCLR/boot.h" #include "../lib/CoreCLR/boot.h"
#include "../lib/CoreCLR/CoreCLR.h"
// https://github.com/nlohmann/json // https://github.com/nlohmann/json
#include "../lib/nlohmann-json/json.hpp" #include "../lib/nlohmann-json/json.hpp"

View file

@ -520,3 +520,36 @@ void utils::wait_for_game_window() {
}; };
SendMessageW(game_window, WM_NULL, 0, 0); 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;
}

View file

@ -260,4 +260,6 @@ namespace utils {
HWND try_find_game_window(); HWND try_find_game_window();
void wait_for_game_window(); void wait_for_game_window();
std::wstring escape_shell_arg(const std::wstring& arg);
} }

View file

@ -1,7 +1,5 @@
#include "pch.h" #include "pch.h"
#include "resource.h"
#include "veh.h" #include "veh.h"
#include <shellapi.h> #include <shellapi.h>
@ -10,6 +8,7 @@
#include "utils.h" #include "utils.h"
#include "crashhandler_shared.h" #include "crashhandler_shared.h"
#include "DalamudStartInfo.h"
#pragma comment(lib, "comctl32.lib") #pragma comment(lib, "comctl32.lib")
@ -25,9 +24,8 @@
PVOID g_veh_handle = nullptr; PVOID g_veh_handle = nullptr;
bool g_veh_do_full_dump = false; bool g_veh_do_full_dump = false;
HANDLE g_crashhandler_process = nullptr;
exception_info* g_crashhandler_shared_info; HANDLE g_crashhandler_pipe_write = nullptr;
HANDLE g_crashhandler_event;
std::chrono::time_point<std::chrono::system_clock> g_time_start; 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) bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::filesystem::path& module_file)
{ {
HMODULE handle; HMODULE handle;
@ -91,7 +88,6 @@ bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::
return false; return false;
} }
bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address) bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
{ {
DWORD64 module_base; DWORD64 module_base;
@ -100,369 +96,223 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
return false; return false;
} }
static void append_injector_launch_args(std::vector<std::wstring>& args)
bool get_sym_from_addr(const DWORD64 address, DWORD64& displacement, std::wstring& symbol_name)
{ {
union { args.emplace_back(L"-g");
char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]{}; args.emplace_back(utils::loaded_module::current_process().path().wstring());
SYMBOL_INFOW symbol; if (g_startInfo.BootShowConsole)
}; args.emplace_back(L"--console");
symbol.SizeOfStruct = sizeof(SYMBOL_INFO); if (g_startInfo.BootEnableEtw)
symbol.MaxNameLen = MAX_SYM_NAME; 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]) args.emplace_back(L"--");
{
symbol_name = symbol.Name; if (int nArgs; LPWSTR * szArgList = CommandLineToArgvW(GetCommandLineW(), &nArgs)) {
return true; 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) LONG exception_handler(EXCEPTION_POINTERS* ex)
{ {
static std::recursive_mutex s_exception_handler_mutex; static std::recursive_mutex s_exception_handler_mutex;
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
{
// pass
}
else
{
if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode)) if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode))
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
}
// block any other exceptions hitting the veh while the messagebox is open // block any other exceptions hitting the veh while the messagebox is open
const auto lock = std::lock_guard(s_exception_handler_mutex); const auto lock = std::lock_guard(s_exception_handler_mutex);
const auto module_path = utils::loaded_module(g_hModule).path().parent_path(); exception_info exinfo{};
#ifndef NDEBUG exinfo.pExceptionPointers = ex;
const auto dmp_path = (module_path / L"dalamud_appcrashd.dmp").wstring(); exinfo.ExceptionPointers = *ex;
#else exinfo.ContextRecord = *ex->ContextRecord;
const auto dmp_path = (module_path / L"dalamud_appcrash.dmp").wstring(); exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
#endif
const auto log_path = (module_path / L"dalamud_appcrash.log").wstring();
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(); const auto time_now = std::chrono::system_clock::now();
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>( auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
time_now.time_since_epoch()).count() time_now.time_since_epoch()).count()
- std::chrono::duration_cast<std::chrono::seconds>( - std::chrono::duration_cast<std::chrono::seconds>(
g_time_start.time_since_epoch()).count(); g_time_start.time_since_epoch()).count();
exinfo.nLifetime = lifetime;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
g_crashhandler_shared_info->Lifetime = lifetime; std::wstring stackTrace;
if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
SetEvent(g_crashhandler_event);
}
std::wstring message;
void* fn;
if (const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
L"Dalamud.EntryPoint, Dalamud", L"Dalamud.EntryPoint, Dalamud",
L"VehCallback", L"VehCallback",
L"Dalamud.EntryPoint+VehDelegate, Dalamud", L"Dalamud.EntryPoint+VehDelegate, Dalamud",
nullptr, nullptr, &fn))) nullptr, nullptr, &fn)))
{ {
message = std::format( stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
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);
} }
else 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()); stackTrace = static_cast<wchar_t*(*)()>(fn)();
message = pMessage;
// Don't free it, as the program's going to be quit anyway // Don't free it, as the program's going to be quit anyway
} }
logging::E(std::format(L"Trapped in VEH handler: {}", message)); 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;
// show in another thread to prevent messagebox from pumping messages of current thread 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)
std::thread([&]() return EXCEPTION_CONTINUE_SEARCH;
{
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();
SuspendThread(GetCurrentThread());
return EXCEPTION_CONTINUE_SEARCH; 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) if (g_veh_handle)
return false; return false;
g_veh_handle = AddVectoredExceptionHandler(1, exception_handler); g_veh_handle = AddVectoredExceptionHandler(1, exception_handler);
SetUnhandledExceptionFilter(nullptr); SetUnhandledExceptionFilter(nullptr);
g_veh_do_full_dump = doFullDump; g_veh_do_full_dump = doFullDump;
g_time_start = std::chrono::system_clock::now(); 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); std::optional<std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>> hWritePipe;
if (!file_mapping) { std::optional<std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>> hReadPipeInheritable;
std::cout << "Could not map info share file.\n"; if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536))
g_crashhandler_shared_info = nullptr; {
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 else
{ {
g_crashhandler_shared_info = (exception_info*)MapViewOfFile(file_mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(exception_info)); logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(1) error 0x{:x}", GetLastError());
if (!g_crashhandler_shared_info) { return false;
std::cout << "Could not map view of info share file.\n";
} }
} }
else
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)
{ {
std::cout << "Couldn't acquire event handle\n"; logging::W("Failed to launch DalamudCrashHandler.exe: CreatePipe error 0x{:x}", GetLastError());
return false;
} }
auto handler_path = std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe";
// additional information // additional information
STARTUPINFO si; STARTUPINFOEXW siex{};
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi{};
// set the size of the structures siex.StartupInfo.cb = sizeof siex;
ZeroMemory( &si, sizeof(si) ); siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
si.cb = sizeof(si); #ifdef NDEBUG
ZeroMemory( &pi, sizeof(pi) ); siex.StartupInfo.wShowWindow = SW_HIDE;
#else
siex.StartupInfo.wShowWindow = SW_SHOW;
#endif
CreateProcess( handler_path.c_str(), // the path // set up list of handles to inherit to child process
NULL, // Command line std::vector<char> attributeListBuf;
NULL, // Process handle not inheritable if (SIZE_T size = 0; !InitializeProcThreadAttributeList(nullptr, 1, 0, &size))
NULL, // Thread handle not inheritable {
FALSE, // Set handle inheritance to FALSE if (const auto err = GetLastError(); err != ERROR_INSUFFICIENT_BUFFER)
0, // No creation flags {
NULL, // Use parent's environment block logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(1) error 0x{:x}", err);
NULL, // Use parent's starting directory return false;
&si, // Pointer to STARTUPINFO structure }
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;
}
}
else
{
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;
}
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) &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
); ))
{
logging::W("Failed to launch DalamudCrashHandler.exe: CreateProcessW error 0x{:x}", GetLastError());
return false;
}
// Close process and thread handles. CloseHandle(pi.hThread);
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() bool veh::remove_handler()

View file

@ -2,6 +2,6 @@
namespace veh namespace veh
{ {
bool add_handler(bool doFullDump, std::string workingDirectory); bool add_handler(bool doFullDump, const std::string& workingDirectory);
bool remove_handler(); bool remove_handler();
} }

View file

@ -7,7 +7,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using Dalamud.Game; using Dalamud.Game;
using Newtonsoft.Json; using Newtonsoft.Json;
using Reloaded.Memory.Buffers; using Reloaded.Memory.Buffers;
@ -484,6 +484,7 @@ namespace Dalamud.Injector
var withoutDalamud = false; var withoutDalamud = false;
var noFixAcl = false; var noFixAcl = false;
var waitForGameWindow = true; var waitForGameWindow = true;
var encryptArguments = false;
var parsingGameArgument = false; var parsingGameArgument = false;
for (var i = 2; i < args.Count; i++) for (var i = 2; i < args.Count; i++)
@ -520,6 +521,43 @@ namespace Dalamud.Injector
throw new CommandLineException($"\"{args[i]}\" is not a command line argument."); throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
} }
var checksumTable = "fX1pGtdS5CAP4_VL";
var argDelimiterRegex = new Regex(" (?<!(?:^|[^ ])(?: )*)/");
var kvDelimiterRegex = new Regex(" (?<!(?:^|[^ ])(?: )*)=");
gameArguments = gameArguments.SelectMany(x =>
{
if (!x.StartsWith("//**sqex0003") || !x.EndsWith("**//"))
return new List<string>() { x };
var checksum = checksumTable.IndexOf(x[x.Length - 5]);
if (checksum == -1)
return new List<string>() { x };
var encData = Convert.FromBase64String(x.Substring(12, x.Length - 12 - 5).Replace('-', '+').Replace('_', '/').Replace('*', '='));
var rawData = new byte[encData.Length];
for (var i = (uint)checksum; i < 0x10000u; i += 0x10)
{
var bf = new LegacyBlowfish(Encoding.UTF8.GetBytes($"{i << 16:x08}"));
Buffer.BlockCopy(encData, 0, rawData, 0, rawData.Length);
bf.Decrypt(ref rawData);
var rawString = Encoding.UTF8.GetString(rawData).Split('\0', 2).First();
encryptArguments = true;
var args = argDelimiterRegex.Split(rawString).Skip(1).Select(y => string.Join('=', kvDelimiterRegex.Split(y, 2)).Replace(" ", " ")).ToList();
if (!args.Any())
continue;
if (!args.First().StartsWith("T="))
continue;
if (!uint.TryParse(args.First().Substring(2), out var tickCount))
continue;
if (tickCount >> 16 != i)
continue;
return args.Skip(1);
}
return new List<string>() { x };
}).ToList();
if (showHelp) if (showHelp)
{ {
ProcessHelpCommand(args, "launch"); ProcessHelpCommand(args, "launch");
@ -603,7 +641,39 @@ namespace Dalamud.Injector
}); });
} }
var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x))); string gameArgumentString;
if (encryptArguments)
{
var rawTickCount = (uint)Environment.TickCount;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
[System.Runtime.InteropServices.DllImport("c")]
static extern ulong clock_gettime_nsec_np(int clock_id);
const int CLOCK_MONOTONIC_RAW = 4;
var rawTickCountFixed = (clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000);
Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed);
rawTickCount = (uint)rawTickCountFixed;
}
var ticks = rawTickCount & 0xFFFF_FFFFu;
var key = ticks & 0xFFFF_0000u;
gameArguments.Insert(0, $"T={ticks}");
var escapeValue = (string x) => x.Replace(" ", " ");
gameArgumentString = gameArguments.Select(x => x.Split('=', 2)).Aggregate(new StringBuilder(), (whole, part) => whole.Append($" /{escapeValue(part[0])} ={escapeValue(part.Length > 1 ? part[1] : string.Empty)}")).ToString();
var bf = new LegacyBlowfish(Encoding.UTF8.GetBytes($"{key:x08}"));
var ciphertext = bf.Encrypt(Encoding.UTF8.GetBytes(gameArgumentString));
var base64Str = Convert.ToBase64String(ciphertext).Replace('+', '-').Replace('/', '_').Replace('=', '*');
var checksum = checksumTable[(int)(key >> 16) & 0xF];
gameArgumentString = $"//**sqex0003{base64Str}{checksum}**//";
}
else
{
gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
}
var process = GameStart.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, noFixAcl, (Process p) => var process = GameStart.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, noFixAcl, (Process p) =>
{ {
if (!withoutDalamud && mode == "entrypoint") if (!withoutDalamud && mode == "entrypoint")

View file

@ -0,0 +1,319 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Injector
{
internal class LegacyBlowfish
{
#region P-Array and S-Boxes
private readonly uint[] p =
{
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
};
private readonly uint[,] s =
{
{
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,
0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,
0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,
0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,
0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,
0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,
0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,
0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
},
{
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,
0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,
0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,
0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,
0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,
0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,
0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,
0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,
0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,
0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,
0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,
0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,
0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,
0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,
0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,
0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,
0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,
0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
},
{
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,
0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,
0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,
0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,
0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,
0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,
0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,
0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,
0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,
0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,
0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,
0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,
0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,
0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,
0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,
0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,
0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,
0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,
0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,
0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,
0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,
0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
},
{
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,
0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,
0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,
0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,
0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,
0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,
0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,
0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
}
};
#endregion
private static readonly int Rounds = 16;
/// <summary>
/// Initialize a new blowfish.
/// </summary>
/// <param name="key">The key to use.</param>
/// <param name="fucked">Whether or not a sign confusion should be introduced during key init. This is needed for SE's implementation of blowfish.</param>
public LegacyBlowfish(byte[] key)
{
foreach (var (i, keyFragment) in WrappingUInt32(key, this.p.Length))
this.p[i] ^= keyFragment;
uint l = 0, r = 0;
for (int i = 0; i < this.p.Length; i += 2)
(l, r) = (this.p[i], this.p[i + 1]) = Encrypt(l, r);
for (int i = 0; i < this.s.GetLength(0); i++)
for (int j = 0; j < this.s.GetLength(1); j += 2)
(l, r) = (this.s[i, j], this.s[i, j + 1]) = Encrypt(l, r);
}
public byte[] Encrypt(byte[] data)
{
var paddedLength = data.Length % 8 == 0 ? data.Length : data.Length + (8 - (data.Length % 8));
var buffer = new byte[paddedLength];
Buffer.BlockCopy(data, 0, buffer, 0, data.Length);
for (int i = 0; i < paddedLength; i += 8)
{
var (l, r) = Encrypt(BitConverter.ToUInt32(buffer, i), BitConverter.ToUInt32(buffer, i + 4));
CopyUInt32IntoArray(buffer, l, i);
CopyUInt32IntoArray(buffer, r, i + 4);
}
return buffer;
}
public void Decrypt(ref byte[] data)
{
for (int i = 0; i < data.Length; i += 8)
{
var (l, r) = Decrypt(BitConverter.ToUInt32(data, i), BitConverter.ToUInt32(data, i + 4));
CopyUInt32IntoArray(data, l, i);
CopyUInt32IntoArray(data, r, i + 4);
}
}
private static void CopyUInt32IntoArray(byte[] dest, uint val, int offset)
{
dest[offset] = (byte)(val & 0xFF);
dest[offset + 1] = (byte)((val >> 8) & 0xFF);
dest[offset + 2] = (byte)((val >> 16) & 0xFF);
dest[offset + 3] = (byte)((val >> 24) & 0xFF);
}
private uint F(uint i)
{
return ((this.s[0, i >> 24]
+ this.s[1, (i >> 16) & 0xFF])
^ this.s[2, (i >> 8) & 0xFF])
+ this.s[3, i & 0xFF];
}
private (uint, uint) Encrypt(uint l, uint r)
{
for (int i = 0; i < Rounds; i += 2)
{
l ^= this.p[i];
r ^= F(l);
r ^= this.p[i + 1];
l ^= F(r);
}
return (r ^ this.p[17], l ^ this.p[16]);
}
private (uint, uint) Decrypt(uint l, uint r)
{
for (int i = Rounds; i > 0; i -= 2)
{
l ^= this.p[i + 1];
r ^= F(l);
r ^= this.p[i];
l ^= F(r);
}
return (r ^ this.p[0], l ^ this.p[1]);
}
private static IEnumerable<TSource> Cycle<TSource>(IEnumerable<TSource> source)
{
while (true)
foreach (TSource t in source)
yield return t;
}
private IEnumerable<(int, uint)> WrappingUInt32(IEnumerable<byte> source, int count)
{
var enumerator = Cycle(source).GetEnumerator();
for (int i = 0; i < count; i++)
{
var n = 0u;
for (var j = 0; j < 4 && enumerator.MoveNext(); j++)
{
n = (uint)((n << 8) | (sbyte)enumerator.Current); // NOTE(goat): THIS IS A BUG! SE's implementation wrongly uses signed numbers for this, so we need to as well.
}
yield return (i, n);
}
}
}
}

View file

@ -188,14 +188,14 @@ Global
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|Win32 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|Win32 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|Win32 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|Win32 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|x64

View file

@ -3,7 +3,6 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -41,11 +40,8 @@ namespace Dalamud
/// <summary> /// <summary>
/// A delegate used from VEH handler on exception which CoreCLR will fast fail by default. /// A delegate used from VEH handler on exception which CoreCLR will fast fail by default.
/// </summary> /// </summary>
/// <param name="dumpPath">Path to minidump file created in UTF-16.</param>
/// <param name="logPath">Path to log file to create in UTF-16.</param>
/// <param name="log">Log text in UTF-16.</param>
/// <returns>HGLOBAL for message.</returns> /// <returns>HGLOBAL for message.</returns>
public delegate IntPtr VehDelegate(IntPtr dumpPath, IntPtr logPath, IntPtr log); public delegate IntPtr VehDelegate();
/// <summary> /// <summary>
/// Initialize Dalamud. /// Initialize Dalamud.
@ -64,43 +60,19 @@ namespace Dalamud
} }
/// <summary> /// <summary>
/// Show error message along with stack trace and exit. /// Returns stack trace.
/// </summary> /// </summary>
/// <param name="dumpPath">Path to minidump file created in UTF-16.</param> /// <returns>HGlobal to wchar_t* stack trace c-string.</returns>
/// <param name="logPath">Path to log file to create in UTF-16.</param> public static IntPtr VehCallback()
/// <param name="log">Log text in UTF-16.</param>
public static IntPtr VehCallback(IntPtr dumpPath, IntPtr logPath, IntPtr log)
{ {
string stackTrace;
try try
{ {
stackTrace = Environment.StackTrace; return Marshal.StringToHGlobalUni(Environment.StackTrace);
} }
catch (Exception e) catch (Exception e)
{ {
stackTrace = "Fail: " + e.ToString(); return Marshal.StringToHGlobalUni("Fail: " + e);
} }
var msg = "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\n"
+ "Please attempt an integrity check in the XIVLauncher settings, and disabling plugins you don't need.";
try
{
File.WriteAllText(
Marshal.PtrToStringUni(logPath),
"Stack trace:\n" + stackTrace + "\n\n" + Marshal.PtrToStringUni(log));
}
catch (Exception e)
{
msg += "\n\nAdditionally, failed to write file: " + e.ToString();
}
msg = msg.Format(
Marshal.PtrToStringUni(dumpPath),
Marshal.PtrToStringUni(logPath),
stackTrace);
return Marshal.StringToHGlobalUni(msg);
} }
/// <summary> /// <summary>

View file

@ -615,6 +615,16 @@ namespace Dalamud.Interface.Internal
Service<Dalamud>.Get().Unload(); Service<Dalamud>.Get().Unload();
} }
if (ImGui.MenuItem("Restart game"))
{
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern void RaiseException(uint dwExceptionCode, uint dwExceptionFlags, uint nNumberOfArguments, IntPtr lpArguments);
RaiseException(0x12345678, 0, 0, IntPtr.Zero);
Process.GetCurrentProcess().Kill();
}
if (ImGui.MenuItem("Kill game")) if (ImGui.MenuItem("Kill game"))
{ {
Process.GetCurrentProcess().Kill(); Process.GetCurrentProcess().Kill();

View file

@ -1,171 +1,633 @@
#include <filesystem>
#include <fstream>
#include <iostream> #include <iostream>
#include <map>
#include <optional>
#include <ranges>
#include <span>
#include <sstream> #include <sstream>
#include <windows.h> #include <string>
#include <thread>
#include <vector>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <CommCtrl.h>
#include <DbgHelp.h>
#include <minidumpapiset.h> #include <minidumpapiset.h>
#include <tlhelp32.h> #include <PathCch.h>
#include <Psapi.h>
#include <shellapi.h>
#include <winhttp.h> #include <winhttp.h>
#pragma comment(lib, "comctl32.lib")
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#include "resource.h"
#include "../Dalamud.Boot/crashhandler_shared.h" #include "../Dalamud.Boot/crashhandler_shared.h"
DWORD WINAPI ExitCheckThread(LPVOID lpParam) HANDLE g_hProcess = nullptr;
bool g_bSymbolsAvailable = false;
const std::map<HMODULE, size_t>& get_remote_modules() {
static const auto data = [] {
std::map<HMODULE, size_t> data;
std::vector<HMODULE> buf(8192);
for (size_t i = 0; i < 64; i++) {
if (DWORD needed; !EnumProcessModules(g_hProcess, &buf[0], static_cast<DWORD>(std::span(buf).size_bytes()), &needed)) {
std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl;
break;
} else if (needed > std::span(buf).size_bytes()) {
buf.resize(needed / sizeof(HMODULE) + 16);
} else {
buf.resize(needed / sizeof(HMODULE));
break;
}
}
for (const auto& hModule : buf) {
IMAGE_DOS_HEADER dosh;
IMAGE_NT_HEADERS64 nth64;
if (size_t read; !ReadProcessMemory(g_hProcess, hModule, &dosh, sizeof dosh, &read) || read != sizeof dosh) {
std::cerr << std::format("Failed to read IMAGE_DOS_HEADER for module at 0x{:x}", reinterpret_cast<size_t>(hModule)) << std::endl;
continue;
}
if (size_t read; !ReadProcessMemory(g_hProcess, reinterpret_cast<const char*>(hModule) + dosh.e_lfanew, &nth64, sizeof nth64, &read) || read != sizeof nth64) {
std::cerr << std::format("Failed to read IMAGE_NT_HEADERS64 for module at 0x{:x}", reinterpret_cast<size_t>(hModule)) << std::endl;
continue;
}
data[hModule] = nth64.OptionalHeader.SizeOfImage;
}
return data;
}();
return data;
}
const std::map<HMODULE, std::filesystem::path>& get_remote_module_paths() {
static const auto data = [] {
std::map<HMODULE, std::filesystem::path> data;
std::wstring buf(PATHCCH_MAX_CCH, L'\0');
for (const auto& hModule : get_remote_modules() | std::views::keys) {
buf.resize(PATHCCH_MAX_CCH, L'\0');
buf.resize(GetModuleFileNameExW(g_hProcess, hModule, &buf[0], PATHCCH_MAX_CCH));
if (buf.empty()) {
std::cerr << std::format("Failed to get path for module at 0x{:x}: error 0x{:x}", reinterpret_cast<size_t>(hModule), GetLastError()) << std::endl;
continue;
}
data[hModule] = buf;
}
return data;
}();
return data;
}
bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::filesystem::path& module_file) {
for (const auto& [hModule, path] : get_remote_module_paths()) {
const auto nAddress = reinterpret_cast<DWORD64>(hModule);
if (address < nAddress)
continue;
const auto nAddressTo = nAddress + get_remote_modules().at(hModule);
if (nAddressTo <= address)
continue;
module_base = nAddress;
module_file = path;
return true;
}
return false;
}
bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address) {
DWORD64 module_base;
if (std::filesystem::path module_path; get_module_file_and_base(address, module_base, module_path))
return _wcsicmp(module_path.filename().c_str(), module_name) == 0;
return false;
}
bool get_sym_from_addr(const DWORD64 address, DWORD64& displacement, std::wstring& symbol_name) {
if (!g_bSymbolsAvailable)
return false;
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;
if (SymFromAddrW(g_hProcess, address, &displacement, &symbol) && symbol.Name[0]) {
symbol_name = symbol.Name;
return true;
}
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(g_hProcess, 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(HANDLE hThread, const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log) {
std::vector<EXCEPTION_RECORD> exRecs;
if (ex.ExceptionRecord) {
size_t rec_index = 0;
size_t read;
exRecs.emplace_back();
for (auto pRemoteExRec = ex.ExceptionRecord;
pRemoteExRec
&& rec_index < 64
&& ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read)
&& read >= offsetof(EXCEPTION_RECORD, ExceptionInformation)
&& read >= static_cast<size_t>(reinterpret_cast<const char*>(&exRecs.back().ExceptionInformation[exRecs.back().NumberParameters]) - reinterpret_cast<const char*>(&exRecs.back()));
rec_index++) {
log << std::format(L"\nException Info #{}\n", rec_index);
log << std::format(L"Address: {:X}\n", exRecs.back().ExceptionCode);
log << std::format(L"Flags: {:X}\n", exRecs.back().ExceptionFlags);
log << std::format(L"Address: {:X}\n", reinterpret_cast<size_t>(exRecs.back().ExceptionAddress));
if (!exRecs.back().NumberParameters)
continue;
log << L"Parameters: ";
for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i) {
if (i != 0)
log << L", ";
log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]);
}
pRemoteExRec = exRecs.back().ExceptionRecord;
exRecs.emplace_back();
}
exRecs.pop_back();
}
log << L"\nCall Stack\n{";
STACKFRAME64 sf{};
sf.AddrPC.Offset = ctx.Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrStack.Offset = ctx.Rsp;
sf.AddrStack.Mode = AddrModeFlat;
sf.AddrFrame.Offset = ctx.Rbp;
sf.AddrFrame.Mode = AddrModeFlat;
int frame_index = 0;
log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false));
const auto appendContextToLog = [&](const CONTEXT& ctxWalk) {
log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false));
};
const auto tryStackWalk = [&] {
__try {
CONTEXT ctxWalk = ctx;
do {
if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, g_hProcess, hThread, &sf, &ctxWalk, nullptr, &SymFunctionTableAccess64, &SymGetModuleBase64, nullptr))
break;
appendContextToLog(ctxWalk);
} while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset);
return true;
} __except(EXCEPTION_EXECUTE_HANDLER) {
return false;
}
};
if (!tryStackWalk())
log << L"\n Access violation while walking up the stack.";
log << L"\n}\n";
}
void print_exception_info_extended(const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log)
{ {
while (true) log << L"\nRegisters\n{";
{
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 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));
if (Process32First(snapshot, &entry) == TRUE) log << std::format(L"\n RSI:\t{}", to_address_string(ctx.Rsi));
{ log << std::format(L"\n RDI:\t{}", to_address_string(ctx.Rdi));
bool had_xiv = false; 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));
while (Process32Next(snapshot, &entry) == TRUE) log << L"\n}" << std::endl;
if(0x10000 < ctx.Rsp && ctx.Rsp < 0x7FFFFFFE0000)
{ {
// Exit if there's another crash handler log << L"\nStack\n{";
// TODO(goat): We should make this more robust and ensure that there is one per PID
if (_wcsicmp(entry.szExeFile, L"DalamudCrashHandler.exe") == 0 && DWORD64 stackData[16];
entry.th32ProcessID != GetCurrentProcessId()) size_t read;
{ ReadProcessMemory(g_hProcess, reinterpret_cast<void*>(ctx.Rsp), stackData, sizeof stackData, &read);
ExitProcess(0); for(DWORD64 i = 0; i < 16 && i * sizeof(size_t) < read; i++)
log << std::format(L"\n [RSP+{:X}]\t{}", i * 8, to_address_string(stackData[i]));
log << L"\n}\n";
}
log << L"\nModules\n{";
for (const auto& [hModule, path] : get_remote_module_paths())
log << std::format(L"\n {:08X}\t{}", reinterpret_cast<DWORD64>(hModule), path.wstring());
log << L"\n}\n";
}
std::wstring 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;
}
enum {
IdRadioRestartNormal = 101,
IdRadioRestartWithout3pPlugins,
IdRadioRestartWithoutPlugins,
IdRadioRestartWithoutDalamud,
IdButtonRestart = 201,
IdButtonHelp = IDHELP,
IdButtonExit = IDCANCEL,
};
void restart_game_using_injector(int nRadioButton, const std::vector<std::wstring>& launcherArgs)
{
std::wstring pathStr(PATHCCH_MAX_CCH, L'\0');
pathStr.resize(GetModuleFileNameExW(GetCurrentProcess(), GetModuleHandleW(nullptr), &pathStr[0], PATHCCH_MAX_CCH));
std::vector<std::wstring> args;
args.emplace_back((std::filesystem::path(pathStr).parent_path() / L"Dalamud.Injector.exe").wstring());
args.emplace_back(L"launch");
switch (nRadioButton) {
case IdRadioRestartWithout3pPlugins:
args.emplace_back(L"--no-3rd-plugin");
break;
case IdRadioRestartWithoutPlugins:
args.emplace_back(L"--no-plugin");
break;
case IdRadioRestartWithoutDalamud:
args.emplace_back(L"--without-dalamud");
break; break;
} }
args.emplace_back(L"--");
args.insert(args.end(), launcherArgs.begin(), launcherArgs.end());
if (_wcsicmp(entry.szExeFile, L"ffxiv_dx11.exe") == 0) std::wstring argstr;
{ for (const auto& arg : args) {
had_xiv = true; argstr.append(escape_shell_arg(arg));
} argstr.push_back(L' ');
} }
argstr.pop_back();
if (!had_xiv) STARTUPINFOW si{};
{ si.cb = sizeof si;
ExitProcess(0); si.dwFlags = STARTF_USESHOWWINDOW;
break; #ifndef NDEBUG
} si.wShowWindow = SW_HIDE;
} #else
si.wShowWindow = SW_SHOW;
CloseHandle(snapshot); #endif
PROCESS_INFORMATION pi{};
Sleep(1000); if (CreateProcessW(args[0].c_str(), &argstr[0], nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
MessageBoxW(nullptr, std::format(L"Failed to restart: 0x{:x}", GetLastError()).c_str(), L"Dalamud Boot", MB_ICONERROR | MB_OK);
} }
} }
int main() int main() {
{ enum crash_handler_special_exit_codes {
CreateThread( InvalidParameter = -101,
NULL, // default security attributes ProcessExitedUnknownExitCode = -102,
0, // use default stack size };
ExitCheckThread, // thread function name
NULL, // argument to thread function
0, // use default creation flags
NULL); // returns the thread identifier
auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); HANDLE hPipeRead = nullptr;
if (!file_mapping) { std::filesystem::path assetDir, logDir;
std::cout << "Could not map info share file.\n"; std::optional<std::vector<std::wstring>> launcherArgs;
return -2;
std::vector<std::wstring> args;
if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) {
for (auto i = 0; i < argc; i++)
args.emplace_back(argv[i]);
LocalFree(argv);
}
for (size_t i = 1; i < args.size(); i++) {
const auto arg = std::wstring_view(args[i]);
if (launcherArgs) {
launcherArgs->emplace_back(arg);
} else if (constexpr wchar_t pwszArgPrefix[] = L"--process-handle="; arg.starts_with(pwszArgPrefix)) {
g_hProcess = reinterpret_cast<HANDLE>(std::wcstoull(&arg[ARRAYSIZE(pwszArgPrefix) - 1], nullptr, 0));
} else if (constexpr wchar_t pwszArgPrefix[] = L"--exception-info-pipe-read-handle="; arg.starts_with(pwszArgPrefix)) {
hPipeRead = reinterpret_cast<HANDLE>(std::wcstoull(&arg[ARRAYSIZE(pwszArgPrefix) - 1], nullptr, 0));
} else if (constexpr wchar_t pwszArgPrefix[] = L"--asset-directory="; arg.starts_with(pwszArgPrefix)) {
assetDir = arg.substr(ARRAYSIZE(pwszArgPrefix) - 1);
} else if (constexpr wchar_t pwszArgPrefix[] = L"--log-directory="; arg.starts_with(pwszArgPrefix)) {
logDir = arg.substr(ARRAYSIZE(pwszArgPrefix) - 1);
} else if (arg == L"--") {
launcherArgs.emplace();
} else {
std::wcerr << L"Invalid argument: " << arg << std::endl;
return InvalidParameter;
}
} }
auto file_ptr = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, sizeof(exception_info)); if (g_hProcess == nullptr) {
if (!file_ptr) { std::wcerr << L"Target process not specified" << std::endl;
std::cout << "Could not map view of info share file.\n"; return InvalidParameter;
return -3;
} }
if (hPipeRead == nullptr) {
std::wcerr << L"Read pipe handle not specified" << std::endl;
return InvalidParameter;
}
const auto dwProcessId = GetProcessId(g_hProcess);
if (!dwProcessId){
std::wcerr << L"Target process not specified" << std::endl;
return InvalidParameter;
}
while (true) {
std::cout << "Waiting for crash...\n"; std::cout << "Waiting for crash...\n";
auto crash_event = CreateEvent( exception_info exinfo;
NULL, // default security attributes if (DWORD exsize{}; !ReadFile(hPipeRead, &exinfo, static_cast<DWORD>(sizeof exinfo), &exsize, nullptr) || exsize != sizeof exinfo) {
TRUE, // manual-reset event if (WaitForSingleObject(g_hProcess, 0) == WAIT_OBJECT_0) {
FALSE, // initial state is nonsignaled auto excode = static_cast<DWORD>(ProcessExitedUnknownExitCode);
CRASHDUMP_EVENT_NAME // object name if (!GetExitCodeProcess(g_hProcess, &excode))
); std::cerr << std::format("Process exited, but failed to read exit code; error: 0x{:x}", GetLastError()) << std::endl;
else
if (!crash_event) std::cout << std::format("Process exited with exit code {0} (0x{0:x})", excode) << std::endl;
{ break;
std::cout << "Couldn't acquire event handle\n";
return -1;
} }
auto wait_result = WaitForSingleObject(crash_event, INFINITE); const auto err = GetLastError();
std::cout << "Crash triggered, writing dump!\n"; std::cerr << std::format("Failed to read exception information; error: 0x{:x}", err) << std::endl;
std::cerr << "Terminating target process." << std::endl;
auto info_share = (exception_info*)file_ptr; TerminateProcess(g_hProcess, -1);
break;
if (!info_share->ExceptionPointers)
{
std::cout << "info_share->ExceptionPointers was nullptr\n";
return -4;
} }
MINIDUMP_EXCEPTION_INFORMATION mdmp_info; if (exinfo.ExceptionRecord.ExceptionCode == 0x12345678) {
mdmp_info.ClientPointers = true; std::cout << "Restart requested" << std::endl;
mdmp_info.ExceptionPointers = (PEXCEPTION_POINTERS)info_share->ExceptionPointers; TerminateProcess(g_hProcess, 0);
mdmp_info.ThreadId = info_share->ThreadId; restart_game_using_injector(IdRadioRestartNormal, *launcherArgs);
break;
std::cout << "Dump for " << info_share->ProcessId << std::endl;
HANDLE file = CreateFileW(info_share->DumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (!file)
{
auto hr = GetLastError();
std::cout << "Failed to open dump file: " << std::hex << hr << std::endl;
return -6;
} }
auto process = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, FALSE, info_share->ProcessId); std::cout << "Crash triggered" << std::endl;
if (!process)
{ if (g_bSymbolsAvailable) {
auto hr = GetLastError(); SymRefreshModuleList(g_hProcess);
std::cout << "Failed to open " << info_share->ProcessId << ": " << std::hex << hr << std::endl; } else if (g_bSymbolsAvailable = SymInitialize(g_hProcess, nullptr, true); g_bSymbolsAvailable) {
return -6; if (!assetDir.empty()) {
if (!SymSetSearchPathW(g_hProcess, std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring()).c_str()))
std::wcerr << std::format(L"SymSetSearchPathW error: 0x{:x}", GetLastError()) << std::endl;
}
} else {
std::wcerr << std::format(L"SymInitialize error: 0x{:x}", GetLastError()) << std::endl;
} }
auto success = MiniDumpWriteDump(process, info_share->ProcessId, file, MiniDumpWithFullMemory, &mdmp_info, NULL, NULL); std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0');
if (!success) if (exinfo.dwStackTraceLength) {
{ if (DWORD read; !ReadFile(hPipeRead, &stackTrace[0], 2 * exinfo.dwStackTraceLength, &read, nullptr)) {
auto hr = GetLastError(); std::cout << std::format("Failed to read supplied stack trace: error 0x{:x}", GetLastError()) << std::endl;
std::cout << "Failed: " << std::hex << hr << std::endl; }
} }
// TODO(goat): Technically, we should have another event or a semaphore to block xiv while dumping... SYSTEMTIME st;
GetLocalTime(&st);
const auto dumpPath = logDir.empty() ? std::filesystem::path() : logDir / std::format("dalamud_appcrash_{:04}{:02}{:02}_{:02}{:02}{:02}_{:03}_{}.dmp", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, dwProcessId);
const auto logPath = logDir.empty() ? std::filesystem::path() : logDir / std::format("dalamud_appcrash_{:04}{:02}{:02}_{:02}{:02}{:02}_{:03}_{}.log", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, dwProcessId);
std::wstring dumpError;
if (dumpPath.empty()) {
std::cout << "Skipping dump path, as log directory has not been specified" << std::endl;
} else {
MINIDUMP_EXCEPTION_INFORMATION mdmp_info{};
mdmp_info.ThreadId = GetThreadId(exinfo.hThreadHandle);
mdmp_info.ExceptionPointers = exinfo.pExceptionPointers;
mdmp_info.ClientPointers = TRUE;
CloseHandle(file); do {
CloseHandle(process); const auto hDumpFile = CreateFileW(dumpPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
CloseHandle(file_mapping); if (hDumpFile == INVALID_HANDLE_VALUE) {
std::wcerr << (dumpError = std::format(L"CreateFileW({}, GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr) error: 0x{:x}", dumpPath.wstring(), GetLastError())) << std::endl;
if (getenv("DALAMUD_NO_METRIC")) break;
return 0;
HINTERNET internet = WinHttpOpen(L"DALAMUDCRASHHANDLER", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, NULL, NULL, WINHTTP_FLAG_SECURE_DEFAULTS);
HINTERNET connect = NULL, request = NULL;
if (internet)
{
connect = WinHttpConnect(internet, L"kamori.goats.dev", INTERNET_DEFAULT_HTTPS_PORT, 0);
} }
if (connect) std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)> hDumpFilePtr(hDumpFile, &CloseHandle);
{ if (!MiniDumpWriteDump(g_hProcess, dwProcessId, hDumpFile, static_cast<MINIDUMP_TYPE>(MiniDumpWithDataSegs | MiniDumpWithModuleHeaders), &mdmp_info, nullptr, nullptr)) {
std::wstringstream url{ L"/Dalamud/Metric/ReportCrash/" }; std::wcerr << (dumpError = std::format(L"MiniDumpWriteDump(0x{:x}, {}, 0x{:x}({}), MiniDumpWithFullMemory, ..., nullptr, nullptr) error: 0x{:x}", reinterpret_cast<size_t>(g_hProcess), dwProcessId, reinterpret_cast<size_t>(hDumpFile), dumpPath.wstring(), GetLastError())) << std::endl;
url << "?lt=" << info_share->Lifetime << "&code=" << std::hex << info_share->ExceptionCode; break;
request = WinHttpOpenRequest(internet, L"GET", url.str().c_str(), NULL, NULL, NULL, 0);
} }
if (request) std::wcout << "Dump written to path: " << dumpPath << std::endl;
{ } while (false);
bool sent = WinHttpSendRequest(request, }
std::wostringstream log;
log << std::format(L"Unhandled native exception occurred at {}", to_address_string(exinfo.ContextRecord.Rip, false)) << std::endl;
log << std::format(L"Code: {:X}", exinfo.ExceptionRecord.ExceptionCode) << std::endl;
if (dumpPath.empty())
log << L"Dump skipped" << std::endl;
else if (dumpError.empty())
log << std::format(L"Dump at: {}", dumpPath.wstring()) << std::endl;
else
log << std::format(L"Dump error: {}", dumpError) << std::endl;
log << L"Time: " << std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::system_clock::now() } << std::endl;
log << L"\n" << stackTrace << std::endl;
SymRefreshModuleList(GetCurrentProcess());
print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log);
auto window_log_str = log.str();
print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log);
std::wofstream(logPath) << log.str();
std::thread submitThread;
if (!getenv("DALAMUD_NO_METRIC")) {
auto url = std::format(L"/Dalamud/Metric/ReportCrash/?lt={}&code={:x}", exinfo.nLifetime, exinfo.ExceptionRecord.ExceptionCode);
submitThread = std::thread([url = std::move(url)] {
const auto hInternet = WinHttpOpen(L"DALAMUDCRASHHANDLER", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, nullptr, nullptr, WINHTTP_FLAG_SECURE_DEFAULTS);
const auto hConnect = !hInternet ? nullptr : WinHttpConnect(hInternet, L"kamori.goats.dev", INTERNET_DEFAULT_HTTPS_PORT, 0);
const auto hRequest = !hConnect ? nullptr : WinHttpOpenRequest(hConnect, L"GET", url.c_str(), nullptr, nullptr, nullptr, 0);
const auto bSent = !hRequest ? false : WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS, WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0, 0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0); 0, 0);
if (!sent) if (!bSent)
std::cout << "Failed to send metric: " << std::hex << GetLastError() << std::endl; std::cerr << std::format("Failed to send metric: 0x{:x}", GetLastError()) << std::endl;
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hInternet) WinHttpCloseHandle(hInternet);
});
} }
if (request) WinHttpCloseHandle(request); TASKDIALOGCONFIG config = { 0 };
if (connect) WinHttpCloseHandle(connect);
if (internet) WinHttpCloseHandle(internet); const TASKDIALOG_BUTTON radios[]{
{IdRadioRestartNormal, L"Restart"},
{IdRadioRestartWithout3pPlugins, L"Restart without 3rd party plugins"},
{IdRadioRestartWithoutPlugins, L"Restart without any plugin"},
{IdRadioRestartWithoutDalamud, L"Restart without Dalamud"},
};
const TASKDIALOG_BUTTON buttons[]{
{IdButtonRestart, L"Restart\nRestart the game, optionally without plugins or Dalamud."},
{IdButtonExit, L"Exit\nExit the game."},
};
config.cbSize = sizeof(config);
config.hInstance = GetModuleHandleW(nullptr);
config.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS;
config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1);
config.pszMainInstruction = L"An error occurred";
config.pszContent = (L""
R"aa(This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool, or simply a bug in the game.)aa" "\n"
"\n"
R"aa(Try running integrity check in the XIVLauncher settings, and disabling plugins you don't need.)aa"
);
config.pButtons = buttons;
config.cButtons = ARRAYSIZE(buttons);
config.nDefaultButton = IdButtonRestart;
config.pszExpandedInformation = window_log_str.c_str();
config.pszWindowTitle = L"Dalamud Error";
config.pRadioButtons = radios;
config.cRadioButtons = ARRAYSIZE(radios);
config.nDefaultRadioButton = IdRadioRestartNormal;
config.cxWidth = 300;
config.pszFooter = (L""
R"aa(<a href="help">Help</a> | <a href="logdir">Open log directory</a> | <a href="logfile">Open log file</a> | <a href="resume">Attempt to resume</a>)aa"
);
// Can't do this, xiv stops pumping messages here
//config.hwndParent = FindWindowA("FFXIVGAME", NULL);
auto attemptResume = false;
const auto callback = [&](HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam) -> HRESULT {
switch (uNotification) {
case TDN_CREATED:
{
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
return S_OK;
}
case TDN_HYPERLINK_CLICKED:
{
const auto link = std::wstring_view(reinterpret_cast<const wchar_t*>(lParam));
if (link == L"help") {
ShellExecuteW(hwnd, nullptr, L"https://goatcorp.github.io/faq?utm_source=vectored", nullptr, nullptr, SW_SHOW);
} else if (link == L"logdir") {
ShellExecuteW(hwnd, nullptr, L"explorer.exe", escape_shell_arg(std::format(L"/select,{}", logPath.wstring())).c_str(), nullptr, SW_SHOW);
} else if (link == L"logfile") {
ShellExecuteW(hwnd, nullptr, logPath.c_str(), nullptr, nullptr, SW_SHOW);
} else if (link == L"resume") {
attemptResume = true;
DestroyWindow(hwnd);
}
return S_OK;
}
}
return S_OK;
};
config.pfCallback = [](HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR dwRefData) {
return (*reinterpret_cast<decltype(callback)*>(dwRefData))(hwnd, uNotification, wParam, lParam);
};
config.lpCallbackData = reinterpret_cast<LONG_PTR>(&callback);
if (submitThread.joinable()) {
submitThread.join();
submitThread = {};
}
int nButtonPressed = 0, nRadioButton = 0;
if (FAILED(TaskDialogIndirect(&config, &nButtonPressed, &nRadioButton, nullptr))) {
ResumeThread(exinfo.hThreadHandle);
} else {
switch (nButtonPressed) {
case IdButtonRestart:
{
TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode);
restart_game_using_injector(nRadioButton, *launcherArgs);
break;
}
default:
if (attemptResume)
ResumeThread(exinfo.hThreadHandle);
else
TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode);
}
}
}
return 0; return 0;
} }

View file

@ -1,18 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{317a264c-920b-44a1-8a34-f3a6827b0705}</ProjectGuid>
<RootNamespace>DalamudCrashHandler</RootNamespace>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Platform>x64</Platform>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations"> <ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64"> <ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<Platform>x64</Platform> <Platform>x64</Platform>
</ProjectConfiguration> </ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64"> <ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
<Platform>x64</Platform> <Platform>x64</Platform>
@ -21,118 +19,60 @@
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion> <VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword> <Keyword>Win32Proj</Keyword>
<ProjectGuid>{317a264c-920b-44a1-8a34-f3a6827b0705}</ProjectGuid>
<RootNamespace>DalamudCrashHandler</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<OutDir>..\bin\$(Configuration)\</OutDir>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<LinkIncremental>false</LinkIncremental>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> <OutDir>..\bin\$(Configuration)\</OutDir>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <IntDir>obj\$(Configuration)\</IntDir>
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
</ImportGroup> <UseDebugLibraries>true</UseDebugLibraries>
<ImportGroup Label="Shared"> </PropertyGroup>
</ImportGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <UseDebugLibraries>false</UseDebugLibraries>
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </PropertyGroup>
</ImportGroup> <ItemDefinitionGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem> <SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <FunctionLevelLinking>true</FunctionLevelLinking>
<SDLCheck>true</SDLCheck> <IntrinsicFunctions>false</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem> <EnableCOMDATFolding>false</EnableCOMDATFolding>
<GenerateDebugInformation>true</GenerateDebugInformation> <OptimizeReferences>false</OptimizeReferences>
<AdditionalDependencies>Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>