mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
feat: new VEH experience
This commit is contained in:
parent
379c52f5d4
commit
8bc4d362ad
10 changed files with 269 additions and 61 deletions
71
Dalamud.Boot/Dalamud.Boot.rc
Normal file
71
Dalamud.Boot/Dalamud.Boot.rc
Normal file
|
|
@ -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
|
||||
|
||||
|
|
@ -165,12 +165,19 @@
|
|||
<ClInclude Include="DalamudStartInfo.h" />
|
||||
<ClInclude Include="hooks.h" />
|
||||
<ClInclude Include="logging.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="unicode.h" />
|
||||
<ClInclude Include="utils.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="veh.h" />
|
||||
<ClInclude Include="xivfixes.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Dalamud.Boot.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="dalamud.ico" />
|
||||
</ItemGroup>
|
||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||
|
|
|
|||
|
|
@ -138,5 +138,12 @@
|
|||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\table64.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Dalamud.Boot.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="dalamud.ico" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
BIN
Dalamud.Boot/dalamud.ico
Normal file
BIN
Dalamud.Boot/dalamud.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
16
Dalamud.Boot/resource.h
Normal file
16
Dalamud.Boot/resource.h
Normal file
|
|
@ -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
|
||||
|
|
@ -1,10 +1,26 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#include "veh.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
|
||||
#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<size_t>(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<size_t>(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;
|
||||
|
|
@ -259,7 +298,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ void xivfixes::clr_failfast_hijack(bool bApply)
|
|||
{
|
||||
static const char* LogTag = "[xivfixes:clr_failfast_hijack]";
|
||||
static std::optional<hooks::import_hook<decltype(RaiseFailFastException)>> s_HookClrFatalError;
|
||||
static std::optional<hooks::import_hook<decltype(SetUnhandledExceptionFilter)>> s_HookSetUnhandledExceptionFilter;
|
||||
|
||||
if (bApply)
|
||||
{
|
||||
|
|
@ -506,6 +507,7 @@ 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,
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue