diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj
index 95eb9d12c..957bc4c10 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj
@@ -85,6 +85,7 @@
+
@@ -94,6 +95,7 @@
+
@@ -103,4 +105,4 @@
-
+
\ No newline at end of file
diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
index afcc6e502..e357b90ea 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
@@ -27,6 +27,9 @@
Source Files
+
+ Source Files
+
@@ -50,6 +53,9 @@
Header Files
+
+ Header Files
+
diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp
index b9c27ff16..62673173e 100644
--- a/Dalamud.Boot/dllmain.cpp
+++ b/Dalamud.Boot/dllmain.cpp
@@ -5,6 +5,7 @@
#include
#include "..\lib\CoreCLR\CoreCLR.h"
#include "..\lib\CoreCLR\boot.h"
+#include "veh.h"
HMODULE g_hModule;
@@ -44,6 +45,12 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam)
entrypoint_fn(lpParam);
printf("Done!\n");
+ // ============================== VEH ======================================== //
+
+ if (veh::add_handler())
+ printf("VEH Installed\n");
+ else printf("Failed to Install VEH\n");
+
// =========================================================================== //
#ifndef NDEBUG
@@ -65,6 +72,8 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese
g_hModule = hModule;
break;
case DLL_PROCESS_DETACH:
+ // remove the VEH on unload
+ veh::remove_handler();
break;
}
return TRUE;
diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp
new file mode 100644
index 000000000..273ac001f
--- /dev/null
+++ b/Dalamud.Boot/veh.cpp
@@ -0,0 +1,163 @@
+#define WIN32_LEAN_AND_MEAN
+#include "veh.h"
+#include
+#include
+#include
+#include
+
+std::vector g_exception_whitelist({
+ STATUS_ACCESS_VIOLATION,
+ STATUS_IN_PAGE_ERROR,
+ STATUS_INVALID_HANDLE,
+ STATUS_INVALID_PARAMETER,
+ STATUS_NO_MEMORY,
+ STATUS_ILLEGAL_INSTRUCTION,
+ STATUS_NONCONTINUABLE_EXCEPTION,
+ STATUS_INVALID_DISPOSITION,
+ STATUS_ARRAY_BOUNDS_EXCEEDED,
+ STATUS_FLOAT_DENORMAL_OPERAND,
+ STATUS_FLOAT_DIVIDE_BY_ZERO,
+ STATUS_FLOAT_INEXACT_RESULT,
+ STATUS_FLOAT_INVALID_OPERATION,
+ STATUS_FLOAT_OVERFLOW,
+ STATUS_FLOAT_STACK_CHECK,
+ STATUS_FLOAT_UNDERFLOW,
+ STATUS_INTEGER_DIVIDE_BY_ZERO,
+ STATUS_INTEGER_OVERFLOW,
+ STATUS_PRIVILEGED_INSTRUCTION,
+ STATUS_STACK_OVERFLOW,
+ STATUS_DLL_NOT_FOUND,
+ STATUS_ORDINAL_NOT_FOUND,
+ STATUS_ENTRYPOINT_NOT_FOUND,
+ STATUS_DLL_INIT_FAILED,
+ STATUS_CONTROL_STACK_VIOLATION,
+ STATUS_FLOAT_MULTIPLE_FAULTS,
+ STATUS_FLOAT_MULTIPLE_TRAPS,
+ STATUS_HEAP_CORRUPTION,
+ STATUS_STACK_BUFFER_OVERRUN,
+ STATUS_INVALID_CRUNTIME_PARAMETER,
+ STATUS_THREAD_NOT_RUNNING,
+ STATUS_ALREADY_REGISTERED
+ });
+
+bool GetModuleFileAndBase(DWORD64 address, PDWORD64 moduleBase, std::filesystem::path& moduleFile)
+{
+ HMODULE handle;
+ if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(address), &handle))
+ {
+ if (wchar_t path[1024]; GetModuleFileNameW(handle, path, sizeof path / 2) > 0)
+ {
+ *moduleBase = reinterpret_cast(handle);
+ moduleFile = path;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GetCallStack(PEXCEPTION_POINTERS ex, std::vector& addressList, DWORD frames = 10)
+{
+ 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;
+ addressList.clear();
+ do
+ {
+ if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, nullptr, nullptr, nullptr))
+ return false;
+ addressList.push_back(sf.AddrPC.Offset);
+ } while (sf.AddrReturn.Offset != 0 && --frames);
+ return true;
+}
+
+LONG ExceptionHandler(PEXCEPTION_POINTERS ex)
+{
+ // return if the exception is not in the whitelist
+ if (std::ranges::find(g_exception_whitelist, ex->ExceptionRecord->ExceptionCode) == g_exception_whitelist.end())
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ DWORD64 module_base;
+ std::filesystem::path module_path;
+
+ // return if the exception did not happen in ffxiv_dx11.exe
+ if (!GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path) || module_path.filename() != L"ffxiv_dx11.exe")
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ GetModuleFileAndBase(reinterpret_cast(&ExceptionHandler), &module_base, module_path);
+#ifndef NDEBUG
+ std::wstring dmp_path = _wcsdup(module_path.replace_filename(L"dalamud_appcrashd.dmp").wstring().c_str());
+#else
+ std::wstring dmp_path = _wcsdup(module_path.replace_filename(L"dalamud_appcrash.dmp").wstring().c_str());
+#endif
+ std::wstring log_path = _wcsdup(module_path.replace_filename(L"dalamud_appcrash.log").wstring().c_str());
+
+ std::wofstream log;
+ log.open(log_path, std::ios::app);
+
+ std::wstring time_stamp;
+
+ std::time_t t = std::time(nullptr);
+ std::tm tm{};
+ localtime_s(&tm, &t);
+
+ log << L"[" << std::put_time(&tm, L"%d/%m/%Y %H:%M:%S") << L"] Exception " << std::uppercase << std::hex << ex->ExceptionRecord->ExceptionCode << " at ";
+ if (GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path))
+ log << module_path.filename() << "+" << std::uppercase << std::hex << ex->ContextRecord->Rip - module_base << std::endl;
+ else log << std::uppercase << std::hex << ex->ContextRecord->Rip << std::endl;
+
+ if (std::vector call_stack; GetCallStack(ex, call_stack, -1))
+ {
+ log << L"Call Stack:" << std::endl;
+ for (auto& addr : call_stack)
+ {
+ if (GetModuleFileAndBase(addr, &module_base, module_path))
+ log << L" " << module_path.filename().c_str() << "+" << std::uppercase << std::hex << addr - module_base << std::endl;
+ else log << L" " << std::uppercase << std::hex << addr << std::endl;
+ }
+ }
+
+ MINIDUMP_EXCEPTION_INFORMATION ex_info;
+ ex_info.ClientPointers = true;
+ ex_info.ExceptionPointers = ex;
+ ex_info.ThreadId = GetCurrentThreadId();
+
+ auto file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpWithDataSegs, &ex_info, nullptr, nullptr);
+ CloseHandle(file);
+
+ log << "Crash Dump: " << dmp_path << std::endl;
+
+ log.close();
+
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+PVOID g_hVEH; // VEH Handle to remove the handler later in DLL_PROCESS_DETACH
+
+bool veh::add_handler()
+{
+ if (g_hVEH)
+ return false;
+ g_hVEH = AddVectoredExceptionHandler(0, ExceptionHandler);
+ return g_hVEH != nullptr;
+}
+
+bool veh::remove_handler()
+{
+ if (g_hVEH && RemoveVectoredExceptionHandler(g_hVEH) != 0)
+ {
+ g_hVEH = nullptr;
+ return true;
+ }
+ return false;
+}
+
+void* veh::get_handle()
+{
+ return g_hVEH;
+}
diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h
new file mode 100644
index 000000000..ae199d70c
--- /dev/null
+++ b/Dalamud.Boot/veh.h
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace veh
+{
+ bool add_handler();
+ bool remove_handler();
+ void* get_handle();
+}
diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs
index 89f7aae37..a94d5ebc6 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -71,8 +71,6 @@ namespace Dalamud
Log.Information(new string('-', 80));
Log.Information("Initializing a session..");
- var vehManager = new VehManager(0, OnVectoredException);
-
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
@@ -84,7 +82,6 @@ namespace Dalamud
dalamud.WaitForUnload();
dalamud.Dispose();
- vehManager.Dispose();
}
catch (Exception ex)
{
@@ -195,89 +192,7 @@ namespace Dalamud
*/
}
}
-
- private static unsafe int OnVectoredException(ref VehManager.ExceptionPointers ex)
- {
- if (!Enum.IsDefined(typeof(VehManager.ExceptionCode), ex.ExceptionRecord->ExceptionCode))
- return VehManager.ExceptionContinueSearch;
-
- var code = (VehManager.ExceptionCode)ex.ExceptionRecord->ExceptionCode;
- var address = ex.ExceptionRecord->ExceptionAddress;
- var info = $"{code}(0x{ex.ExceptionRecord->ExceptionCode:X})";
-
- var module = Process.GetCurrentProcess().Modules.Cast().FirstOrDefault(m => address >= m.BaseAddress && address < m.BaseAddress + m.ModuleMemorySize);
- if (module != null)
- {
- var rva = address - module.BaseAddress;
-
- info += $"\nat {module.ModuleName}+{rva:X}";
- }
- else
- {
- info += $"\nat {ex.ExceptionRecord->ExceptionAddress:X}";
- }
-
- const MessageBoxType flags = NativeFunctions.MessageBoxType.AbortRetryIgnore | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
- var result = MessageBoxW(
- Process.GetCurrentProcess().MainWindowHandle,
- $"An error within the game occurred and Dalamud handled it. This may indicate a malfunctioning plugin.\nThe game must close.\n\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\n" +
- "Click \"Abort\" to save further information.\n" +
- "Click \"Retry\" to disable all plugins.\n" +
- "Click \"Ignore\" to do nothing and quit.",
- "Dalamud",
- flags);
-
- switch (result)
- {
- case (int)User32.MessageBoxResult.IDRETRY:
- {
- Log.Information("User chose to disable plugins on next launch...");
- var config = Service.Get();
- config.PluginSafeMode = true;
- config.Save();
- break;
- }
-
- case (int)User32.MessageBoxResult.IDABORT:
- {
- // TODO: We can also do this for managed exceptions, but do we want to? It requires doing dumps with full memory.
- Log.Information("User chose to save minidump...");
-
- #if DEBUG
- var path = Path.Combine(Path.GetDirectoryName(typeof(EntryPoint).Assembly.Location), $"dalamud_appcrashd_{DateTimeOffset.Now.ToUnixTimeSeconds()}.dmp");
- #else
- var path = Path.Combine(Path.GetDirectoryName(typeof(EntryPoint).Assembly.Location), "..", "..", "..", $"dalamud_appcrash_{DateTimeOffset.Now.ToUnixTimeSeconds()}.dmp");
- #endif
-
- try
- {
- var file = new FileStream(path, FileMode.Create);
-
- fixed (void* pEx = &ex)
- {
- var mdmpInfo = default(MinidumpExceptionInformation);
- mdmpInfo.ClientPointers = 1;
-
- mdmpInfo.ExceptionPointers = new IntPtr(pEx);
- mdmpInfo.ThreadId = GetCurrentThreadId();
-
- MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file.SafeFileHandle.DangerousGetHandle(), (int)MiniDumpType.MiniDumpWithDataSegs, ref mdmpInfo, IntPtr.Zero, IntPtr.Zero);
- }
- }
- catch (Exception e)
- {
- Log.Error(e, "Failed to save minidump");
- }
-
- break;
- }
- }
-
- Environment.Exit(-1);
-
- return VehManager.ExceptionContinueSearch;
- }
-
+
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
switch (args.ExceptionObject)
diff --git a/Dalamud/VehManager.cs b/Dalamud/VehManager.cs
deleted file mode 100644
index b3adc99c1..000000000
--- a/Dalamud/VehManager.cs
+++ /dev/null
@@ -1,303 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Dalamud
-{
- ///
- /// Class encapsulating a vectored exception handler.
- ///
- internal unsafe class VehManager
- {
- ///
- /// Execute the exception handler.
- ///
- public const int ExceptionExecuteHandler = 1;
-
- ///
- /// Continue the search for another handler.
- ///
- public const int ExceptionContinueSearch = 0;
-
- ///
- /// Continue execution after the handler.
- ///
- public const int ExceptionContinueExecution = -1;
-
- private VectoredExceptionHandler? myHandler; // to keep a reference, just in case, idk if it's needed
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Position of this VEH. 0 = Last, 1 = First.
- /// The handler to register.
- public VehManager(uint first, VectoredExceptionHandler myHandler)
- {
- this.myHandler = myHandler;
- this.Handle = AddVectoredExceptionHandler(first, this.myHandler);
- }
-
- ///
- /// VEH Delegate.
- ///
- /// Exception information.
- /// Code that determines which action to take.
- public delegate int VectoredExceptionHandler(ref ExceptionPointers ex);
-
- ///
- /// Gets the handle to the VEH.
- ///
- public nint Handle { get; private set; }
-
- ///
- /// Dispose and remove this VEH.
- ///
- public void Dispose()
- {
- if (this.Handle == 0)
- return;
-
- if (!RemoveVectoredExceptionHandler(this.Handle))
- return;
-
- this.Handle = 0;
- this.myHandler = null;
- }
-
- #region DllImports
-
- [DllImport("kernel32", SetLastError = true)]
- private static extern nint AddVectoredExceptionHandler(uint first, VectoredExceptionHandler handler);
-
- [DllImport("kernel32", SetLastError = true)]
- private static extern bool RemoveVectoredExceptionHandler(nint handle);
-
- #endregion
-
-#pragma warning disable SA1600
-#pragma warning disable SA1602
-#pragma warning disable SA1201
-
- #region Enums
-
- public enum ExceptionCode : uint
- {
- AccessViolation = 0xC0000005,
- InPageError = 0xC0000006,
- InvalidHandle = 0xC0000008,
- InvalidParameter = 0xC000000D,
- NoMemory = 0xC0000017,
- IllegalInstruction = 0xC000001D,
- NoncontinuableException = 0xC0000025,
- InvalidDisposition = 0xC0000026,
- ArrayBoundsExceeded = 0xC000008C,
- FloatDenormalOperand = 0xC000008D,
- FloatDivideByZero = 0xC000008E,
- FloatInexactResult = 0xC000008F,
- FloatInvalidOperation = 0xC0000090,
- FloatOverflow = 0xC0000091,
- FloatStackCheck = 0xC0000092,
- FloatUnderflow = 0xC0000093,
- IntegerDivideByZero = 0xC0000094,
- IntegerOverflow = 0xC0000095,
- PrivilegedInstruction = 0xC0000096,
- StackOverflow = 0xC00000FD,
- DllNotFound = 0xC0000135,
- OrdinalNotFound = 0xC0000138,
- EntrypointNotFound = 0xC0000139,
- ControlCExit = 0xC000013A,
- DllInitFailed = 0xC0000142,
- ControlStackViolation = 0xC00001B2,
- FloatMultipleFaults = 0xC00002B4,
- FloatMultipleTraps = 0xC00002B5,
- RegNatConsumption = 0xC00002C9,
- HeapCorruption = 0xC0000374,
- StackBufferOverrun = 0xC0000409,
- InvalidCruntimeParameter = 0xC0000417,
- AssertionFailure = 0xC0000420,
- EnclaveViolation = 0xC00004A2,
- Interrupted = 0xC0000515,
- ThreadNotRunning = 0xC0000516,
- AlreadyRegistered = 0xC0000718,
- }
-
- #endregion
-
- #region Structures
-
- [StructLayout(LayoutKind.Sequential)]
- public struct ExceptionPointers
- {
- public ExceptionRecord64* ExceptionRecord;
- public Context* ContextRecord;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct ExceptionRecord64
- {
- public uint ExceptionCode;
- public uint ExceptionFlags;
- public nint ExceptionRecord;
- public nint ExceptionAddress;
- public uint NumberParameters;
- public uint UnusedAlignment;
- public fixed ulong ExceptionInformation[15];
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct Context
- {
- // Register parameter home addresses.
- //
- // N.B. These fields are for convience - they could be used to extend the
- // context record in the future.
-
- public nint P1Home;
- public nint P2Home;
- public nint P3Home;
- public nint P4Home;
- public nint P5Home;
- public nint P6Home;
-
- // Control flags.
-
- public uint ContextFlags;
- public uint MxCsr;
-
- // Segment Registers and processor flags.
-
- public ushort SegCs;
- public ushort SegDs;
- public ushort SegEs;
- public ushort SegFs;
- public ushort SegGs;
- public ushort SegSs;
- public uint EFlags;
-
- // Debug registers
-
- public nint Dr0;
- public nint Dr1;
- public nint Dr2;
- public nint Dr3;
- public nint Dr6;
- public nint Dr7;
-
- // Integer registers.
-
- public nint Rax;
- public nint Rcx;
- public nint Rdx;
- public nint Rbx;
- public nint Rsp;
- public nint Rbp;
- public nint Rsi;
- public nint Rdi;
- public nint R8;
- public nint R9;
- public nint R10;
- public nint R11;
- public nint R12;
- public nint R13;
- public nint R14;
- public nint R15;
-
- // Program counter.
-
- public nint Rip;
-
- // Floating point state.
-
- public XmmRegisters FloatRegisters;
-
- // Vector registers.
-
- public VectorRegisters VectorRegister;
- public nint VectorControl;
-
- // Special debug control registers.
-
- public nint DebugControl;
- public nint LastBranchToRip;
- public nint LastBranchFromRip;
- public nint LastExceptionToRip;
-
- public nint LastExceptionFromRip;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct M128A
- {
- public ulong Low;
- public long High;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct VectorRegisters
- {
- public M128A V0;
- public M128A V1;
- public M128A V2;
- public M128A V3;
- public M128A V4;
- public M128A V5;
- public M128A V6;
- public M128A V7;
- public M128A V8;
- public M128A V9;
- public M128A V10;
- public M128A V11;
- public M128A V12;
- public M128A V13;
- public M128A V14;
- public M128A V15;
- public M128A V16;
- public M128A V17;
- public M128A V18;
- public M128A V19;
- public M128A V20;
- public M128A V21;
- public M128A V22;
- public M128A V23;
- public M128A V24;
- public M128A V25;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct XmmRegisters
- {
- public readonly M128A Header0;
- public readonly M128A Header1;
-
- public M128A Float0;
- public M128A Float1;
- public M128A Float2;
- public M128A Float3;
- public M128A Float4;
- public M128A Float5;
- public M128A Float6;
- public M128A Float7;
-
- public M128A Xmm0;
- public M128A Xmm1;
- public M128A Xmm2;
- public M128A Xmm3;
- public M128A Xmm4;
- public M128A Xmm5;
- public M128A Xmm6;
- public M128A Xmm7;
- public M128A Xmm8;
- public M128A Xmm9;
- public M128A Xmm10;
- public M128A Xmm11;
- public M128A Xmm12;
- public M128A Xmm13;
- public M128A Xmm14;
- public M128A Xmm15;
- }
-
- #endregion
-
-#pragma warning restore SA1600
-#pragma warning restore SA1602
-#pragma warning restore SA1201
- }
-}