From 3c7dbf9f81e147e45f7b3541844e95cebcc3a5df Mon Sep 17 00:00:00 2001 From: MidoriKami Date: Fri, 5 Dec 2025 16:59:17 -0800 Subject: [PATCH 1/8] Remove AddonEventManagerAddressResolver.cs --- .../Game/Addon/Events/AddonEventManager.cs | 30 ++++++++----------- .../AddonEventManagerAddressResolver.cs | 21 ------------- 2 files changed, 12 insertions(+), 39 deletions(-) delete mode 100644 Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs index 945197e2b..980404940 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManager.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs @@ -9,7 +9,6 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Events; @@ -32,25 +31,21 @@ internal unsafe class AddonEventManager : IInternalDisposableService private readonly AddonLifecycleEventListener finalizeEventListener; - private readonly AddonEventManagerAddressResolver address; - private readonly Hook onUpdateCursor; + private readonly Hook onUpdateCursor; private readonly ConcurrentDictionary pluginEventControllers; - private AddonCursorType? cursorOverride; + private AtkCursor.CursorType? cursorOverride; [ServiceManager.ServiceConstructor] - private AddonEventManager(TargetSigScanner sigScanner) + private AddonEventManager() { - this.address = new AddonEventManagerAddressResolver(); - this.address.Setup(sigScanner); - this.pluginEventControllers = new ConcurrentDictionary(); this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController()); this.cursorOverride = null; - this.onUpdateCursor = Hook.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour); + this.onUpdateCursor = Hook.FromAddress(AtkUnitManager.Addresses.UpdateCursor.Value, this.UpdateCursorDetour); this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize); this.addonLifecycle.RegisterListener(this.finalizeEventListener); @@ -58,8 +53,6 @@ internal unsafe class AddonEventManager : IInternalDisposableService this.onUpdateCursor.Enable(); } - private delegate nint UpdateCursorDelegate(RaptureAtkModule* module); - /// void IInternalDisposableService.DisposeService() { @@ -117,7 +110,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService /// Force the game cursor to be the specified cursor. /// /// Which cursor to use. - internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor; + internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor; /// /// Un-forces the game cursor. @@ -168,7 +161,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService } } - private nint UpdateCursorDetour(RaptureAtkModule* module) + private void UpdateCursorDetour(AtkUnitManager* thisPtr) { try { @@ -176,13 +169,14 @@ internal unsafe class AddonEventManager : IInternalDisposableService if (this.cursorOverride is not null && atkStage is not null) { - var cursor = (AddonCursorType)atkStage->AtkCursor.Type; - if (cursor != this.cursorOverride) + ref var atkCursor = ref atkStage->AtkCursor; + + if (atkCursor.Type != this.cursorOverride) { - AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1); + atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1); } - return nint.Zero; + return; } } catch (Exception e) @@ -190,7 +184,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService Log.Error(e, "Exception in UpdateCursorDetour."); } - return this.onUpdateCursor!.Original(module); + this.onUpdateCursor!.Original(thisPtr); } } diff --git a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs deleted file mode 100644 index 415e1b169..000000000 --- a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Dalamud.Game.Addon.Events; - -/// -/// AddonEventManager memory address resolver. -/// -internal class AddonEventManagerAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the AtkModule UpdateCursor method. - /// - public nint UpdateCursor { get; private set; } - - /// - /// Scan for and setup any configured address pointers. - /// - /// The signature scanner to facilitate setup. - protected override void Setup64Bit(ISigScanner scanner) - { - this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS - } -} From 2e5c560ed729b7e88e6f036aa9987bca5e6f7be0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Dec 2025 12:48:34 +0000 Subject: [PATCH 2/8] Update ClientStructs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index e5dedba42..6f339d8f7 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit e5dedba42a3fea8f050ea54ac583a5874bf51c6f +Subproject commit 6f339d8f725fa6922449f7e5c584ca6b8fa2fb19 From 9c2d2b7c1dbe192599748e7320fce3c1cc972072 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 6 Dec 2025 15:07:09 +0100 Subject: [PATCH 3/8] Report CLR errors through DalamudCrashHandler/VEH by hooking ReportEventW --- Dalamud.Boot/dllmain.cpp | 41 ++++++++++++++++++++++++++++++++++++++++ Dalamud.Boot/veh.cpp | 37 ++++++++++++++++++++++++++++-------- Dalamud.Boot/veh.h | 1 + 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 80a16f89a..2af78092d 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -331,6 +331,47 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { logging::I("VEH was disabled manually"); } + // ============================== CLR Reporting =================================== // + + // This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it + // was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now. + // Ideally all of this will go away once they get to it. + static std::shared_ptr> s_hook; + s_hook = std::make_shared>("advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW"); + s_hook->set_detour([hook = s_hook.get()]( HANDLE hEventLog, + WORD wType, + WORD wCategory, + DWORD dwEventID, + PSID lpUserSid, + WORD wNumStrings, + DWORD dwDataSize, + LPCWSTR* lpStrings, + LPVOID lpRawData)->BOOL { + + // Check for CLR Error Event IDs + if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception + dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast + dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime + dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow + dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed + { + return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData); + } + + if (wNumStrings == 0 || lpStrings == nullptr) { + logging::W("ReportEventW called with no strings."); + return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData); + } + + // In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log. + const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData); + + const std::wstring error_details(lpStrings[0]); + veh::raise_external_event(error_details); + + return original_ret; + }); + // ============================== Dalamud ==================================== // if (static_cast(g_startInfo.BootWaitMessageBox) & static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)) diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index b0ec1cefa..24a846e66 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -11,6 +11,8 @@ #include "crashhandler_shared.h" #include "DalamudStartInfo.h" +#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679 + #pragma comment(lib, "comctl32.lib") #if defined _M_IX86 @@ -31,6 +33,8 @@ HANDLE g_crashhandler_process = nullptr; HANDLE g_crashhandler_event = nullptr; HANDLE g_crashhandler_pipe_write = nullptr; +wchar_t g_external_event_info[16384] = L""; + std::recursive_mutex g_exception_handler_mutex; std::chrono::time_point g_time_start; @@ -190,7 +194,11 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS); std::wstring stackTrace; - if (!g_clr) + if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT) + { + stackTrace = std::wstring(g_external_event_info); + } + else if (!g_clr) { stackTrace = L"(no CLR stack trace available)"; } @@ -251,6 +259,12 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex) LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex) { + // special case for CLR exceptions, always trigger crash handler + if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT) + { + return exception_handler(ex); + } + if (ex->ExceptionRecord->ExceptionCode == 0x12345678) { // pass @@ -268,7 +282,7 @@ LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex) if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) - return EXCEPTION_CONTINUE_SEARCH; + return EXCEPTION_CONTINUE_SEARCH; } return exception_handler(ex); @@ -297,7 +311,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536)) { hWritePipe.emplace(hWritePipeRaw, &CloseHandle); - + if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle); @@ -315,9 +329,9 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) } // additional information - STARTUPINFOEXW siex{}; + STARTUPINFOEXW siex{}; PROCESS_INFORMATION pi{}; - + siex.StartupInfo.cb = sizeof siex; siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; siex.StartupInfo.wShowWindow = g_startInfo.CrashHandlerShow ? SW_SHOW : SW_HIDE; @@ -385,7 +399,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) 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()); @@ -400,7 +414,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) 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 + nullptr, // Use parent's starting directory &siex.StartupInfo, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) )) @@ -416,7 +430,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) } CloseHandle(pi.hThread); - + g_crashhandler_process = pi.hProcess; g_crashhandler_pipe_write = hWritePipe->release(); logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId); @@ -434,3 +448,10 @@ bool veh::remove_handler() } return false; } + +void veh::raise_external_event(const std::wstring& errorMessage) +{ + const auto info_size = std::min(errorMessage.size(), std::size(g_external_event_info) - 1); + wcsncpy_s(g_external_event_info, errorMessage.c_str(), info_size); + RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr); +} diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h index 1905272ea..2a02c374e 100644 --- a/Dalamud.Boot/veh.h +++ b/Dalamud.Boot/veh.h @@ -4,4 +4,5 @@ namespace veh { bool add_handler(bool doFullDump, const std::string& workingDirectory); bool remove_handler(); + void raise_external_event(const std::wstring& info); } From e09c43b8decc55fcbc38cd61102256db0bdc4e29 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 6 Dec 2025 15:07:46 +0100 Subject: [PATCH 4/8] Fix bad exit condition when looping exception records --- DalamudCrashHandler/DalamudCrashHandler.cpp | 78 ++++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index ec7115ffd..107261541 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -119,7 +119,7 @@ std::wstring describe_module(const std::filesystem::path& path) { return std::format(L"", GetLastError()); UINT size = 0; - + std::wstring version = L"v?.?.?.?"; if (LPVOID lpBuffer; VerQueryValueW(block.data(), L"\\", &lpBuffer, &size)) { const auto& v = *static_cast(lpBuffer); @@ -176,7 +176,7 @@ const std::map& get_remote_modules() { std::vector buf(8192); for (size_t i = 0; i < 64; i++) { if (DWORD needed; !EnumProcessModules(g_hProcess, &buf[0], static_cast(std::span(buf).size_bytes()), &needed)) { - std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl; + 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); @@ -201,7 +201,7 @@ const std::map& get_remote_modules() { data[hModule] = nth64.OptionalHeader.SizeOfImage; } - + return data; }(); @@ -292,35 +292,43 @@ std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = void print_exception_info(HANDLE hThread, const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log) { std::vector exRecs; - if (ex.ExceptionRecord) { + 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(reinterpret_cast(&exRecs.back().ExceptionInformation[exRecs.back().NumberParameters]) - reinterpret_cast(&exRecs.back())); - rec_index++) { + pRemoteExRec && rec_index < 64; + rec_index++) + { + exRecs.emplace_back(); + + if (!ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read) + || read < offsetof(EXCEPTION_RECORD, ExceptionInformation) + || read < static_cast(reinterpret_cast(&exRecs.back().ExceptionInformation[exRecs. + back().NumberParameters]) - reinterpret_cast(&exRecs.back()))) + { + exRecs.pop_back(); + break; + } 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(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]); + if (exRecs.back().NumberParameters) + { + 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{"; @@ -410,7 +418,7 @@ void print_exception_info_extended(const EXCEPTION_POINTERS& ex, const CONTEXT& 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); @@ -504,7 +512,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s filePath.emplace(pFilePath); std::fstream fileStream(*filePath, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - + mz_zip_archive zipa{}; zipa.m_pIO_opaque = &fileStream; zipa.m_pRead = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t { @@ -566,7 +574,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s 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 hLogFileClose(hLogFile, &CloseHandle); LARGE_INTEGER size, baseOffset{}; @@ -695,7 +703,7 @@ int main() { // IFileSaveDialog only works on STA CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - + std::vector args; if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) { for (auto i = 0; i < argc; i++) @@ -823,14 +831,14 @@ int main() { hr = pOleWindow->GetWindow(&hwndProgressDialog); if (SUCCEEDED(hr)) { - SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0, + SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); SetForegroundWindow(hwndProgressDialog); } - + pOleWindow->Release(); } - + } else { std::cerr << "Failed to create progress window" << std::endl; @@ -852,14 +860,14 @@ int main() { https://github.com/sumatrapdfreader/sumatrapdf/blob/master/src/utils/DbgHelpDyn.cpp */ - + if (g_bSymbolsAvailable) { SymRefreshModuleList(g_hProcess); } else if(!assetDir.empty()) { auto symbol_search_path = std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring()); - + g_bSymbolsAvailable = SymInitializeW(g_hProcess, symbol_search_path.c_str(), true); std::wcout << std::format(L"Init symbols with PDB at {}", symbol_search_path) << std::endl; @@ -870,12 +878,12 @@ int main() { g_bSymbolsAvailable = SymInitializeW(g_hProcess, nullptr, true); std::cout << "Init symbols without PDB" << std::endl; } - + if (!g_bSymbolsAvailable) { std::wcerr << std::format(L"SymInitialize error: 0x{:x}", GetLastError()) << std::endl; } - if (pProgressDialog) + if (pProgressDialog) pProgressDialog->SetLine(3, L"Reading troubleshooting data", FALSE, NULL); std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0'); @@ -936,7 +944,7 @@ int main() { if (shutup) log << L"======= Crash handler was globally muted(shutdown?) =======" << std::endl; - + if (dumpPath.empty()) log << L"Dump skipped" << std::endl; else if (dumpError.empty()) @@ -1003,7 +1011,7 @@ int main() { R"aa(Help | Open log directory | Open log file)aa" ); #endif - + // Can't do this, xiv stops pumping messages here //config.hwndParent = FindWindowA("FFXIVGAME", NULL); @@ -1056,13 +1064,13 @@ int main() { return (*reinterpret_cast(dwRefData))(hwnd, uNotification, wParam, lParam); }; config.lpCallbackData = reinterpret_cast(&callback); - + if (pProgressDialog) { pProgressDialog->StopProgressDialog(); pProgressDialog->Release(); pProgressDialog = NULL; } - + const auto kill_game = [&] { TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); }; if (shutup) { From 446c7e38771a943ac7b2dd9d5b48478f95d66250 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 6 Dec 2025 15:14:13 +0100 Subject: [PATCH 5/8] Some logging, cleanup --- Dalamud.Boot/dllmain.cpp | 26 +++++++++++++++----------- Dalamud.Boot/veh.cpp | 6 +++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 2af78092d..687089f82 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -336,19 +336,22 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { // This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it // was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now. // Ideally all of this will go away once they get to it. - static std::shared_ptr> s_hook; - s_hook = std::make_shared>("advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW"); - s_hook->set_detour([hook = s_hook.get()]( HANDLE hEventLog, - WORD wType, - WORD wCategory, - DWORD dwEventID, - PSID lpUserSid, - WORD wNumStrings, - DWORD dwDataSize, - LPCWSTR* lpStrings, - LPVOID lpRawData)->BOOL { + static std::shared_ptr> s_report_event_hook; + s_report_event_hook = std::make_shared>( + "advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW"); + s_report_event_hook->set_detour([hook = s_report_event_hook.get()]( + HANDLE hEventLog, + WORD wType, + WORD wCategory, + DWORD dwEventID, + PSID lpUserSid, + WORD wNumStrings, + DWORD dwDataSize, + LPCWSTR* lpStrings, + LPVOID lpRawData)-> BOOL { // Check for CLR Error Event IDs + // https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/eventreporter.cpp#L370 if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime @@ -371,6 +374,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { return original_ret; }); + logging::I("ReportEventW hook installed."); // ============================== Dalamud ==================================== // diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 24a846e66..25c9b5045 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -449,9 +449,9 @@ bool veh::remove_handler() return false; } -void veh::raise_external_event(const std::wstring& errorMessage) +void veh::raise_external_event(const std::wstring& info) { - const auto info_size = std::min(errorMessage.size(), std::size(g_external_event_info) - 1); - wcsncpy_s(g_external_event_info, errorMessage.c_str(), info_size); + const auto info_size = std::min(info.size(), std::size(g_external_event_info) - 1); + wcsncpy_s(g_external_event_info, info.c_str(), info_size); RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr); } From e032840ac8e7ac13cd0493a30cac4398bcd86d94 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 6 Dec 2025 18:32:03 +0100 Subject: [PATCH 6/8] Clean up crash handler window log for external events --- Dalamud.Boot/crashhandler_shared.h | 2 ++ Dalamud.Boot/veh.cpp | 2 -- DalamudCrashHandler/DalamudCrashHandler.cpp | 26 ++++++++++++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h index 8d93e4460..0308306ce 100644 --- a/Dalamud.Boot/crashhandler_shared.h +++ b/Dalamud.Boot/crashhandler_shared.h @@ -6,6 +6,8 @@ #define WIN32_LEAN_AND_MEAN #include +#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679 + struct exception_info { LPEXCEPTION_POINTERS pExceptionPointers; diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 25c9b5045..50ac9b34c 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -11,8 +11,6 @@ #include "crashhandler_shared.h" #include "DalamudStartInfo.h" -#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679 - #pragma comment(lib, "comctl32.lib") #if defined _M_IX86 diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index 107261541..1feec4b2f 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -938,9 +938,19 @@ int main() { } while (false); } + const bool is_external_event = exinfo.ExceptionRecord.ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT; + 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 (!is_external_event) + { + 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; + } + else + { + log << L"CLR error occurred" << std::endl; + } if (shutup) log << L"======= Crash handler was globally muted(shutdown?) =======" << std::endl; @@ -957,9 +967,19 @@ int main() { if (pProgressDialog) pProgressDialog->SetLine(3, L"Refreshing Module List", FALSE, NULL); + std::wstring window_log_str; + + // Cut the log here for external events, the rest is unreadable and doesn't matter since we can't get + // symbols for mixed-mode stacks yet. + if (is_external_event) + window_log_str = log.str(); + SymRefreshModuleList(GetCurrentProcess()); print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log); - const auto window_log_str = log.str(); + + if (!is_external_event) + window_log_str = log.str(); + print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log); std::wofstream(logPath) << log.str(); From ab5ea34e686e7ead9673f0fb8492ca0a00aa5a09 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:46:06 +0100 Subject: [PATCH 7/8] ci: make deploying builds globally blocking, don't cancel in-progress --- .github/workflows/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index be44afacc..ea1afcac5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,9 +1,10 @@ name: Build Dalamud on: [push, pull_request, workflow_dispatch] +# Globally blocking because of git pushes in deploy step concurrency: - group: build_dalamud_${{ github.ref_name }} - cancel-in-progress: true + group: build_dalamud_${{ github.repository_owner }} + cancel-in-progress: false jobs: build: From b313fefbf36c65099f011248480fce3ce8e0e18a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Dec 2025 18:33:02 +0000 Subject: [PATCH 8/8] Update Excel Schema --- lib/Lumina.Excel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Lumina.Excel b/lib/Lumina.Excel index 5d01489c3..19111a077 160000 --- a/lib/Lumina.Excel +++ b/lib/Lumina.Excel @@ -1 +1 @@ -Subproject commit 5d01489c34f33a3d645f49085d7fc0065a1ac801 +Subproject commit 19111a07705e6fa2d5d499662988f7e78d101b12