diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj
index 3733fe388..77783e269 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj
@@ -162,6 +162,7 @@
+
@@ -182,4 +183,4 @@
-
+
\ No newline at end of file
diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
index 27483eeed..8b4483684 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
@@ -139,6 +139,7 @@
MinHook
+
@@ -146,4 +147,4 @@
-
+
\ No newline at end of file
diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h
new file mode 100644
index 000000000..0c1649cc4
--- /dev/null
+++ b/Dalamud.Boot/crashhandler_shared.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "windows.h"
+
+struct exception_info
+{
+ void* ExceptionPointers; // Cannot dereference!
+ DWORD ThreadId;
+ DWORD ProcessId;
+ BOOL DoFullDump;
+ wchar_t DumpPath[1000];
+};
+
+constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare";
+constexpr wchar_t CRASHDUMP_EVENT_NAME[] = L"Global\\DalamudRequestWriteDump";
diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp
index 5a2665f09..d5660e976 100644
--- a/Dalamud.Boot/dllmain.cpp
+++ b/Dalamud.Boot/dllmain.cpp
@@ -136,7 +136,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
if (utils::is_running_on_linux()) {
logging::I("=> VEH was disabled, running on linux");
} else if (g_startInfo.BootVehEnabled) {
- if (veh::add_handler(g_startInfo.BootVehFull))
+ if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))
logging::I("=> Done!");
else
logging::I("=> Failed!");
diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp
index 1ab0e5c9b..b29c38b07 100644
--- a/Dalamud.Boot/veh.cpp
+++ b/Dalamud.Boot/veh.cpp
@@ -9,6 +9,8 @@
#include "logging.h"
#include "utils.h"
+#include "crashhandler_shared.h"
+
#pragma comment(lib, "comctl32.lib")
#if defined _M_IX86
@@ -24,6 +26,9 @@
PVOID g_veh_handle = nullptr;
bool g_veh_do_full_dump = false;
+exception_info* g_crashhandler_shared_info;
+HANDLE g_crashhandler_event;
+
bool is_whitelist_exception(const DWORD code)
{
switch (code)
@@ -292,14 +297,19 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
ex_info.ClientPointers = false;
ex_info.ExceptionPointers = ex;
ex_info.ThreadId = GetCurrentThreadId();
-
- auto miniDumpType = MiniDumpWithDataSegs;
- if (g_veh_do_full_dump)
- miniDumpType = MiniDumpWithFullMemory;
- HANDLE file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
- //MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr);
- CloseHandle(file);
+ 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;
+
+ SetEvent(g_crashhandler_event);
+ }
std::wstring message;
void* fn;
@@ -384,7 +394,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
return EXCEPTION_CONTINUE_SEARCH;
}
-bool veh::add_handler(bool doFullDump)
+bool veh::add_handler(bool doFullDump, std::string workingDirectory)
{
if (g_veh_handle)
return false;
@@ -393,6 +403,58 @@ bool veh::add_handler(bool doFullDump)
g_veh_do_full_dump = doFullDump;
+ auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME);
+ if (!file_mapping) {
+ std::cout << "Could not map info share file.\n";
+ g_crashhandler_shared_info = nullptr;
+ }
+ else
+ {
+ g_crashhandler_shared_info = (exception_info*)MapViewOfFile(file_mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(exception_info));
+ if (!g_crashhandler_shared_info) {
+ std::cout << "Could not map view of info share file.\n";
+ }
+ }
+
+ 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";
+ }
+
+ auto handler_path = std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe";
+
+ // additional information
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ // set the size of the structures
+ ZeroMemory( &si, sizeof(si) );
+ si.cb = sizeof(si);
+ ZeroMemory( &pi, sizeof(pi) );
+
+ CreateProcess( handler_path.c_str(), // the path
+ NULL, // Command line
+ NULL, // Process handle not inheritable
+ NULL, // Thread handle not inheritable
+ FALSE, // Set handle inheritance to FALSE
+ 0, // No creation flags
+ NULL, // Use parent's environment block
+ NULL, // Use parent's starting directory
+ &si, // Pointer to STARTUPINFO structure
+ &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
+ );
+
+ // Close process and thread handles.
+ CloseHandle( pi.hProcess );
+ CloseHandle( pi.hThread );
+
return g_veh_handle != nullptr;
}
diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h
index bf0c549f3..7820d6982 100644
--- a/Dalamud.Boot/veh.h
+++ b/Dalamud.Boot/veh.h
@@ -2,6 +2,6 @@
namespace veh
{
- bool add_handler(bool doFullDump);
+ bool add_handler(bool doFullDump, std::string workingDirectory);
bool remove_handler();
}
diff --git a/Dalamud.sln b/Dalamud.sln
index 546e31dfe..a9cdf123d 100644
--- a/Dalamud.sln
+++ b/Dalamud.sln
@@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.Generators", "lib\FFXIVClientStructs\FFXIVClientStructs.Generators\FFXIVClientStructs.Generators.csproj", "{05AB2F46-268B-4915-806F-DDF813E2D59D}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "DalamudCrashHandler\DalamudCrashHandler.vcxproj", "{317A264C-920B-44A1-8A34-F3A6827B0705}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -186,6 +188,18 @@ Global
{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.Build.0 = Release|Any CPU
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {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.Build.0 = Debug|x64
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|Win32
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|Win32
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64
+ {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.Build.0 = Release|x64
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|Win32
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp
new file mode 100644
index 000000000..e7bc79055
--- /dev/null
+++ b/DalamudCrashHandler/DalamudCrashHandler.cpp
@@ -0,0 +1,136 @@
+#include
+#include
+#include
+#include
+
+#include "../Dalamud.Boot/crashhandler_shared.h"
+
+DWORD WINAPI MyThreadFunction(LPVOID lpParam)
+{
+ while (true)
+ {
+ PROCESSENTRY32 entry;
+ entry.dwSize = sizeof(PROCESSENTRY32);
+
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
+
+ if (Process32First(snapshot, &entry) == TRUE)
+ {
+ bool had_xiv = false;
+
+ while (Process32Next(snapshot, &entry) == TRUE)
+ {
+ // Exit if there's another crash handler
+ // TODO(goat): We should make this more robust and ensure that there is one per PID
+ if (_wcsicmp(entry.szExeFile, L"DalamudCrashHandler.exe") == 0 &&
+ entry.th32ProcessID != GetCurrentProcessId())
+ {
+ ExitProcess(0);
+ break;
+ }
+
+ if (_wcsicmp(entry.szExeFile, L"ffxiv_dx11.exe") == 0)
+ {
+ had_xiv = true;
+ }
+ }
+
+ if (!had_xiv)
+ {
+ ExitProcess(0);
+ break;
+ }
+ }
+
+ CloseHandle(snapshot);
+
+ Sleep(1000);
+ }
+}
+
+int main()
+{
+ CreateThread(
+ NULL, // default security attributes
+ 0, // use default stack size
+ MyThreadFunction, // 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);
+ if (!file_mapping) {
+ std::cout << "Could not map info share file.\n";
+ return -2;
+ }
+
+ auto file_ptr = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, sizeof(exception_info));
+ if (!file_ptr) {
+ std::cout << "Could not map view of info share file.\n";
+ return -3;
+ }
+
+ std::cout << "Waiting for crash...\n";
+
+ auto crash_event = CreateEvent(
+ NULL, // default security attributes
+ TRUE, // manual-reset event
+ FALSE, // initial state is nonsignaled
+ CRASHDUMP_EVENT_NAME // object name
+ );
+
+ if (!crash_event)
+ {
+ std::cout << "Couldn't acquire event handle\n";
+ return -1;
+ }
+
+ auto wait_result = WaitForSingleObject(crash_event, INFINITE);
+ std::cout << "Crash triggered, writing dump!\n";
+
+ auto info_share = (exception_info*)file_ptr;
+
+ if (!info_share->ExceptionPointers)
+ {
+ std::cout << "info_share->ExceptionPointers was nullptr\n";
+ return -4;
+ }
+
+ MINIDUMP_EXCEPTION_INFORMATION mdmp_info;
+ mdmp_info.ClientPointers = true;
+ mdmp_info.ExceptionPointers = (PEXCEPTION_POINTERS)info_share->ExceptionPointers;
+ mdmp_info.ThreadId = info_share->ThreadId;
+
+
+ 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);
+ if (!process)
+ {
+ auto hr = GetLastError();
+ std::cout << "Failed to open " << info_share->ProcessId << ": " << std::hex << hr << std::endl;
+ return -6;
+ }
+
+ auto success = MiniDumpWriteDump(process, info_share->ProcessId, file, MiniDumpWithFullMemory, &mdmp_info, NULL, NULL);
+ if (!success)
+ {
+ auto hr = GetLastError();
+ std::cout << "Failed: " << std::hex << hr << std::endl;
+ }
+
+ // TODO(goat): Technically, we should have another event or a semaphore to block xiv while dumping...
+
+ CloseHandle(file);
+ CloseHandle(process);
+ CloseHandle(file_mapping);
+
+ return 0;
+}
diff --git a/DalamudCrashHandler/DalamudCrashHandler.rc b/DalamudCrashHandler/DalamudCrashHandler.rc
new file mode 100644
index 000000000..daa41a282
--- /dev/null
+++ b/DalamudCrashHandler/DalamudCrashHandler.rc
@@ -0,0 +1,71 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United Kingdom) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_ICON1 ICON "dalamud.ico"
+
+#endif // English (United Kingdom) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj
new file mode 100644
index 000000000..b56628f6a
--- /dev/null
+++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj
@@ -0,0 +1,93 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {317a264c-920b-44a1-8a34-f3a6827b0705}
+ DalamudCrashHandler
+ 10.0
+ ..\bin\$(Configuration)\
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Windows
+ true
+ Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)
+ mainCRTStartup
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters b/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters
new file mode 100644
index 000000000..890f66520
--- /dev/null
+++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters
@@ -0,0 +1,40 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Resource Files
+
+
+
+
+ Resource Files
+
+
+
\ No newline at end of file
diff --git a/DalamudCrashHandler/dalamud.ico b/DalamudCrashHandler/dalamud.ico
new file mode 100644
index 000000000..1cd63765d
Binary files /dev/null and b/DalamudCrashHandler/dalamud.ico differ
diff --git a/DalamudCrashHandler/resource.h b/DalamudCrashHandler/resource.h
new file mode 100644
index 000000000..cdeb6ea3c
--- /dev/null
+++ b/DalamudCrashHandler/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by DalamudCrashHandler.rc
+//
+#define IDI_ICON1 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs
index 01d6d3038..318af7851 100644
--- a/build/DalamudBuild.cs
+++ b/build/DalamudBuild.cs
@@ -31,6 +31,9 @@ public class DalamudBuild : NukeBuild
AbsolutePath DalamudBootProjectDir => RootDirectory / "Dalamud.Boot";
AbsolutePath DalamudBootProjectFile => DalamudBootProjectDir / "Dalamud.Boot.vcxproj";
+
+ AbsolutePath DalamudCrashHandlerProjectDir => RootDirectory / "DalamudCrashHandler";
+ AbsolutePath DalamudCrashHandlerProjectFile => DalamudBootProjectDir / "DalamudCrashHandler.vcxproj";
AbsolutePath InjectorProjectDir => RootDirectory / "Dalamud.Injector";
AbsolutePath InjectorProjectFile => InjectorProjectDir / "Dalamud.Injector.csproj";
@@ -71,6 +74,14 @@ public class DalamudBuild : NukeBuild
.SetTargetPath(DalamudBootProjectFile)
.SetConfiguration(Configuration));
});
+
+ Target CompileDalamudCrashHandler => _ => _
+ .Executes(() =>
+ {
+ MSBuildTasks.MSBuild(s => s
+ .SetTargetPath(DalamudCrashHandlerProjectFile)
+ .SetConfiguration(Configuration));
+ });
Target CompileInjector => _ => _
.DependsOn(Restore)
@@ -117,6 +128,11 @@ public class DalamudBuild : NukeBuild
.SetProjectFile(DalamudBootProjectFile)
.SetConfiguration(Configuration)
.SetTargets("Clean"));
+
+ MSBuildTasks.MSBuild(s => s
+ .SetProjectFile(DalamudCrashHandlerProjectFile)
+ .SetConfiguration(Configuration)
+ .SetTargets("Clean"));
DotNetTasks.DotNetClean(s => s
.SetProject(InjectorProjectFile)