Merge pull request #1010 from Soreepeong/feature/tspack-from-crash-handler

This commit is contained in:
goat 2022-09-14 18:16:27 +02:00 committed by GitHub
commit c47ff6fe76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 9371 additions and 9 deletions

View file

@ -80,6 +80,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
config.Language = json.value("Language", config.Language); config.Language = json.value("Language", config.Language);
config.GameVersion = json.value("GameVersion", config.GameVersion); config.GameVersion = json.value("GameVersion", config.GameVersion);
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs); config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
config.BootLogPath = json.value("BootLogPath", config.BootLogPath); config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole); config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
@ -100,6 +101,8 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
for (const auto& val : *it) for (const auto& val : *it)
config.BootUnhookDlls.insert(unicode::convert<std::string>(val.get<std::string>(), &unicode::lower)); config.BootUnhookDlls.insert(unicode::convert<std::string>(val.get<std::string>(), &unicode::lower));
} }
config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow);
} }
void DalamudStartInfo::from_envvars() { void DalamudStartInfo::from_envvars() {

View file

@ -34,6 +34,7 @@ struct DalamudStartInfo {
ClientLanguage Language = ClientLanguage::English; ClientLanguage Language = ClientLanguage::English;
std::string GameVersion; std::string GameVersion;
int DelayInitializeMs = 0; int DelayInitializeMs = 0;
std::string TroubleshootingPackData;
std::string BootLogPath; std::string BootLogPath;
bool BootShowConsole = false; bool BootShowConsole = false;
@ -47,6 +48,8 @@ struct DalamudStartInfo {
std::set<std::string> BootEnabledGameFixes{}; std::set<std::string> BootEnabledGameFixes{};
std::set<std::string> BootUnhookDlls{}; std::set<std::string> BootUnhookDlls{};
bool CrashHandlerShow = false;
friend void from_json(const nlohmann::json&, DalamudStartInfo&); friend void from_json(const nlohmann::json&, DalamudStartInfo&);
void from_envvars(); void from_envvars();
}; };

View file

@ -15,4 +15,5 @@ struct exception_info
uint64_t nLifetime; uint64_t nLifetime;
HANDLE hThreadHandle; HANDLE hThreadHandle;
DWORD dwStackTraceLength; DWORD dwStackTraceLength;
DWORD dwTroubleshootingPackDataLength;
}; };

View file

@ -175,12 +175,16 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
} }
exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size()); exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size());
exinfo.dwTroubleshootingPackDataLength = static_cast<DWORD>(g_startInfo.TroubleshootingPackData.size());
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written) if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
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) 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; return EXCEPTION_CONTINUE_SEARCH;
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &g_startInfo.TroubleshootingPackData[0], static_cast<DWORD>(std::span(g_startInfo.TroubleshootingPackData).size_bytes()), &written, nullptr) || std::span(g_startInfo.TroubleshootingPackData).size_bytes() != written)
return EXCEPTION_CONTINUE_SEARCH;
SuspendThread(GetCurrentThread()); SuspendThread(GetCurrentThread());
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
@ -224,11 +228,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
siex.StartupInfo.cb = sizeof siex; siex.StartupInfo.cb = sizeof siex;
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
#ifdef NDEBUG siex.StartupInfo.wShowWindow = g_startInfo.CrashHandlerShow ? SW_SHOW : SW_HIDE;
siex.StartupInfo.wShowWindow = SW_HIDE;
#else
siex.StartupInfo.wShowWindow = SW_SHOW;
#endif
// set up list of handles to inherit to child process // set up list of handles to inherit to child process
std::vector<char> attributeListBuf; std::vector<char> attributeListBuf;
@ -309,7 +309,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
} }
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
g_crashhandler_process = pi.hProcess; g_crashhandler_process = pi.hProcess;
g_crashhandler_pipe_write = hWritePipe->release(); g_crashhandler_pipe_write = hWritePipe->release();
logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId); logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId);

View file

