diff --git a/Dalamud.Boot/Dalamud.Boot.rc b/Dalamud.Boot/Dalamud.Boot.rc new file mode 100644 index 000000000..daa41a282 --- /dev/null +++ b/Dalamud.Boot/Dalamud.Boot.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/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index e71750f47..3733fe388 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -165,14 +165,21 @@ + + + + + + + - \ No newline at end of file + diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters index b527ec60c..27483eeed 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters @@ -138,5 +138,12 @@ MinHook + - \ No newline at end of file + + + + + + + diff --git a/Dalamud.Boot/dalamud.ico b/Dalamud.Boot/dalamud.ico new file mode 100644 index 000000000..1cd63765d Binary files /dev/null and b/Dalamud.Boot/dalamud.ico differ diff --git a/Dalamud.Boot/resource.h b/Dalamud.Boot/resource.h new file mode 100644 index 000000000..51acf37df --- /dev/null +++ b/Dalamud.Boot/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Dalamud.Boot.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/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 887a189bb..6979b2564 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -1,10 +1,26 @@ #include "pch.h" +#include "resource.h" + #include "veh.h" +#include + #include "logging.h" #include "utils.h" +#pragma comment(lib, "comctl32.lib") + +#if defined _M_IX86 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_IA64 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_X64 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#else +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") +#endif + PVOID g_veh_handle = nullptr; bool g_veh_do_full_dump = false; @@ -69,11 +85,11 @@ bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std:: } -bool is_ffxiv_address(const DWORD64 address) +bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address) { DWORD64 module_base; if (std::filesystem::path module_path; get_module_file_and_base(address, module_base, module_path)) - return _wcsicmp(module_path.filename().c_str(), L"ffxiv_dx11.exe") == 0; + return _wcsicmp(module_path.filename().c_str(), module_name) == 0; return false; } @@ -118,53 +134,9 @@ std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = return value != 0 ? std::format(L"{} [{}]", addr_str, to_address_string(value, false)) : addr_str; } - -void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log) +void print_exception_info_extended(const EXCEPTION_POINTERS* ex, std::wostringstream& log) { - size_t rec_index = 0; - for (auto rec = ex->ExceptionRecord; rec; rec = rec->ExceptionRecord) - { - log << std::format(L"\nException Info #{}\n", ++rec_index); - log << std::format(L"Address: {:X}\n", rec->ExceptionCode); - log << std::format(L"Flags: {:X}\n", rec->ExceptionFlags); - log << std::format(L"Address: {:X}\n", reinterpret_cast(rec->ExceptionAddress)); - if (!rec->NumberParameters) - continue; - log << L"Parameters: "; - for (DWORD i = 0; i < rec->NumberParameters; ++i) - { - if (i != 0) - log << L", "; - log << std::format(L"{:X}", rec->ExceptionInformation[i]); - } - } - - log << L"\nCall Stack\n{"; - - STACKFRAME64 sf; - sf.AddrPC.Offset = ex->ContextRecord->Rip; - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrStack.Offset = ex->ContextRecord->Rsp; - sf.AddrStack.Mode = AddrModeFlat; - sf.AddrFrame.Offset = ex->ContextRecord->Rbp; - sf.AddrFrame.Mode = AddrModeFlat; CONTEXT ctx = *ex->ContextRecord; - int frame_index = 0; - - log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); - - do - { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) - break; - - log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); - - } while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset); - - log << L"\n}\n"; - - ctx = *ex->ContextRecord; log << L"\nRegisters\n{"; @@ -219,6 +191,70 @@ void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log log << L"\n}\n"; } +void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log) +{ + size_t rec_index = 0; + for (auto rec = ex->ExceptionRecord; rec; rec = rec->ExceptionRecord) + { + log << std::format(L"\nException Info #{}\n", ++rec_index); + log << std::format(L"Address: {:X}\n", rec->ExceptionCode); + log << std::format(L"Flags: {:X}\n", rec->ExceptionFlags); + log << std::format(L"Address: {:X}\n", reinterpret_cast(rec->ExceptionAddress)); + if (!rec->NumberParameters) + continue; + log << L"Parameters: "; + for (DWORD i = 0; i < rec->NumberParameters; ++i) + { + if (i != 0) + log << L", "; + log << std::format(L"{:X}", rec->ExceptionInformation[i]); + } + } + + log << L"\nCall Stack\n{"; + + STACKFRAME64 sf; + sf.AddrPC.Offset = ex->ContextRecord->Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = ex->ContextRecord->Rsp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = ex->ContextRecord->Rbp; + sf.AddrFrame.Mode = AddrModeFlat; + CONTEXT ctx = *ex->ContextRecord; + int frame_index = 0; + + log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); + + do + { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) + break; + + log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); + + } while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset); + + log << L"\n}\n"; +} + +HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd, + UINT uNotification, + WPARAM wParam, + LPARAM lParam, + LONG_PTR dwRefData) +{ + HRESULT hr = S_OK; + + switch (uNotification) + { + case TDN_CREATED: + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + break; + } + + return hr; +} + LONG exception_handler(EXCEPTION_POINTERS* ex) { static std::recursive_mutex s_exception_handler_mutex; @@ -226,7 +262,8 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode)) return EXCEPTION_CONTINUE_SEARCH; - if (!is_ffxiv_address(ex->ContextRecord->Rip)) + if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && + !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) return EXCEPTION_CONTINUE_SEARCH; // block any other exceptions hitting the veh while the messagebox is open @@ -248,6 +285,8 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) SymRefreshModuleList(GetCurrentProcess()); print_exception_info(ex, log); + auto window_log_str = log.str(); + print_exception_info_extended(ex, log); MINIDUMP_EXCEPTION_INFORMATION ex_info; ex_info.ClientPointers = false; @@ -257,9 +296,9 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) 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); + //MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr); CloseHandle(file); std::wstring message; @@ -289,7 +328,58 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) logging::E(std::format(L"Trapped in VEH handler: {}", message)); // show in another thread to prevent messagebox from pumping messages of current thread - std::thread([&]() { MessageBoxW(nullptr, message.c_str(), L"Dalamud Error", MB_ICONERROR | MB_TOPMOST | MB_OK); }).join(); + std::thread([&]() + { + int nButtonPressed = 0; + TASKDIALOGCONFIG config = {0}; + const TASKDIALOG_BUTTON buttons[] = { + {IDOK, L"Disable all plugins"}, + {IDABORT, L"Open help page"}, + }; + config.cbSize = sizeof(config); + config.hInstance = g_hModule; + config.dwCommonButtons = TDCBF_CLOSE_BUTTON; + config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1); + //config.hMainIcon = dalamud_icon; + config.pszMainInstruction = L"An error occurred"; + config.pszContent = message.c_str(); + config.pButtons = buttons; + config.cButtons = ARRAYSIZE(buttons); + config.pszExpandedInformation = window_log_str.c_str(); + config.pszWindowTitle = L"Dalamud Error"; + config.nDefaultButton = IDCLOSE; + config.cxWidth = 300; + + // Can't do this, xiv stops pumping messages here + //config.hwndParent = FindWindowA("FFXIVGAME", NULL); + + config.pfCallback = TaskDialogCallbackProc; + + TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL); + switch (nButtonPressed) + { + case IDOK: + TCHAR szPath[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, szPath))) + { + auto appdata = std::filesystem::path(szPath); + auto safemode_file_path = ( appdata / "XIVLauncher" / ".dalamud_safemode" ); + + std::ofstream ofs(safemode_file_path); + ofs << "STAY SAFE!!!"; + ofs.close(); + } + + break; + case IDABORT: + ShellExecute(0, 0, L"https://goatcorp.github.io/faq/", 0, 0 , SW_SHOW ); + break; + case IDCANCEL: + break; + default: + break; + } + }).join(); return EXCEPTION_CONTINUE_SEARCH; } diff --git a/Dalamud.Boot/xivfixes.cpp b/Dalamud.Boot/xivfixes.cpp index 13315669c..338938fc1 100644 --- a/Dalamud.Boot/xivfixes.cpp +++ b/Dalamud.Boot/xivfixes.cpp @@ -497,6 +497,7 @@ void xivfixes::clr_failfast_hijack(bool bApply) { static const char* LogTag = "[xivfixes:clr_failfast_hijack]"; static std::optional> s_HookClrFatalError; + static std::optional> s_HookSetUnhandledExceptionFilter; if (bApply) { @@ -506,7 +507,8 @@ void xivfixes::clr_failfast_hijack(bool bApply) } s_HookClrFatalError.emplace("kernel32.dll!RaiseFailFastException (import, backup_userdata_save)", "kernel32.dll", "RaiseFailFastException", 0); - + s_HookSetUnhandledExceptionFilter.emplace("kernel32.dll!SetUnhandledExceptionFilter (lpTopLevelExceptionFilter)", "kernel32.dll", "SetUnhandledExceptionFilter", 0); + s_HookClrFatalError->set_detour([](PEXCEPTION_RECORD pExceptionRecord, _In_opt_ PCONTEXT pContextRecord, _In_ DWORD dwFlags) @@ -516,6 +518,12 @@ void xivfixes::clr_failfast_hijack(bool bApply) return s_HookClrFatalError->call_original(pExceptionRecord, pContextRecord, dwFlags); }); + s_HookSetUnhandledExceptionFilter->set_detour([](LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) -> LPTOP_LEVEL_EXCEPTION_FILTER + { + logging::I("{} SetUnhandledExceptionFilter", LogTag); + return nullptr; + }); + logging::I("{} Enable", LogTag); } else @@ -523,6 +531,7 @@ void xivfixes::clr_failfast_hijack(bool bApply) if (s_HookClrFatalError) { logging::I("{} Disable ClrFatalError", LogTag); s_HookClrFatalError.reset(); + s_HookSetUnhandledExceptionFilter.reset(); } } } diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 7bd4cdbe9..88a240c65 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -322,7 +322,8 @@ namespace Dalamud.Injector startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox3") ? 4 : 0; - startInfo.BootVehEnabled = args.Contains("--veh"); + // startInfo.BootVehEnabled = args.Contains("--veh"); + startInfo.BootVehEnabled = true; startInfo.BootVehFull = args.Contains("--veh-full"); startInfo.NoLoadPlugins = args.Contains("--no-plugin"); startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-third-plugin"); diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index f0b5b884d..cfc934f36 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -81,12 +81,8 @@ namespace Dalamud stackTrace = "Fail: " + e.ToString(); } - var msg = "An error within the game has occurred.\n\n" - + "This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n" - + "Please try \"Start Over\" or \"Download Index Backup\" in TexTools, an integrity check in the XIVLauncher settings, and disabling plugins you don't need.\n\n" - + "The log file is located at:\n" - + "{1}\n\n" - + "Press OK to exit the application.\n\nStack trace:\n{2}"; + var msg = "This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n\n" + + "Please attempt an integrity check in the XIVLauncher settings, and disabling plugins you don't need."; try { diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 8f8a2a963..fc98709ee 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -69,6 +69,17 @@ internal partial class PluginManager : IDisposable, IServiceType this.devPluginDirectory.Create(); this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || this.configuration.PluginSafeMode || this.startInfo.NoLoadPlugins; + + try + { + var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + this.SafeMode = this.SafeMode || File.Exists(Path.Combine(appdata, "XIVLauncher", ".dalamud_safemode")); + } + catch (Exception ex) + { + Log.Error(ex, "Couldn't check safe mode file"); + } + if (this.SafeMode) { this.configuration.PluginSafeMode = false;