@ -94,6 +94,7 @@ namespace Dalamud.Injector
args.Remove("--veh-full"); args.Remove("--veh-full");
args.Remove("--no-plugin"); args.Remove("--no-plugin");
args.Remove("--no-3rd-plugin"); args.Remove("--no-3rd-plugin");
args.Remove("--crash-handler-console");
var mainCommand = args[1].ToLowerInvariant(); var mainCommand = args[1].ToLowerInvariant();
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand) if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
@ -252,6 +253,7 @@ namespace Dalamud.Injector
var assetDirectory = startInfo.AssetDirectory; var assetDirectory = startInfo.AssetDirectory;
var delayInitializeMs = startInfo.DelayInitializeMs; var delayInitializeMs = startInfo.DelayInitializeMs;
var languageStr = startInfo.Language.ToString().ToLowerInvariant(); var languageStr = startInfo.Language.ToString().ToLowerInvariant();
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
for (var i = 2; i < args.Count; i++) for (var i = 2; i < args.Count; i++)
{ {
@ -269,6 +271,8 @@ namespace Dalamud.Injector
delayInitializeMs = int.Parse(args[i][key.Length..]); delayInitializeMs = int.Parse(args[i][key.Length..]);
else if (args[i].StartsWith(key = "--dalamud-client-language=")) else if (args[i].StartsWith(key = "--dalamud-client-language="))
languageStr = args[i][key.Length..].ToLowerInvariant(); languageStr = args[i][key.Length..].ToLowerInvariant();
else if (args[i].StartsWith(key = "--dalamud-tspack-b64="))
troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..]));
else else
continue; continue;
@ -313,6 +317,7 @@ namespace Dalamud.Injector
startInfo.Language = clientLanguage; startInfo.Language = clientLanguage;
startInfo.DelayInitializeMs = delayInitializeMs; startInfo.DelayInitializeMs = delayInitializeMs;
startInfo.GameVersion = null; startInfo.GameVersion = null;
startInfo.TroubleshootingPackData = troubleshootingData;
// Set boot defaults // Set boot defaults
startInfo.BootShowConsole = args.Contains("--console"); startInfo.BootShowConsole = args.Contains("--console");
@ -329,6 +334,7 @@ namespace Dalamud.Injector
startInfo.NoLoadPlugins = args.Contains("--no-plugin"); startInfo.NoLoadPlugins = args.Contains("--no-plugin");
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-third-plugin"); startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-third-plugin");
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" }; // startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
startInfo.CrashHandlerShow = args.Contains("--crash-handler-console");
return startInfo; return startInfo;
} }
@ -364,7 +370,7 @@ namespace Dalamud.Injector
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]"); Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
Console.WriteLine("Verbose logging:\t[-v]"); Console.WriteLine("Verbose logging:\t[-v]");
Console.WriteLine("Show Console:\t[--console]"); Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
Console.WriteLine("Enable ETW:\t[--etw]"); Console.WriteLine("Enable ETW:\t[--etw]");
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full]"); Console.WriteLine("Enable VEH:\t[--veh], [--veh-full]");
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]"); Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");

View file

@ -34,6 +34,7 @@ namespace Dalamud
this.Language = other.Language; this.Language = other.Language;
this.GameVersion = other.GameVersion; this.GameVersion = other.GameVersion;
this.DelayInitializeMs = other.DelayInitializeMs; this.DelayInitializeMs = other.DelayInitializeMs;
this.TroubleshootingPackData = other.TroubleshootingPackData;
this.NoLoadPlugins = other.NoLoadPlugins; this.NoLoadPlugins = other.NoLoadPlugins;
this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins; this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins;
this.BootLogPath = other.BootLogPath; this.BootLogPath = other.BootLogPath;
@ -47,6 +48,7 @@ namespace Dalamud
this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode; this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
this.BootEnabledGameFixes = other.BootEnabledGameFixes; this.BootEnabledGameFixes = other.BootEnabledGameFixes;
this.BootUnhookDlls = other.BootUnhookDlls; this.BootUnhookDlls = other.BootUnhookDlls;
this.CrashHandlerShow = other.CrashHandlerShow;
} }
/// <summary> /// <summary>
@ -85,6 +87,11 @@ namespace Dalamud
[JsonConverter(typeof(GameVersionConverter))] [JsonConverter(typeof(GameVersionConverter))]
public GameVersion? GameVersion { get; set; } public GameVersion? GameVersion { get; set; }
/// <summary>
/// Gets or sets troubleshooting information to attach when generating a tspack file.
/// </summary>
public string TroubleshootingPackData { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value that specifies how much to wait before a new Dalamud session. /// Gets or sets a value that specifies how much to wait before a new Dalamud session.
/// </summary> /// </summary>
@ -154,5 +161,10 @@ namespace Dalamud
/// Gets or sets a list of DLLs that should be unhooked. /// Gets or sets a list of DLLs that should be unhooked.
/// </summary> /// </summary>
public List<string>? BootUnhookDlls { get; set; } public List<string>? BootUnhookDlls { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to show crash handler console window.
/// </summary>
public bool CrashHandlerShow { get; set; }
} }
} }

View file

@ -1,3 +1,5 @@
#include <array>
#include <chrono>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
@ -14,23 +16,96 @@
#define NOMINMAX #define NOMINMAX
#include <Windows.h> #include <Windows.h>
#include <comdef.h>
#include <CommCtrl.h> #include <CommCtrl.h>
#include <DbgHelp.h> #include <DbgHelp.h>
#include <minidumpapiset.h> #include <minidumpapiset.h>
#include <PathCch.h> #include <PathCch.h>
#include <Psapi.h> #include <Psapi.h>
#include <shellapi.h> #include <shellapi.h>
#include <ShlGuid.h>
#include <ShObjIdl.h>
#include <winhttp.h> #include <winhttp.h>
#pragma comment(lib, "comctl32.lib") #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='*'\"") #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
_COM_SMARTPTR_TYPEDEF(IFileOperation, __uuidof(IFileOperation));
_COM_SMARTPTR_TYPEDEF(IFileSaveDialog, __uuidof(IFileSaveDialog));
_COM_SMARTPTR_TYPEDEF(IShellItem, __uuidof(IShellItem));
_COM_SMARTPTR_TYPEDEF(IBindCtx, __uuidof(IBindCtx));
_COM_SMARTPTR_TYPEDEF(IStream, __uuidof(IStream));
static constexpr GUID Guid_IFileDialog_Tspack{ 0xfc057318, 0xad35, 0x4599, {0xa7, 0x68, 0xdd, 0xaf, 0x70, 0xbe, 0x98, 0x75} };
#include "resource.h" #include "resource.h"
#include "../Dalamud.Boot/crashhandler_shared.h" #include "../Dalamud.Boot/crashhandler_shared.h"
#include "miniz.h"
HANDLE g_hProcess = nullptr; HANDLE g_hProcess = nullptr;
bool g_bSymbolsAvailable = false; bool g_bSymbolsAvailable = false;
std::string ws_to_u8(const std::wstring& ws) {
std::string s(WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast<int>(ws.size()), nullptr, 0, nullptr, nullptr), '\0');
WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast<int>(ws.size()), s.data(), static_cast<int>(s.size()), nullptr, nullptr);
return s;
}
std::wstring u8_to_ws(const std::string& s) {
std::wstring ws(MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0), '\0');
MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), ws.data(), static_cast<int>(ws.size()));
return ws;
}
std::wstring get_window_string(HWND hWnd) {
std::wstring buf(GetWindowTextLengthW(hWnd), L'\0');
GetWindowTextW(hWnd, &buf[0], static_cast<int>(buf.size()));
return buf;
}
[[noreturn]]
void throw_hresult(HRESULT hr, const std::string& clue = {}) {
wchar_t* pwszMsg = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
hr,
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
reinterpret_cast<LPWSTR>(&pwszMsg),
0,
nullptr);
if (!pwszMsg) {
if (clue.empty())
throw std::runtime_error(std::format("Error (HRESULT=0x{:08X})", static_cast<uint32_t>(hr)));
else
throw std::runtime_error(std::format("Error at {} (HRESULT=0x{:08X})", clue, static_cast<uint32_t>(hr)));
}
std::unique_ptr<wchar_t, decltype(LocalFree)*> pszMsgFree(pwszMsg, LocalFree);
if (clue.empty())
throw std::runtime_error(std::format("Error (HRESULT=0x{:08X}): {}", static_cast<uint32_t>(hr), ws_to_u8(pwszMsg)));
else
throw std::runtime_error(std::format("Error at {} (HRESULT=0x{:08X}): {}", clue, static_cast<uint32_t>(hr), ws_to_u8(pwszMsg)));
}
[[noreturn]]
void throw_last_error(const std::string& clue = {}) {
throw_hresult(HRESULT_FROM_WIN32(GetLastError()), clue);
}
HRESULT throw_if_failed(HRESULT hr, std::initializer_list<HRESULT> acceptables = {}, const std::string& clue = {}) {
if (SUCCEEDED(hr))
return hr;
for (const auto& h : acceptables) {
if (h == hr)
return hr;
}
throw_hresult(hr, clue);
}
std::wstring describe_module(const std::filesystem::path& path) { std::wstring describe_module(const std::filesystem::path& path) {
DWORD verHandle = 0; DWORD verHandle = 0;
std::vector<uint8_t> block; std::vector<uint8_t> block;
@ -366,6 +441,160 @@ std::wstring escape_shell_arg(const std::wstring& arg) {
return res; return res;
} }
void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const std::string& crashLog, const std::string& troubleshootingPackData) {
static const char* SourceLogFiles[] = {
"output.log",
"patcher.log",
"dalamud.log",
"dalamud.injector.log",
"dalamud.boot.log",
"aria.log",
};
static constexpr auto MaxSizePerLog = 1 * 1024 * 1024;
static constexpr std::array<COMDLG_FILTERSPEC, 2> OutputFileTypeFilterSpec{{
{ L"Dalamud Troubleshooting Pack File (*.tspack)", L"*.tspack" },
{ L"All files (*.*)", L"*" },
}};
IShellItemPtr pItem;
try {
SYSTEMTIME st;
GetLocalTime(&st);
IFileSaveDialogPtr pDialog;
throw_if_failed(pDialog.CreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC_SERVER), {}, "pDialog.CreateInstance");
throw_if_failed(pDialog->SetClientGuid(Guid_IFileDialog_Tspack), {}, "pDialog->SetClientGuid");
throw_if_failed(pDialog->SetFileTypes(static_cast<UINT>(OutputFileTypeFilterSpec.size()), OutputFileTypeFilterSpec.data()), {}, "pDialog->SetFileTypes");
throw_if_failed(pDialog->SetFileTypeIndex(0), {}, "pDialog->SetFileTypeIndex");
throw_if_failed(pDialog->SetTitle(L"Export Dalamud Troubleshooting Pack"), {}, "pDialog->SetTitle");
throw_if_failed(pDialog->SetFileName(std::format(L"crash-{:04}{:02}{:02}{:02}{:02}{:02}.tspack", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond).c_str()), {}, "pDialog->SetFileName");
throw_if_failed(pDialog->SetDefaultExtension(L"tspack"), {}, "pDialog->SetDefaultExtension");
switch (throw_if_failed(pDialog->Show(hWndParent), { HRESULT_FROM_WIN32(ERROR_CANCELLED) }, "pDialog->Show")) {
case HRESULT_FROM_WIN32(ERROR_CANCELLED):
return;
}
throw_if_failed(pDialog->GetResult(&pItem), {}, "pDialog->GetResult");
IBindCtxPtr pBindCtx;
throw_if_failed(CreateBindCtx(0, &pBindCtx), {}, "CreateBindCtx");
auto options = BIND_OPTS{.cbStruct = sizeof(BIND_OPTS), .grfMode = STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE};
throw_if_failed(pBindCtx->SetBindOptions(&options), {}, "pBindCtx->SetBindOptions");
IStreamPtr pStream;
throw_if_failed(pItem->BindToHandler(pBindCtx, BHID_Stream, IID_PPV_ARGS(&pStream)), {}, "pItem->BindToHandler");
throw_if_failed(pStream->SetSize({}), {}, "pStream->SetSize");
mz_zip_archive zipa{};
zipa.m_pIO_opaque = &*pStream;
zipa.m_pRead = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t {
const auto pStream = static_cast<IStream*>(pOpaque);
throw_if_failed(pStream->Seek({ .QuadPart = static_cast<int64_t>(file_ofs) }, STREAM_SEEK_SET, nullptr), {}, "pStream->Seek");
ULONG read;
throw_if_failed(pStream->Read(pBuf, static_cast<ULONG>(n), &read), {}, "pStream->Read");
return read;
};
zipa.m_pWrite = [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n) -> size_t {
const auto pStream = static_cast<IStream*>(pOpaque);
throw_if_failed(pStream->Seek({ .QuadPart = static_cast<int64_t>(file_ofs) }, STREAM_SEEK_SET, nullptr), {}, "pStream->Seek");
ULONG written;
throw_if_failed(pStream->Write(pBuf, static_cast<ULONG>(n), &written), {}, "pStream->Write");
return written;
};
const auto mz_throw_if_failed = [&zipa](mz_bool res, const std::string& clue) {
if (!res)
throw std::runtime_error(std::format("Failed to save file at {}: mz_error={} description={}", clue, static_cast<int>(mz_zip_get_last_error(&zipa)), mz_zip_get_error_string(mz_zip_get_last_error(&zipa))));
};
mz_throw_if_failed(mz_zip_writer_init_v2(&zipa, 0, 0), "mz_zip_writer_init_v2");
mz_throw_if_failed(mz_zip_writer_add_mem(&zipa, "trouble.json", troubleshootingPackData.data(), troubleshootingPackData.size(), MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE | MZ_BEST_COMPRESSION), "mz_zip_writer_add_mem: trouble.json");
mz_throw_if_failed(mz_zip_writer_add_mem(&zipa, "crash.log", crashLog.data(), crashLog.size(), MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE | MZ_BEST_COMPRESSION), "mz_zip_writer_add_mem: crash.log");
struct HandleAndBaseOffset {
HANDLE h;
int64_t off;
};
const auto fnHandleReader = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t {
const auto& info = *reinterpret_cast<const HandleAndBaseOffset*>(pOpaque);
if (!SetFilePointerEx(info.h, { .QuadPart = static_cast<int64_t>(info.off + file_ofs) }, nullptr, SEEK_SET))
throw_last_error("fnHandleReader: SetFilePointerEx");
if (DWORD read; !ReadFile(info.h, pBuf, static_cast<DWORD>(n), &read, nullptr))
throw_last_error("fnHandleReader: ReadFile");
else
return read;
};
for (const auto& pcszLogFileName : SourceLogFiles) {
const auto logFilePath = logDir / pcszLogFileName;
if (!exists(logFilePath))
continue;
const auto hLogFile = CreateFileW(logFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
if (hLogFile == INVALID_HANDLE_VALUE)
throw_last_error(std::format("indiv. log file: CreateFileW({})", ws_to_u8(logFilePath.wstring())));
std::unique_ptr<void, decltype(&CloseHandle)> hLogFileClose(hLogFile, &CloseHandle);
LARGE_INTEGER size, baseOffset{};
if (!SetFilePointerEx(hLogFile, {}, &size, SEEK_END))
throw_last_error(std::format("indiv. log file: SetFilePointerEx({})", ws_to_u8(logFilePath.wstring())));
if (size.QuadPart > MaxSizePerLog) {
if (!SetFilePointerEx(hLogFile, {.QuadPart = -MaxSizePerLog}, &baseOffset, SEEK_END))
throw_last_error(std::format("indiv. log file: SetFilePointerEx#2({})", ws_to_u8(logFilePath.wstring())));
}
auto handleInfo = HandleAndBaseOffset{.h = hLogFile, .off = baseOffset.QuadPart};
const auto modt = std::chrono::system_clock::to_time_t(std::chrono::clock_cast<std::chrono::system_clock>(last_write_time(logFilePath)));
mz_throw_if_failed(mz_zip_writer_add_read_buf_callback(
&zipa,
pcszLogFileName,
fnHandleReader, &handleInfo, // callback info
size.QuadPart - baseOffset.QuadPart,
&modt,
nullptr, 0, // comments
MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE | MZ_BEST_COMPRESSION, // flags and compression ratio
nullptr, 0, // user extra data (local)
nullptr, 0 // user extra data (central)
), std::format("mz_zip_writer_add_read_buf_callback({})", ws_to_u8(logFilePath.wstring())));
}
mz_throw_if_failed(mz_zip_writer_finalize_archive(&zipa), "mz_zip_writer_finalize_archive");
mz_throw_if_failed(mz_zip_writer_end(&zipa), "mz_zip_writer_end");
} catch (const std::exception& e) {
MessageBoxW(hWndParent, std::format(L"Failed to save file: {}", u8_to_ws(e.what())).c_str(), get_window_string(hWndParent).c_str(), MB_OK | MB_ICONERROR);
if (pItem) {
try {
IFileOperationPtr pFileOps;
throw_if_failed(pFileOps.CreateInstance(__uuidof(FileOperation), nullptr, CLSCTX_ALL));
throw_if_failed(pFileOps->SetOperationFlags(FOF_NO_UI));
throw_if_failed(pFileOps->DeleteItem(pItem, nullptr));
throw_if_failed(pFileOps->PerformOperations());
} catch (const std::exception& e2) {
std::wcerr << std::format(L"Failed to remove temporary file: {}", u8_to_ws(e2.what())) << std::endl;
}
pItem.Release();
}
}
if (pItem) {
PWSTR pwszFileName;
if (FAILED(pItem->GetDisplayName(SIGDN_FILESYSPATH, &pwszFileName))) {
if (FAILED(pItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEEDITING, &pwszFileName))) {
MessageBoxW(hWndParent, L"The file has been saved to the specified path.", get_window_string(hWndParent).c_str(), MB_OK | MB_ICONINFORMATION);
} else {
std::unique_ptr<std::remove_pointer<PWSTR>::type, decltype(CoTaskMemFree)*> pszFileNamePtr(pwszFileName, CoTaskMemFree);
MessageBoxW(hWndParent, std::format(L"The file has been saved to: {}", pwszFileName).c_str(), get_window_string(hWndParent).c_str(), MB_OK | MB_ICONINFORMATION);
}
} else {
std::unique_ptr<std::remove_pointer<PWSTR>::type, decltype(CoTaskMemFree)*> pszFileNamePtr(pwszFileName, CoTaskMemFree);
ShellExecuteW(hWndParent, nullptr, L"explorer.exe", escape_shell_arg(std::format(L"/select,{}", pwszFileName)).c_str(), nullptr, SW_SHOW);
}
}
}
enum { enum {
IdRadioRestartNormal = 101, IdRadioRestartNormal = 101,
IdRadioRestartWithout3pPlugins, IdRadioRestartWithout3pPlugins,
@ -425,6 +654,7 @@ void restart_game_using_injector(int nRadioButton, const std::vector<std::wstrin
int main() { int main() {
enum crash_handler_special_exit_codes { enum crash_handler_special_exit_codes {
UnknownError = -99,
InvalidParameter = -101, InvalidParameter = -101,
ProcessExitedUnknownExitCode = -102, ProcessExitedUnknownExitCode = -102,
}; };
@ -527,8 +757,17 @@ int main() {
} }
} }
std::string troubleshootingPackData(exinfo.dwTroubleshootingPackDataLength, '\0');
if (exinfo.dwTroubleshootingPackDataLength) {
if (DWORD read; !ReadFile(hPipeRead, &troubleshootingPackData[0], exinfo.dwTroubleshootingPackDataLength, &read, nullptr)) {
std::cout << std::format("Failed to read troubleshooting pack data: error 0x{:x}", GetLastError()) << std::endl;
}
}
SYSTEMTIME st; SYSTEMTIME st;
GetLocalTime(&st); GetLocalTime(&st);
const auto dalamudLogPath = logDir.empty() ? std::filesystem::path() : logDir / L"Dalamud.log";
const auto dalamudBootLogPath = logDir.empty() ? std::filesystem::path() : logDir / L"Dalamud.boot.log";
const auto dumpPath = logDir.empty() ? std::filesystem::path() : logDir / std::format(L"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 dumpPath = logDir.empty() ? std::filesystem::path() : logDir / std::format(L"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(L"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); const auto logPath = logDir.empty() ? std::filesystem::path() : logDir / std::format(L"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; std::wstring dumpError;
@ -571,9 +810,8 @@ int main() {
SymRefreshModuleList(GetCurrentProcess()); SymRefreshModuleList(GetCurrentProcess());
print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log); print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log);
auto window_log_str = log.str(); const auto window_log_str = log.str();
print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log); print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log);
std::wofstream(logPath) << log.str(); std::wofstream(logPath) << log.str();
std::thread submitThread; std::thread submitThread;
@ -637,6 +875,8 @@ int main() {
config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1); config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1);
config.pszMainInstruction = L"An error occurred"; config.pszMainInstruction = L"An error occurred";
config.pszContent = (L"" config.pszContent = (L""
R"aa(Upload <a href="exporttspack">THIS FILE (click here)</a> if you want to ask in our <a href="discord">Discord server</a>.)aa" "\n"
"\n"
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" 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" "\n"
R"aa(Try running integrity check in the XIVLauncher settings, and disabling plugins you don't need.)aa" R"aa(Try running integrity check in the XIVLauncher settings, and disabling plugins you don't need.)aa"
@ -644,6 +884,8 @@ int main() {
config.pButtons = buttons; config.pButtons = buttons;
config.cButtons = ARRAYSIZE(buttons); config.cButtons = ARRAYSIZE(buttons);
config.nDefaultButton = IdButtonRestart; config.nDefaultButton = IdButtonRestart;
config.pszExpandedControlText = L"Hide stack trace";
config.pszCollapsedControlText = L"Stack trace for plugin developers";
config.pszExpandedInformation = window_log_str.c_str(); config.pszExpandedInformation = window_log_str.c_str();
config.pszWindowTitle = L"Dalamud Error"; config.pszWindowTitle = L"Dalamud Error";
config.pRadioButtons = radios; config.pRadioButtons = radios;
@ -681,6 +923,10 @@ int main() {
ShellExecuteW(hwnd, nullptr, L"explorer.exe", escape_shell_arg(std::format(L"/select,{}", logPath.wstring())).c_str(), nullptr, SW_SHOW); 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") { } else if (link == L"logfile") {
ShellExecuteW(hwnd, nullptr, logPath.c_str(), nullptr, nullptr, SW_SHOW); ShellExecuteW(hwnd, nullptr, logPath.c_str(), nullptr, nullptr, SW_SHOW);
} else if (link == L"exporttspack") {
export_tspack(hwnd, logDir, ws_to_u8(log.str()), troubleshootingPackData);
} else if (link == L"discord") {
ShellExecuteW(hwnd, nullptr, L"https://goat.place", nullptr, nullptr, SW_SHOW);
} else if (link == L"resume") { } else if (link == L"resume") {
attemptResume = true; attemptResume = true;
DestroyWindow(hwnd); DestroyWindow(hwnd);

View file

@ -79,9 +79,11 @@
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="DalamudCrashHandler.cpp" /> <ClCompile Include="DalamudCrashHandler.cpp" />
<ClCompile Include="miniz.c" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\Dalamud.Boot\crashhandler_shared.h" /> <ClInclude Include="..\Dalamud.Boot\crashhandler_shared.h" />
<ClInclude Include="miniz.h" />
<ClInclude Include="resource.h" /> <ClInclude Include="resource.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -18,6 +18,9 @@
<ClCompile Include="DalamudCrashHandler.cpp"> <ClCompile Include="DalamudCrashHandler.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="miniz.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\Dalamud.Boot\crashhandler_shared.h"> <ClInclude Include="..\Dalamud.Boot\crashhandler_shared.h">
@ -26,6 +29,9 @@
<ClInclude Include="resource.h"> <ClInclude Include="resource.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="miniz.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="DalamudCrashHandler.rc"> <ResourceCompile Include="DalamudCrashHandler.rc">

7733
DalamudCrashHandler/miniz.c Normal file

File diff suppressed because it is too large Load diff

1350
DalamudCrashHandler/miniz.h Normal file

File diff suppressed because it is too large Load diff