diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c0eba166a..598602be8 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -38,7 +38,7 @@ jobs:
deploy_stg:
name: Deploy dalamud-distrib staging
- if: ${{ github.event_name == 'push' }}
+ if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
needs: build
runs-on: windows-latest
steps:
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index 9be9d59cd..27cfe4d84 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -75,6 +75,7 @@
"Compile",
"CompileDalamud",
"CompileDalamudBoot",
+ "CompileDalamudCrashHandler",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
@@ -96,6 +97,7 @@
"Compile",
"CompileDalamud",
"CompileDalamudBoot",
+ "CompileDalamudCrashHandler",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
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..2e0f19aca 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj
@@ -33,10 +33,12 @@
- $(LibraryPath)
+ true
+ $(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
- $(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(LibraryPath)
+ false
+ $(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
@@ -53,7 +55,7 @@
Windows
true
false
- dbghelp.lib;Version.lib;%(AdditionalDependencies)
+ Version.lib;%(AdditionalDependencies)
..\lib\CoreCLR;%(AdditionalLibraryDirectories)
@@ -162,15 +164,23 @@
+
+
+
+
+
+
+
+
diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
index b527ec60c..8b4483684 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
@@ -138,5 +138,13 @@
MinHook
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h
index 4bd377811..ed9d5c4a3 100644
--- a/Dalamud.Boot/DalamudStartInfo.h
+++ b/Dalamud.Boot/DalamudStartInfo.h
@@ -8,6 +8,9 @@ struct DalamudStartInfo {
BeforeDalamudConstruct = 1 << 2,
};
friend void from_json(const nlohmann::json&, WaitMessageboxFlags&);
+ friend WaitMessageboxFlags operator &(WaitMessageboxFlags a, WaitMessageboxFlags b) {
+ return static_cast(static_cast(a) & static_cast(b));
+ }
enum class DotNetOpenProcessHookMode : int {
ImportHooks = 0,
diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h
new file mode 100644
index 000000000..c4bd606c7
--- /dev/null
+++ b/Dalamud.Boot/crashhandler_shared.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
+#include
+
+struct exception_info
+{
+ LPEXCEPTION_POINTERS pExceptionPointers;
+ EXCEPTION_POINTERS ExceptionPointers;
+ EXCEPTION_RECORD ExceptionRecord;
+ CONTEXT ContextRecord;
+ uint64_t nLifetime;
+ HANDLE hThreadHandle;
+ DWORD dwStackTraceLength;
+};
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/dllmain.cpp b/Dalamud.Boot/dllmain.cpp
index 83e678f08..9a741a47f 100644
--- a/Dalamud.Boot/dllmain.cpp
+++ b/Dalamud.Boot/dllmain.cpp
@@ -87,7 +87,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
logging::I("Built at: " __DATE__ "@" __TIME__);
- if (static_cast(g_startInfo.BootWaitMessageBox) & static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize))
+ if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
if (minHookLoaded) {
@@ -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!");
@@ -172,6 +172,9 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese
break;
case DLL_PROCESS_DETACH:
+ // do not show debug message boxes on abort() here
+ _set_abort_behavior(0, _WRITE_ABORT_MSG);
+
// process is terminating; don't bother cleaning up
if (lpReserved)
return TRUE;
diff --git a/Dalamud.Boot/pch.h b/Dalamud.Boot/pch.h
index bbd4b3d73..4cb2d7945 100644
--- a/Dalamud.Boot/pch.h
+++ b/Dalamud.Boot/pch.h
@@ -11,15 +11,16 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
-// Windows Header Files
-#include
-#include
+// Windows Header Files (1)
+#include
+
+// Windows Header Files (2)
+#include
#include
#include
-#include
-#include
-#include
+#include
#include
+#include
// MSVC Compiler Intrinsic
#include
@@ -33,11 +34,11 @@
#include
#include
#include
+#include
#include
#include
#include
#include
-#include
#include
// https://www.akenotsuki.com/misc/srell/en/
@@ -50,8 +51,8 @@
#include "../lib/Nomade040-nmd/nmd_assembly.h"
// https://github.com/dotnet/coreclr
-#include "../lib/CoreCLR/CoreCLR.h"
#include "../lib/CoreCLR/boot.h"
+#include "../lib/CoreCLR/CoreCLR.h"
// https://github.com/nlohmann/json
#include "../lib/nlohmann-json/json.hpp"
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/utils.cpp b/Dalamud.Boot/utils.cpp
index f51ef7e49..e06e812ef 100644
--- a/Dalamud.Boot/utils.cpp
+++ b/Dalamud.Boot/utils.cpp
@@ -520,3 +520,36 @@ void utils::wait_for_game_window() {
};
SendMessageW(game_window, WM_NULL, 0, 0);
}
+
+std::wstring utils::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);
+ } else {
+ res.push_back(L'"');
+ for (auto it = arg.begin(); ; ++it) {
+ size_t bsCount = 0;
+
+ while (it != arg.end() && *it == L'\\') {
+ ++it;
+ ++bsCount;
+ }
+
+ if (it == arg.end()) {
+ res.append(bsCount * 2, L'\\');
+ break;
+ } else if (*it == L'"') {
+ res.append(bsCount * 2 + 1, L'\\');
+ res.push_back(*it);
+ } else {
+ res.append(bsCount, L'\\');
+ res.push_back(*it);
+ }
+ }
+
+ res.push_back(L'"');
+ }
+ return res;
+}
diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h
index bbcba1f84..ca350674a 100644
--- a/Dalamud.Boot/utils.h
+++ b/Dalamud.Boot/utils.h
@@ -260,4 +260,6 @@ namespace utils {
HWND try_find_game_window();
void wait_for_game_window();
+
+ std::wstring escape_shell_arg(const std::wstring& arg);
}
diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp
index 5691d5b24..bb3f37869 100644
--- a/Dalamud.Boot/veh.cpp
+++ b/Dalamud.Boot/veh.cpp
@@ -2,8 +2,32 @@
#include "veh.h"
+#include
+
+#include "logging.h"
+#include "utils.h"
+
+#include "crashhandler_shared.h"
+#include "DalamudStartInfo.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;
+HANDLE g_crashhandler_process = nullptr;
+HANDLE g_crashhandler_pipe_write = nullptr;
+
+std::chrono::time_point g_time_start;
bool is_whitelist_exception(const DWORD code)
{
@@ -47,7 +71,6 @@ bool is_whitelist_exception(const DWORD code)
}
}
-
bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::filesystem::path& module_file)
{
HMODULE handle;
@@ -65,242 +88,231 @@ bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::
return false;
}
-
-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;
}
-
-bool get_sym_from_addr(const DWORD64 address, DWORD64& displacement, std::wstring& symbol_name)
+static void append_injector_launch_args(std::vector& args)
{
- union {
- char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]{};
- SYMBOL_INFOW symbol;
- };
- symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
- symbol.MaxNameLen = MAX_SYM_NAME;
+ args.emplace_back(L"-g");
+ args.emplace_back(utils::loaded_module::current_process().path().wstring());
+ if (g_startInfo.BootShowConsole)
+ args.emplace_back(L"--console");
+ if (g_startInfo.BootEnableEtw)
+ args.emplace_back(L"--etw");
+ if (g_startInfo.BootVehEnabled)
+ args.emplace_back(L"--veh");
+ if (g_startInfo.BootVehFull)
+ args.emplace_back(L"--veh-full");
+ if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
+ args.emplace_back(L"--msgbox1");
+ if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint) != DalamudStartInfo::WaitMessageboxFlags::None)
+ args.emplace_back(L"--msgbox2");
+ if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None)
+ args.emplace_back(L"--msgbox3");
- if (SymFromAddrW(GetCurrentProcess(), address, &displacement, &symbol) && symbol.Name[0])
- {
- symbol_name = symbol.Name;
- return true;
+ args.emplace_back(L"--");
+
+ if (int nArgs; LPWSTR * szArgList = CommandLineToArgvW(GetCommandLineW(), &nArgs)) {
+ for (auto i = 1; i < nArgs; i++)
+ args.emplace_back(szArgList[i]);
+ LocalFree(szArgList);
}
- return false;
-}
-
-
-std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = true)
-{
- DWORD64 module_base;
- std::filesystem::path module_path;
- bool is_mod_addr = get_module_file_and_base(address, module_base, module_path);
-
- DWORD64 value = 0;
- if(try_ptrderef && address > 0x10000 && address < 0x7FFFFFFE0000)
- {
- ReadProcessMemory(GetCurrentProcess(), reinterpret_cast(address), &value, sizeof value, nullptr);
- }
-
- std::wstring addr_str = is_mod_addr ?
- std::format(L"{}+{:X}", module_path.filename().c_str(), address - module_base) :
- std::format(L"{:X}", address);
-
- DWORD64 displacement;
- if (std::wstring symbol; get_sym_from_addr(address, displacement, symbol))
- return std::format(L"{}\t({})", addr_str, displacement != 0 ? std::format(L"{}+0x{:X}", symbol, displacement) : std::format(L"{}", symbol));
- 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)
-{
- 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{";
-
- log << std::format(L"\n RAX:\t{}", to_address_string(ctx.Rax));
- log << std::format(L"\n RBX:\t{}", to_address_string(ctx.Rbx));
- log << std::format(L"\n RCX:\t{}", to_address_string(ctx.Rcx));
- log << std::format(L"\n RDX:\t{}", to_address_string(ctx.Rdx));
- log << std::format(L"\n R8:\t{}", to_address_string(ctx.R8));
- log << std::format(L"\n R9:\t{}", to_address_string(ctx.R9));
- log << std::format(L"\n R10:\t{}", to_address_string(ctx.R10));
- log << std::format(L"\n R11:\t{}", to_address_string(ctx.R11));
- log << std::format(L"\n R12:\t{}", to_address_string(ctx.R12));
- log << std::format(L"\n R13:\t{}", to_address_string(ctx.R13));
- log << std::format(L"\n R14:\t{}", to_address_string(ctx.R14));
- log << std::format(L"\n R15:\t{}", to_address_string(ctx.R15));
-
- log << std::format(L"\n RSI:\t{}", to_address_string(ctx.Rsi));
- log << std::format(L"\n RDI:\t{}", to_address_string(ctx.Rdi));
- log << std::format(L"\n RBP:\t{}", to_address_string(ctx.Rbp));
- log << std::format(L"\n RSP:\t{}", to_address_string(ctx.Rsp));
- log << std::format(L"\n RIP:\t{}", to_address_string(ctx.Rip));
-
- log << L"\n}" << std::endl;
-
- if(0x10000 < ctx.Rsp && ctx.Rsp < 0x7FFFFFFE0000)
- {
- log << L"\nStack\n{";
-
- for(DWORD64 i = 0; i < 16; i++)
- log << std::format(L"\n [RSP+{:X}]\t{}", i * 8, to_address_string(*reinterpret_cast(ctx.Rsp + i * 8ull)));
-
- log << L"\n}\n";
- }
-
- log << L"\nModules\n{";
-
- if(HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetCurrentProcessId()); snap != INVALID_HANDLE_VALUE)
- {
- MODULEENTRY32 mod;
- mod.dwSize = sizeof MODULEENTRY32;
- if(Module32First(snap, &mod))
- {
- do
- {
- log << std::format(L"\n {:08X}\t{}", reinterpret_cast(mod.modBaseAddr), mod.szExePath);
- }
- while (Module32Next(snap, &mod));
- }
- CloseHandle(snap);
- }
-
- log << L"\n}\n";
}
LONG exception_handler(EXCEPTION_POINTERS* ex)
{
- static std::mutex s_exception_handler_mutex;
+ static std::recursive_mutex s_exception_handler_mutex;
- if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode))
- return EXCEPTION_CONTINUE_SEARCH;
+ if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
+ {
+ // pass
+ }
+ else
+ {
+ if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode))
+ return EXCEPTION_CONTINUE_SEARCH;
- if (!is_ffxiv_address(ex->ContextRecord->Rip))
- return EXCEPTION_CONTINUE_SEARCH;
+ 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
const auto lock = std::lock_guard(s_exception_handler_mutex);
- DWORD64 module_base;
- std::filesystem::path module_path;
+ exception_info exinfo{};
+ exinfo.pExceptionPointers = ex;
+ exinfo.ExceptionPointers = *ex;
+ exinfo.ContextRecord = *ex->ContextRecord;
+ exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
+ const auto time_now = std::chrono::system_clock::now();
+ auto lifetime = std::chrono::duration_cast(
+ time_now.time_since_epoch()).count()
+ - std::chrono::duration_cast(
+ g_time_start.time_since_epoch()).count();
+ exinfo.nLifetime = lifetime;
+ DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
- get_module_file_and_base(reinterpret_cast(&exception_handler), module_base, module_path);
-#ifndef NDEBUG
- std::wstring dmp_path = module_path.replace_filename(L"dalamud_appcrashd.dmp").wstring();
-#else
- std::wstring dmp_path = module_path.replace_filename(L"dalamud_appcrash.dmp").wstring();
-#endif
- std::wstring log_path = module_path.replace_filename(L"dalamud_appcrash.log").wstring();
-
- std::wostringstream log;
- log << std::format(L"Unhandled native exception occurred at {}", to_address_string(ex->ContextRecord->Rip, false)) << std::endl;
- log << std::format(L"Code: {:X}", ex->ExceptionRecord->ExceptionCode) << std::endl;
- log << std::format(L"Dump at: {}", dmp_path) << std::endl;
- log << L"Time: " << std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::system_clock::now() } << std::endl;
-
- SymRefreshModuleList(GetCurrentProcess());
- print_exception_info(ex, log);
-
- MINIDUMP_EXCEPTION_INFORMATION ex_info;
- 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);
-
- void* fn;
- if (const auto err = static_cast(g_clr->get_function_pointer(
+ std::wstring stackTrace;
+ if (void* fn; const auto err = static_cast(g_clr->get_function_pointer(
L"Dalamud.EntryPoint, Dalamud",
L"VehCallback",
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
nullptr, nullptr, &fn)))
{
- const auto formatted = std::format(
- L"An error within the game has occurred.\n\n"
- L"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"
- L"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"
- L"The log file is located at:\n"
- L"{1}\n\n"
- L"Press OK to exit the application.\n\nFailed to read stack trace: {2:08x}",
- dmp_path, log_path, err);
-
- // show in another thread to prevent messagebox from pumping messages of current thread
- std::thread([&]() {
- MessageBoxW(nullptr, formatted.c_str(), L"Dalamud Error", MB_OK | MB_ICONERROR | MB_TOPMOST);
- }).join();
+ stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
}
else
{
- ((void(__stdcall*)(const void*, const void*, const void*))fn)(dmp_path.c_str(), log_path.c_str(), log.str().c_str());
+ stackTrace = static_cast(fn)();
+ // Don't free it, as the program's going to be quit anyway
}
+
+ exinfo.dwStackTraceLength = static_cast(stackTrace.size());
+ if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
+ return EXCEPTION_CONTINUE_SEARCH;
+ if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &stackTrace[0], static_cast(std::span(stackTrace).size_bytes()), &written, nullptr) || std::span(stackTrace).size_bytes() != written)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ SuspendThread(GetCurrentThread());
return EXCEPTION_CONTINUE_SEARCH;
}
-bool veh::add_handler(bool doFullDump)
+bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
{
if (g_veh_handle)
return false;
+
g_veh_handle = AddVectoredExceptionHandler(1, exception_handler);
SetUnhandledExceptionFilter(nullptr);
g_veh_do_full_dump = doFullDump;
+ g_time_start = std::chrono::system_clock::now();
- return g_veh_handle != nullptr;
+ std::optional, decltype(&CloseHandle)>> hWritePipe;
+ std::optional, decltype(&CloseHandle)>> hReadPipeInheritable;
+ 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);
+ }
+ else
+ {
+ logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(1) error 0x{:x}", GetLastError());
+ return false;
+ }
+ }
+ else
+ {
+ logging::W("Failed to launch DalamudCrashHandler.exe: CreatePipe error 0x{:x}", GetLastError());
+ return false;
+ }
+
+ // additional information
+ STARTUPINFOEXW siex{};
+ PROCESS_INFORMATION pi{};
+
+ siex.StartupInfo.cb = sizeof siex;
+ siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
+#ifdef NDEBUG
+ siex.StartupInfo.wShowWindow = SW_HIDE;
+#else
+ siex.StartupInfo.wShowWindow = SW_SHOW;
+#endif
+
+ // set up list of handles to inherit to child process
+ std::vector attributeListBuf;
+ if (SIZE_T size = 0; !InitializeProcThreadAttributeList(nullptr, 1, 0, &size))
+ {
+ if (const auto err = GetLastError(); err != ERROR_INSUFFICIENT_BUFFER)
+ {
+ logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(1) error 0x{:x}", err);
+ return false;
+ }
+
+ attributeListBuf.resize(size);
+ siex.lpAttributeList = reinterpret_cast(&attributeListBuf[0]);
+ if (!InitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &size))
+ {
+ logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(2) error 0x{:x}", GetLastError());
+ return false;
+ }
+ }
+ else
+ {
+ logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(0) was supposed to fail");
+ return false;
+ }
+ std::unique_ptr, decltype(&DeleteProcThreadAttributeList)> cleanAttributeList(siex.lpAttributeList, &DeleteProcThreadAttributeList);
+
+ std::vector handles;
+
+ HANDLE hInheritableCurrentProcess;
+ if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &hInheritableCurrentProcess, 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(2) error 0x{:x}", GetLastError());
+ return false;
+ }
+ handles.push_back(hInheritableCurrentProcess);
+ handles.push_back(hReadPipeInheritable->get());
+
+ std::vector args;
+ std::wstring argstr;
+ args.emplace_back((std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe").wstring());
+ args.emplace_back(std::format(L"--process-handle={}", reinterpret_cast(hInheritableCurrentProcess)));
+ args.emplace_back(std::format(L"--exception-info-pipe-read-handle={}", reinterpret_cast(hReadPipeInheritable->get())));
+ args.emplace_back(std::format(L"--asset-directory={}", unicode::convert(g_startInfo.AssetDirectory)));
+ args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
+ ? utils::loaded_module(g_hModule).path().parent_path().wstring()
+ : std::filesystem::path(unicode::convert(g_startInfo.BootLogPath)).parent_path().wstring()));
+ args.emplace_back(L"--");
+ append_injector_launch_args(args);
+
+ for (const auto& arg : args)
+ {
+ argstr.append(utils::escape_shell_arg(arg));
+ 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());
+ return false;
+ }
+
+ if (!CreateProcessW(
+ args[0].c_str(), // The path
+ &argstr[0], // Command line
+ nullptr, // Process handle not inheritable
+ nullptr, // Thread handle not inheritable
+ 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
+ &siex.StartupInfo, // Pointer to STARTUPINFO structure
+ &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
+ ))
+ {
+ logging::W("Failed to launch DalamudCrashHandler.exe: CreateProcessW error 0x{:x}", GetLastError());
+ return false;
+ }
+
+ CloseHandle(pi.hThread);
+
+ g_crashhandler_process = pi.hProcess;
+ g_crashhandler_pipe_write = hWritePipe->release();
+ logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId);
+ return true;
}
bool veh::remove_handler()
diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h
index bf0c549f3..1905272ea 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, const std::string& workingDirectory);
bool remove_handler();
}
diff --git a/Dalamud.Boot/xivfixes.cpp b/Dalamud.Boot/xivfixes.cpp
index 1f11d7ed5..338938fc1 100644
--- a/Dalamud.Boot/xivfixes.cpp
+++ b/Dalamud.Boot/xivfixes.cpp
@@ -493,6 +493,49 @@ void xivfixes::backup_userdata_save(bool bApply) {
}
}
+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)
+ {
+ if (!g_startInfo.BootEnabledGameFixes.contains("clr_failfast_hijack")) {
+ logging::I("{} Turned off via environment variable.", LogTag);
+ return;
+ }
+
+ 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)
+ {
+ MessageBoxW(nullptr, L"An error in a Dalamud plugin was detected and the game cannot continue.\n\nPlease take a screenshot of this error message and let us know about it.", L"Dalamud", MB_OK | MB_ICONERROR);
+
+ 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
+ {
+ if (s_HookClrFatalError) {
+ logging::I("{} Disable ClrFatalError", LogTag);
+ s_HookClrFatalError.reset();
+ s_HookSetUnhandledExceptionFilter.reset();
+ }
+ }
+}
+
void xivfixes::apply_all(bool bApply) {
for (const auto& [taskName, taskFunction] : std::initializer_list>
{
@@ -501,6 +544,7 @@ void xivfixes::apply_all(bool bApply) {
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
{ "redirect_openprocess", &redirect_openprocess },
{ "backup_userdata_save", &backup_userdata_save },
+ { "clr_failfast_hijack", &clr_failfast_hijack }
}
) {
try {
diff --git a/Dalamud.Boot/xivfixes.h b/Dalamud.Boot/xivfixes.h
index 556c5422f..894c60880 100644
--- a/Dalamud.Boot/xivfixes.h
+++ b/Dalamud.Boot/xivfixes.h
@@ -6,6 +6,7 @@ namespace xivfixes {
void disable_game_openprocess_access_check(bool bApply);
void redirect_openprocess(bool bApply);
void backup_userdata_save(bool bApply);
+ void clr_failfast_hijack(bool bApply);
void apply_all(bool bApply);
}
diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj
index 556b7dbda..f7da123fe 100644
--- a/Dalamud.Injector/Dalamud.Injector.csproj
+++ b/Dalamud.Injector/Dalamud.Injector.csproj
@@ -60,7 +60,7 @@
-
+
diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs
index 402fe8809..205ba6537 100644
--- a/Dalamud.Injector/EntryPoint.cs
+++ b/Dalamud.Injector/EntryPoint.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
+using System.Text.RegularExpressions;
using Dalamud.Game;
using Newtonsoft.Json;
@@ -317,12 +318,13 @@ namespace Dalamud.Injector
startInfo.BootShowConsole = args.Contains("--console");
startInfo.BootEnableEtw = args.Contains("--etw");
startInfo.BootLogPath = GetLogPath("dalamud.boot");
- startInfo.BootEnabledGameFixes = new List { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save" };
+ startInfo.BootEnabledGameFixes = new List { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "clr_failfast_hijack" };
startInfo.BootDotnetOpenProcessHookMode = 0;
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");
@@ -483,6 +485,7 @@ namespace Dalamud.Injector
var withoutDalamud = false;
var noFixAcl = false;
var waitForGameWindow = true;
+ var encryptArguments = false;
var parsingGameArgument = false;
for (var i = 2; i < args.Count; i++)
@@ -519,6 +522,43 @@ namespace Dalamud.Injector
throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
}
+ var checksumTable = "fX1pGtdS5CAP4_VL";
+ var argDelimiterRegex = new Regex(" (?
+ {
+ if (!x.StartsWith("//**sqex0003") || !x.EndsWith("**//"))
+ return new List() { x };
+
+ var checksum = checksumTable.IndexOf(x[x.Length - 5]);
+ if (checksum == -1)
+ return new List() { x };
+
+ var encData = Convert.FromBase64String(x.Substring(12, x.Length - 12 - 5).Replace('-', '+').Replace('_', '/').Replace('*', '='));
+ var rawData = new byte[encData.Length];
+
+ for (var i = (uint)checksum; i < 0x10000u; i += 0x10)
+ {
+ var bf = new LegacyBlowfish(Encoding.UTF8.GetBytes($"{i << 16:x08}"));
+ Buffer.BlockCopy(encData, 0, rawData, 0, rawData.Length);
+ bf.Decrypt(ref rawData);
+ var rawString = Encoding.UTF8.GetString(rawData).Split('\0', 2).First();
+ encryptArguments = true;
+ var args = argDelimiterRegex.Split(rawString).Skip(1).Select(y => string.Join('=', kvDelimiterRegex.Split(y, 2)).Replace(" ", " ")).ToList();
+ if (!args.Any())
+ continue;
+ if (!args.First().StartsWith("T="))
+ continue;
+ if (!uint.TryParse(args.First().Substring(2), out var tickCount))
+ continue;
+ if (tickCount >> 16 != i)
+ continue;
+ return args.Skip(1);
+ }
+
+ return new List() { x };
+ }).ToList();
+
if (showHelp)
{
ProcessHelpCommand(args, "launch");
@@ -602,7 +642,39 @@ namespace Dalamud.Injector
});
}
- var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
+ string gameArgumentString;
+ if (encryptArguments)
+ {
+ var rawTickCount = (uint)Environment.TickCount;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ [System.Runtime.InteropServices.DllImport("c")]
+ static extern ulong clock_gettime_nsec_np(int clock_id);
+
+ const int CLOCK_MONOTONIC_RAW = 4;
+ var rawTickCountFixed = (clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000);
+ Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed);
+ rawTickCount = (uint)rawTickCountFixed;
+ }
+
+ var ticks = rawTickCount & 0xFFFF_FFFFu;
+ var key = ticks & 0xFFFF_0000u;
+ gameArguments.Insert(0, $"T={ticks}");
+
+ var escapeValue = (string x) => x.Replace(" ", " ");
+ gameArgumentString = gameArguments.Select(x => x.Split('=', 2)).Aggregate(new StringBuilder(), (whole, part) => whole.Append($" /{escapeValue(part[0])} ={escapeValue(part.Length > 1 ? part[1] : string.Empty)}")).ToString();
+ var bf = new LegacyBlowfish(Encoding.UTF8.GetBytes($"{key:x08}"));
+ var ciphertext = bf.Encrypt(Encoding.UTF8.GetBytes(gameArgumentString));
+ var base64Str = Convert.ToBase64String(ciphertext).Replace('+', '-').Replace('/', '_').Replace('=', '*');
+ var checksum = checksumTable[(int)(key >> 16) & 0xF];
+ gameArgumentString = $"//**sqex0003{base64Str}{checksum}**//";
+ }
+ else
+ {
+ gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
+ }
+
var process = GameStart.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, noFixAcl, (Process p) =>
{
if (!withoutDalamud && mode == "entrypoint")
@@ -731,7 +803,7 @@ namespace Dalamud.Injector
using var startInfoBuffer = new MemoryBufferHelper(process).CreatePrivateMemoryBuffer(startInfoBytes.Length + 0x8);
var startInfoAddress = startInfoBuffer.Add(startInfoBytes);
- if (startInfoAddress == UIntPtr.Zero)
+ if (startInfoAddress == 0)
throw new Exception("Unable to allocate start info JSON");
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
diff --git a/Dalamud.Injector/Injector.cs b/Dalamud.Injector/Injector.cs
index 9ebb87bd3..17ca5ccb5 100644
--- a/Dalamud.Injector/Injector.cs
+++ b/Dalamud.Injector/Injector.cs
@@ -32,11 +32,11 @@ namespace Dalamud.Injector
private readonly CircularBuffer circularBuffer;
private readonly PrivateMemoryBuffer memoryBuffer;
- private UIntPtr loadLibraryShellPtr;
- private UIntPtr loadLibraryRetPtr;
+ private nuint loadLibraryShellPtr;
+ private nuint loadLibraryRetPtr;
- private UIntPtr getProcAddressShellPtr;
- private UIntPtr getProcAddressRetPtr;
+ private nuint getProcAddressShellPtr;
+ private nuint getProcAddressRetPtr;
///
/// Initializes a new instance of the class.
@@ -81,16 +81,16 @@ namespace Dalamud.Injector
///
/// Absolute file path.
/// Address to the module.
- public void LoadLibrary(string modulePath, out UIntPtr address)
+ public void LoadLibrary(string modulePath, out IntPtr address)
{
var lpParameter = this.WriteNullTerminatedUnicodeString(modulePath);
- if (lpParameter == UIntPtr.Zero)
+ if (lpParameter == 0)
throw new Exception("Unable to allocate LoadLibraryW parameter");
this.CallRemoteFunction(this.loadLibraryShellPtr, lpParameter, out var err);
- this.extMemory.Read(this.loadLibraryRetPtr, out address);
- if (address == UIntPtr.Zero)
+ this.extMemory.Read(this.loadLibraryRetPtr, out address);
+ if (address == IntPtr.Zero)
throw new Exception($"LoadLibraryW(\"{modulePath}\") failure: {new Win32Exception((int)err).Message} ({err})");
}
@@ -100,17 +100,17 @@ namespace Dalamud.Injector
/// Module address.
/// Name of the exported method.
/// Address to the function.
- public void GetFunctionAddress(UIntPtr module, string functionName, out UIntPtr address)
+ public void GetFunctionAddress(IntPtr module, string functionName, out nuint address)
{
var functionNamePtr = this.WriteNullTerminatedASCIIString(functionName);
var getProcAddressParams = new GetProcAddressParams(module, functionNamePtr);
var lpParameter = this.circularBuffer.Add(ref getProcAddressParams);
- if (lpParameter == UIntPtr.Zero)
+ if (lpParameter == 0)
throw new Exception("Unable to allocate GetProcAddress parameter ptr");
this.CallRemoteFunction(this.getProcAddressShellPtr, lpParameter, out var err);
- this.extMemory.Read(this.getProcAddressRetPtr, out address);
- if (address == UIntPtr.Zero)
+ this.extMemory.Read(this.getProcAddressRetPtr, out address);
+ if (address == 0)
throw new Exception($"GetProcAddress(0x{module:X}, \"{functionName}\") failure: {new Win32Exception((int)err).Message} ({err})");
}
@@ -120,7 +120,7 @@ namespace Dalamud.Injector
/// Method address.
/// Parameter address.
/// Thread exit code.
- public void CallRemoteFunction(UIntPtr methodAddress, UIntPtr parameterAddress, out uint exitCode)
+ public void CallRemoteFunction(nuint methodAddress, nuint parameterAddress, out uint exitCode)
{
// Create and initialize a thread at our address and parameter address.
var threadHandle = CreateRemoteThread(
@@ -150,27 +150,24 @@ namespace Dalamud.Injector
var functionAddr = kernel32Module.BaseAddress + (int)this.GetExportedFunctionOffset(kernel32Exports, "LoadLibraryW");
Log.Verbose($"LoadLibraryW: 0x{functionAddr.ToInt64():X}");
- var functionPtr = (UIntPtr)this.memoryBuffer.Add(ref functionAddr);
- Log.Verbose($"LoadLibraryPtr: 0x{functionPtr.ToUInt64():X}");
+ var functionPtr = this.memoryBuffer.Add(ref functionAddr);
+ Log.Verbose($"LoadLibraryPtr: 0x{functionPtr:X}");
- if (functionPtr == UIntPtr.Zero)
+ if (functionPtr == 0)
throw new Exception("Unable to allocate LoadLibraryW function ptr");
var dummy = IntPtr.Zero;
this.loadLibraryRetPtr = this.memoryBuffer.Add(ref dummy);
- Log.Verbose($"LoadLibraryRetPtr: 0x{this.loadLibraryRetPtr.ToUInt64():X}");
+ Log.Verbose($"LoadLibraryRetPtr: 0x{this.loadLibraryRetPtr:X}");
- if (this.loadLibraryRetPtr == UIntPtr.Zero)
+ if (this.loadLibraryRetPtr == 0)
throw new Exception("Unable to allocate LoadLibraryW return value");
- var func = functionPtr.ToUInt64();
- var retVal = this.loadLibraryRetPtr.ToUInt64();
-
var asm = new Assembler(64);
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
- asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX.
- asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal], rax //
+ asm.call(__qword_ptr[__qword_ptr[functionPtr]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX.
+ asm.mov(__qword_ptr[__qword_ptr[this.loadLibraryRetPtr]], rax); // mov qword [qword retVal], rax //
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.mov(rax, (ulong)getLastErrorAddr); // mov rax, pfnGetLastError // Change return address to GetLastError.
asm.push(rax); // push rax //
@@ -178,9 +175,9 @@ namespace Dalamud.Injector
var bytes = this.Assemble(asm);
this.loadLibraryShellPtr = this.memoryBuffer.Add(bytes);
- Log.Verbose($"LoadLibraryShellPtr: 0x{this.loadLibraryShellPtr.ToUInt64():X}");
+ Log.Verbose($"LoadLibraryShellPtr: 0x{this.loadLibraryShellPtr:X}");
- if (this.loadLibraryShellPtr == UIntPtr.Zero)
+ if (this.loadLibraryShellPtr == 0)
throw new Exception("Unable to allocate LoadLibraryW shellcode");
this.extMemory.ChangePermission(this.loadLibraryShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE);
@@ -193,7 +190,7 @@ namespace Dalamud.Injector
Log.Verbose($"LoadLibraryRet: {this.GetResultMarker(dummy == outRetPtr)}");
this.extMemory.ReadRaw(this.loadLibraryShellPtr, out var outBytes, bytes.Length);
- Log.Verbose($"LoadLibraryShellPtr: {this.GetResultMarker(bytes.SequenceEqual(outBytes))}");
+ Log.Verbose($"LoadLibraryShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}");
#endif
}
@@ -206,29 +203,26 @@ namespace Dalamud.Injector
var functionAddr = kernel32Module.BaseAddress + (int)offset;
Log.Verbose($"GetProcAddress: 0x{functionAddr.ToInt64():X}");
- var functionPtr = (UIntPtr)this.memoryBuffer.Add(ref functionAddr);
- Log.Verbose($"GetProcAddressPtr: 0x{functionPtr.ToUInt64():X}");
+ var functionPtr = this.memoryBuffer.Add(ref functionAddr);
+ Log.Verbose($"GetProcAddressPtr: 0x{functionPtr:X}");
- if (functionPtr == UIntPtr.Zero)
+ if (functionPtr == 0)
throw new Exception("Unable to allocate GetProcAddress function ptr");
var dummy = IntPtr.Zero;
this.getProcAddressRetPtr = this.memoryBuffer.Add(ref dummy);
- Log.Verbose($"GetProcAddressRetPtr: 0x{this.loadLibraryRetPtr.ToUInt64():X}");
+ Log.Verbose($"GetProcAddressRetPtr: 0x{this.loadLibraryRetPtr:X}");
- if (this.getProcAddressRetPtr == UIntPtr.Zero)
+ if (this.getProcAddressRetPtr == 0)
throw new Exception("Unable to allocate GetProcAddress return value");
- var func = functionPtr.ToUInt64();
- var retVal = this.getProcAddressRetPtr.ToUInt64();
-
var asm = new Assembler(64);
asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary +32 shadow space
asm.mov(rdx, __qword_ptr[__qword_ptr[rcx + 8]]); // mov rdx, qword [qword rcx + 8] // lpProcName
asm.mov(rcx, __qword_ptr[__qword_ptr[rcx + 0]]); // mov rcx, qword [qword rcx + 0] // hModule
- asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] //
- asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal] //
+ asm.call(__qword_ptr[__qword_ptr[functionPtr]]); // call qword [qword func] //
+ asm.mov(__qword_ptr[__qword_ptr[this.getProcAddressRetPtr]], rax); // mov qword [qword retVal] //
asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space.
asm.mov(rax, (ulong)getLastErrorAddr); // mov rax, pfnGetLastError // Change return address to GetLastError.
asm.push(rax); // push rax //
@@ -236,9 +230,9 @@ namespace Dalamud.Injector
var bytes = this.Assemble(asm);
this.getProcAddressShellPtr = this.memoryBuffer.Add(bytes);
- Log.Verbose($"GetProcAddressShellPtr: 0x{this.getProcAddressShellPtr.ToUInt64():X}");
+ Log.Verbose($"GetProcAddressShellPtr: 0x{this.getProcAddressShellPtr:X}");
- if (this.getProcAddressShellPtr == UIntPtr.Zero)
+ if (this.getProcAddressShellPtr == 0)
throw new Exception("Unable to allocate GetProcAddress shellcode");
this.extMemory.ChangePermission(this.getProcAddressShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE);
@@ -251,7 +245,7 @@ namespace Dalamud.Injector
Log.Verbose($"GetProcAddressRet: {this.GetResultMarker(dummy == outRetPtr)}");
this.extMemory.ReadRaw(this.getProcAddressShellPtr, out var outBytes, bytes.Length);
- Log.Verbose($"GetProcAddressShellPtr: {this.GetResultMarker(bytes.SequenceEqual(outBytes))}");
+ Log.Verbose($"GetProcAddressShellPtr: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))}");
#endif
}
@@ -298,33 +292,33 @@ namespace Dalamud.Injector
return exportFunction.Address;
}
- private UIntPtr WriteNullTerminatedASCIIString(string value)
+ private nuint WriteNullTerminatedASCIIString(string value)
{
var bytes = Encoding.ASCII.GetBytes(value + '\0');
- var address = (UIntPtr)this.circularBuffer.Add(bytes);
+ var address = this.circularBuffer.Add(bytes);
- if (address == UIntPtr.Zero)
+ if (address == 0)
throw new Exception("Unable to write ASCII string to buffer");
#if DEBUG
this.extMemory.ReadRaw(address, out var outBytes, bytes.Length);
- Log.Verbose($"WriteASCII: {this.GetResultMarker(bytes.SequenceEqual(outBytes))} 0x{address.ToUInt64():X} {value}");
+ Log.Verbose($"WriteASCII: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address:X} {value}");
#endif
return address;
}
- private UIntPtr WriteNullTerminatedUnicodeString(string value)
+ private nuint WriteNullTerminatedUnicodeString(string value)
{
var bytes = Encoding.Unicode.GetBytes(value + '\0');
- var address = (UIntPtr)this.circularBuffer.Add(bytes);
+ var address = this.circularBuffer.Add(bytes);
- if (address == UIntPtr.Zero)
+ if (address == 0)
throw new Exception("Unable to write Unicode string to buffer");
#if DEBUG
this.extMemory.ReadRaw(address, out var outBytes, bytes.Length);
- Log.Verbose($"WriteUnicode: {this.GetResultMarker(bytes.SequenceEqual(outBytes))} 0x{address.ToUInt64():X} {value}");
+ Log.Verbose($"WriteUnicode: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address:X} {value}");
#endif
return address;
@@ -337,15 +331,15 @@ namespace Dalamud.Injector
[StructLayout(LayoutKind.Sequential)]
private struct GetProcAddressParams
{
- public GetProcAddressParams(UIntPtr hModule, UIntPtr lPProcName)
+ public GetProcAddressParams(IntPtr hModule, nuint lPProcName)
{
- this.HModule = (long)hModule.ToUInt64();
- this.LPProcName = (long)lPProcName.ToUInt64();
+ this.HModule = hModule.ToInt64();
+ this.LPProcName = lPProcName;
}
public long HModule { get; set; }
- public long LPProcName { get; set; }
+ public nuint LPProcName { get; set; }
}
}
}
diff --git a/Dalamud.Injector/LegacyBlowfish.cs b/Dalamud.Injector/LegacyBlowfish.cs
new file mode 100644
index 000000000..576940521
--- /dev/null
+++ b/Dalamud.Injector/LegacyBlowfish.cs
@@ -0,0 +1,319 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dalamud.Injector
+{
+ internal class LegacyBlowfish
+ {
+ #region P-Array and S-Boxes
+
+ private readonly uint[] p =
+ {
+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
+ 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+ 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
+ };
+
+ private readonly uint[,] s =
+ {
+ {
+ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
+ 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+ 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
+ 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+ 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,
+ 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+ 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
+ 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+ 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
+ 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+ 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,
+ 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+ 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
+ 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+ 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
+ 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+ 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,
+ 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+ 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
+ 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+ 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
+ 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+ 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,
+ 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+ 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
+ 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+ 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
+ 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+ 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,
+ 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+ 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
+ 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+ 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
+ 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+ 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,
+ 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+ 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
+ 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+ 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
+ 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+ 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,
+ 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+ 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
+ },
+ {
+ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,
+ 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+ 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,
+ 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+ 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,
+ 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+ 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
+ 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+ 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,
+ 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+ 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,
+ 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+ 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,
+ 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+ 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,
+ 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+ 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
+ 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+ 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,
+ 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+ 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,
+ 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+ 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,
+ 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+ 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,
+ 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+ 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
+ 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+ 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,
+ 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+ 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,
+ 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+ 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,
+ 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+ 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,
+ 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+ 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
+ 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+ 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,
+ 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+ 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,
+ 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+ 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
+ },
+ {
+ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,
+ 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+ 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,
+ 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+ 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,
+ 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+ 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,
+ 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+ 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,
+ 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+ 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,
+ 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+ 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,
+ 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+ 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,
+ 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+ 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,
+ 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+ 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,
+ 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+ 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,
+ 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+ 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,
+ 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+ 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,
+ 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+ 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,
+ 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+ 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,
+ 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+ 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,
+ 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+ 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,
+ 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+ 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,
+ 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+ 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,
+ 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+ 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,
+ 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+ 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,
+ 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+ 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
+ },
+ {
+ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
+ 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+ 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,
+ 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+ 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
+ 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+ 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
+ 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+ 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,
+ 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+ 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
+ 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+ 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
+ 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+ 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,
+ 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+ 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
+ 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+ 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
+ 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+ 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,
+ 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+ 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
+ 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+ 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
+ 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+ 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,
+ 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+ 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
+ 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+ 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
+ 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+ 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,
+ 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+ 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
+ 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+ 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
+ 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+ 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,
+ 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+ 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
+ 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+ 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
+ }
+ };
+
+ #endregion
+
+ private static readonly int Rounds = 16;
+
+ ///
+ /// Initialize a new blowfish.
+ ///
+ /// The key to use.
+ /// Whether or not a sign confusion should be introduced during key init. This is needed for SE's implementation of blowfish.
+ public LegacyBlowfish(byte[] key)
+ {
+ foreach (var (i, keyFragment) in WrappingUInt32(key, this.p.Length))
+ this.p[i] ^= keyFragment;
+
+ uint l = 0, r = 0;
+ for (int i = 0; i < this.p.Length; i += 2)
+ (l, r) = (this.p[i], this.p[i + 1]) = Encrypt(l, r);
+
+ for (int i = 0; i < this.s.GetLength(0); i++)
+ for (int j = 0; j < this.s.GetLength(1); j += 2)
+ (l, r) = (this.s[i, j], this.s[i, j + 1]) = Encrypt(l, r);
+ }
+
+ public byte[] Encrypt(byte[] data)
+ {
+ var paddedLength = data.Length % 8 == 0 ? data.Length : data.Length + (8 - (data.Length % 8));
+ var buffer = new byte[paddedLength];
+ Buffer.BlockCopy(data, 0, buffer, 0, data.Length);
+
+ for (int i = 0; i < paddedLength; i += 8)
+ {
+ var (l, r) = Encrypt(BitConverter.ToUInt32(buffer, i), BitConverter.ToUInt32(buffer, i + 4));
+ CopyUInt32IntoArray(buffer, l, i);
+ CopyUInt32IntoArray(buffer, r, i + 4);
+ }
+
+ return buffer;
+ }
+
+ public void Decrypt(ref byte[] data)
+ {
+ for (int i = 0; i < data.Length; i += 8)
+ {
+ var (l, r) = Decrypt(BitConverter.ToUInt32(data, i), BitConverter.ToUInt32(data, i + 4));
+ CopyUInt32IntoArray(data, l, i);
+ CopyUInt32IntoArray(data, r, i + 4);
+ }
+ }
+
+ private static void CopyUInt32IntoArray(byte[] dest, uint val, int offset)
+ {
+ dest[offset] = (byte)(val & 0xFF);
+ dest[offset + 1] = (byte)((val >> 8) & 0xFF);
+ dest[offset + 2] = (byte)((val >> 16) & 0xFF);
+ dest[offset + 3] = (byte)((val >> 24) & 0xFF);
+ }
+
+ private uint F(uint i)
+ {
+ return ((this.s[0, i >> 24]
+ + this.s[1, (i >> 16) & 0xFF])
+ ^ this.s[2, (i >> 8) & 0xFF])
+ + this.s[3, i & 0xFF];
+ }
+
+ private (uint, uint) Encrypt(uint l, uint r)
+ {
+ for (int i = 0; i < Rounds; i += 2)
+ {
+ l ^= this.p[i];
+ r ^= F(l);
+ r ^= this.p[i + 1];
+ l ^= F(r);
+ }
+
+ return (r ^ this.p[17], l ^ this.p[16]);
+ }
+
+ private (uint, uint) Decrypt(uint l, uint r)
+ {
+ for (int i = Rounds; i > 0; i -= 2)
+ {
+ l ^= this.p[i + 1];
+ r ^= F(l);
+ r ^= this.p[i];
+ l ^= F(r);
+ }
+
+ return (r ^ this.p[0], l ^ this.p[1]);
+ }
+
+ private static IEnumerable Cycle(IEnumerable source)
+ {
+ while (true)
+ foreach (TSource t in source)
+ yield return t;
+ }
+
+ private IEnumerable<(int, uint)> WrappingUInt32(IEnumerable source, int count)
+ {
+ var enumerator = Cycle(source).GetEnumerator();
+
+ for (int i = 0; i < count; i++)
+ {
+ var n = 0u;
+
+ for (var j = 0; j < 4 && enumerator.MoveNext(); j++)
+ {
+ n = (uint)((n << 8) | (sbyte)enumerator.Current); // NOTE(goat): THIS IS A BUG! SE's implementation wrongly uses signed numbers for this, so we need to as well.
+ }
+
+ yield return (i, n);
+ }
+ }
+ }
+}
diff --git a/Dalamud.Injector/NativeFunctions.cs b/Dalamud.Injector/NativeFunctions.cs
index 83dcc8a2b..2a4654aaf 100644
--- a/Dalamud.Injector/NativeFunctions.cs
+++ b/Dalamud.Injector/NativeFunctions.cs
@@ -661,8 +661,8 @@ namespace Dalamud.Injector
IntPtr hProcess,
IntPtr lpThreadAttributes,
UIntPtr dwStackSize,
- UIntPtr lpStartAddress,
- UIntPtr lpParameter,
+ nuint lpStartAddress,
+ nuint lpParameter,
CreateThreadFlags dwCreationFlags,
out uint lpThreadId);
diff --git a/Dalamud.sln b/Dalamud.sln
index 546e31dfe..a618f1156 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|x64
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|x64
+ {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|x64
+ {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index 1eed6a2fa..1b2a43c17 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -332,6 +332,11 @@ namespace Dalamud.Configuration.Internal
///
public bool ShowDevBarInfo { get; set; } = true;
+ ///
+ /// Gets or sets the last-used contact details for the plugin feedback form.
+ ///
+ public string LastFeedbackContactDetails { get; set; } = string.Empty;
+
///
/// Load a configuration from the provided path.
///
diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs
index 24011b489..cc858eee8 100644
--- a/Dalamud/Dalamud.cs
+++ b/Dalamud/Dalamud.cs
@@ -28,7 +28,6 @@ namespace Dalamud
#region Internals
private readonly ManualResetEvent unloadSignal;
- private bool hasDisposedPlugins = false;
#endregion
@@ -117,8 +116,6 @@ namespace Dalamud
///
public void DisposePlugins()
{
- this.hasDisposedPlugins = true;
-
// this must be done before unloading interface manager, in order to do rebuild
// the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
// will not receive any windows messages
diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj
index 48e0bd7ae..619713a4c 100644
--- a/Dalamud/Dalamud.csproj
+++ b/Dalamud/Dalamud.csproj
@@ -8,7 +8,7 @@
- 6.4.0.36
+ 6.4.0.42
XIV Launcher addon framework
$(DalamudVersion)
$(DalamudVersion)
@@ -74,7 +74,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs
index 5fef315d5..c28c3bbaf 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -10,7 +10,6 @@ using Dalamud.Configuration.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Support;
using Dalamud.Utility;
-using ImGuiNET;
using Newtonsoft.Json;
using PInvoke;
using Serilog;
@@ -41,10 +40,8 @@ namespace Dalamud
///
/// A delegate used from VEH handler on exception which CoreCLR will fast fail by default.
///
- /// Path to minidump file created in UTF-16.
- /// Path to log file to create in UTF-16.
- /// Log text in UTF-16.
- public delegate void VehDelegate(IntPtr dumpPath, IntPtr logPath, IntPtr log);
+ /// HGLOBAL for message.
+ public delegate IntPtr VehDelegate();
///
/// Initialize Dalamud.
@@ -63,54 +60,19 @@ namespace Dalamud
}
///
- /// Show error message along with stack trace and exit.
+ /// Returns stack trace.
///
- /// Path to minidump file created in UTF-16.
- /// Path to log file to create in UTF-16.
- /// Log text in UTF-16.
- public static void VehCallback(IntPtr dumpPath, IntPtr logPath, IntPtr log)
+ /// HGlobal to wchar_t* stack trace c-string.
+ public static IntPtr VehCallback()
{
- string stackTrace;
try
{
- stackTrace = Environment.StackTrace;
+ return Marshal.StringToHGlobalUni(Environment.StackTrace);
}
catch (Exception e)
{
- stackTrace = "Fail: " + e.ToString();
+ return Marshal.StringToHGlobalUni("Fail: " + e);
}
-
- 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}";
-
- try
- {
- File.WriteAllText(
- Marshal.PtrToStringUni(logPath),
- "Stack trace:\n" + stackTrace + "\n\n" + Marshal.PtrToStringUni(log));
- }
- catch (Exception e)
- {
- msg += "\n\nAdditionally, failed to write file: " + e.ToString();
- }
-
- // Show in another thread to prevent messagebox from pumping messages of current thread.
- var msgThread = new Thread(() =>
- {
- Utility.Util.Fatal(
- msg.Format(
- Marshal.PtrToStringUni(dumpPath),
- Marshal.PtrToStringUni(logPath),
- stackTrace),
- "Dalamud Error",
- false);
- });
- msgThread.Start();
- msgThread.Join();
}
///
diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs
index 7215568aa..3e489af82 100644
--- a/Dalamud/Game/ChatHandlers.cs
+++ b/Dalamud/Game/ChatHandlers.cs
@@ -292,7 +292,7 @@ namespace Dalamud.Game
this.hasAutoUpdatedPlugins = true;
- Task.Run(() => pluginManager.UpdatePluginsAsync(!this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
+ Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
{
if (task.IsFaulted)
{
@@ -301,11 +301,11 @@ namespace Dalamud.Game
}
var updatedPlugins = task.Result;
- if (updatedPlugins != null && updatedPlugins.Any())
+ if (updatedPlugins.Any())
{
if (this.configuration.AutoUpdatePlugins)
{
- PluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
+ Service.Get().PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info);
}
else
diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs
index 0ccc445f6..ffc7188f4 100644
--- a/Dalamud/Game/SigScanner.cs
+++ b/Dalamud/Game/SigScanner.cs
@@ -326,7 +326,9 @@ namespace Dalamud.Game
if (insnByte == 0xE8 || insnByte == 0xE9)
scanRet = ReadJmpCallSig(scanRet);
- if (this.textCache != null)
+ // If this is below the module, there's bound to be a problem with the sig/resolution... Let's not save it
+ // TODO: THIS IS A HACK! FIX THE ROOT CAUSE!
+ if (this.textCache != null && scanRet.ToInt64() >= this.Module.BaseAddress.ToInt64())
{
this.textCache[signature] = scanRet.ToInt64() - this.Module.BaseAddress.ToInt64();
}
@@ -377,7 +379,7 @@ namespace Dalamud.Game
}
catch (Exception e)
{
- Log.Warning(e, "Failed to save cache to {0}", this.cacheFile);
+ Log.Warning(e, "Failed to save cache to {CachePath}", this.cacheFile);
}
}
@@ -532,7 +534,17 @@ namespace Dalamud.Game
return;
}
- this.textCache = JsonConvert.DeserializeObject>(File.ReadAllText(this.cacheFile.FullName)) ?? new ConcurrentDictionary();
+ try
+ {
+ this.textCache =
+ JsonConvert.DeserializeObject>(
+ File.ReadAllText(this.cacheFile.FullName)) ?? new ConcurrentDictionary();
+ }
+ catch (Exception ex)
+ {
+ this.textCache = new ConcurrentDictionary();
+ Log.Error(ex, "Couldn't load cached sigs");
+ }
}
}
}
diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs
index 9f56b8b31..104d4f5a7 100644
--- a/Dalamud/Hooking/Hook.cs
+++ b/Dalamud/Hooking/Hook.cs
@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
+using Dalamud.Configuration.Internal;
using Dalamud.Hooking.Internal;
namespace Dalamud.Hooking
@@ -59,6 +60,9 @@ namespace Dalamud.Hooking
[Obsolete("Use Hook.FromAddress instead.")]
private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly)
{
+ if (EnvironmentConfiguration.DalamudForceMinHook)
+ useMinHook = true;
+
address = HookManager.FollowJmp(address);
if (useMinHook)
this.compatHookImpl = new MinHookHook(address, detour, callingAssembly);
@@ -214,6 +218,9 @@ namespace Dalamud.Hooking
/// The hook with the supplied parameters.
public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook)
{
+ if (EnvironmentConfiguration.DalamudForceMinHook)
+ useMinHook = true;
+
var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName);
if (moduleHandle == IntPtr.Zero)
throw new Exception($"Could not get a handle to module {moduleName}");
@@ -240,6 +247,9 @@ namespace Dalamud.Hooking
/// The hook with the supplied parameters.
public static Hook FromAddress(IntPtr procAddress, T detour, bool useMinHook = false)
{
+ if (EnvironmentConfiguration.DalamudForceMinHook)
+ useMinHook = true;
+
procAddress = HookManager.FollowJmp(procAddress);
if (useMinHook)
return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly());
diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs
index b87ab1796..51047b97e 100644
--- a/Dalamud/Hooking/Internal/HookManager.cs
+++ b/Dalamud/Hooking/Internal/HookManager.cs
@@ -68,6 +68,9 @@ namespace Dalamud.Hooking.Internal
return address;
}
+ if (address.ToInt64() <= 0)
+ throw new InvalidOperationException($"Address was <= 0, this can't be happening?! ({address:X})");
+
var bytes = MemoryHelper.ReadRaw(address, 8);
var codeReader = new ByteArrayCodeReader(bytes);
diff --git a/Dalamud/Hooking/Internal/ReloadedHook.cs b/Dalamud/Hooking/Internal/ReloadedHook.cs
index d82b452a3..28c22b4f8 100644
--- a/Dalamud/Hooking/Internal/ReloadedHook.cs
+++ b/Dalamud/Hooking/Internal/ReloadedHook.cs
@@ -29,6 +29,8 @@ namespace Dalamud.Hooking.Internal
}
this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64());
+ this.hookImpl.Activate();
+ this.hookImpl.Disable();
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
}
@@ -75,9 +77,6 @@ namespace Dalamud.Hooking.Internal
lock (HookManager.HookEnableSyncRoot)
{
- if (!this.hookImpl.IsHookActivated)
- this.hookImpl.Activate();
-
if (!this.hookImpl.IsHookEnabled)
this.hookImpl.Enable();
}
diff --git a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs
new file mode 100644
index 000000000..bdf14b430
--- /dev/null
+++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs
@@ -0,0 +1,71 @@
+using System.Numerics;
+
+using ImGuiNET;
+
+namespace Dalamud.Interface.Components
+{
+ ///
+ /// Component for toggle buttons.
+ ///
+ public static partial class ImGuiComponents
+ {
+ ///
+ /// Draw a toggle button.
+ ///
+ /// The id of the button.
+ /// The state of the switch.
+ /// If the button has been interacted with this frame.
+ public static bool ToggleButton(string id, ref bool v)
+ {
+ var colors = ImGui.GetStyle().Colors;
+ var p = ImGui.GetCursorScreenPos();
+ var drawList = ImGui.GetWindowDrawList();
+
+ var height = ImGui.GetFrameHeight();
+ var width = height * 1.55f;
+ var radius = height * 0.50f;
+
+ // TODO: animate
+
+ var changed = false;
+ ImGui.InvisibleButton(id, new Vector2(width, height));
+ if (ImGui.IsItemClicked())
+ {
+ v = !v;
+ changed = true;
+ }
+
+ if (ImGui.IsItemHovered())
+ drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f);
+ else
+ drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f);
+ drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1)));
+
+ return changed;
+ }
+
+ ///
+ /// Draw a disabled toggle button.
+ ///
+ /// The id of the button.
+ /// The state of the switch.
+ public static void DisabledToggleButton(string id, bool v)
+ {
+ var colors = ImGui.GetStyle().Colors;
+ var p = ImGui.GetCursorScreenPos();
+ var drawList = ImGui.GetWindowDrawList();
+
+ var height = ImGui.GetFrameHeight();
+ var width = height * 1.55f;
+ var radius = height * 0.50f;
+
+ // TODO: animate
+ ImGui.InvisibleButton(id, new Vector2(width, height));
+
+ var dimFactor = 0.5f;
+
+ drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f);
+ drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor));
+ }
+ }
+}
diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs
index fa1098be7..af71fe67f 100644
--- a/Dalamud/Interface/GameFonts/GameFontManager.cs
+++ b/Dalamud/Interface/GameFonts/GameFontManager.cs
@@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Data;
+using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.Utility.Timing;
using ImGuiNET;
@@ -184,16 +185,9 @@ namespace Dalamud.Interface.GameFonts
needRebuild = !this.fonts.ContainsKey(style);
if (needRebuild)
{
- if (interfaceManager.IsBuildingFontsBeforeAtlasBuild && this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild)
- {
- Log.Information("[GameFontManager] NewFontRef: Building {0} right now, as it is called while BuildFonts is already in progress yet atlas build has not been called yet.", style.ToString());
- this.EnsureFont(style);
- }
- else
- {
- Log.Information("[GameFontManager] NewFontRef: Calling RebuildFonts because {0} has been requested.", style.ToString());
- interfaceManager.RebuildFonts();
- }
+ Log.Information("[GameFontManager] NewFontRef: Queueing RebuildFonts because {0} has been requested.", style.ToString());
+ Service.GetAsync()
+ .ContinueWith(task => task.Result.RunOnTick(() => interfaceManager.RebuildFonts()));
}
return new(this, style);
diff --git a/Dalamud/Interface/ImGuiHelpers.cs b/Dalamud/Interface/ImGuiHelpers.cs
index cc24af087..0ae672253 100644
--- a/Dalamud/Interface/ImGuiHelpers.cs
+++ b/Dalamud/Interface/ImGuiHelpers.cs
@@ -4,7 +4,9 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
+using Dalamud.Game.ClientState.Keys;
using ImGuiNET;
+using ImGuiScene;
namespace Dalamud.Interface
{
@@ -222,6 +224,56 @@ namespace Dalamud.Interface
target.Value!.BuildLookupTable();
}
+ ///
+ /// Map a VirtualKey keycode to an ImGuiKey enum value.
+ ///
+ /// The VirtualKey value to retrieve the ImGuiKey counterpart for.
+ /// The ImGuiKey that corresponds to this VirtualKey, or ImGuiKey.None otherwise.
+ public static ImGuiKey VirtualKeyToImGuiKey(VirtualKey key)
+ {
+ return ImGui_Input_Impl_Direct.VirtualKeyToImGuiKey((int)key);
+ }
+
+ ///
+ /// Map an ImGuiKey enum value to a VirtualKey code.
+ ///
+ /// The ImGuiKey value to retrieve the VirtualKey counterpart for.
+ /// The VirtualKey that corresponds to this ImGuiKey, or VirtualKey.NO_KEY otherwise.
+ public static VirtualKey ImGuiKeyToVirtualKey(ImGuiKey key)
+ {
+ return (VirtualKey)ImGui_Input_Impl_Direct.ImGuiKeyToVirtualKey(key);
+ }
+
+ ///
+ /// Show centered text.
+ ///
+ /// Text to show.
+ public static void CenteredText(string text)
+ {
+ CenterCursorForText(text);
+ ImGui.TextUnformatted(text);
+ }
+
+ ///
+ /// Center the ImGui cursor for a certain text.
+ ///
+ /// The text to center for.
+ public static void CenterCursorForText(string text)
+ {
+ var textWidth = ImGui.CalcTextSize(text).X;
+ CenterCursorFor((int)textWidth);
+ }
+
+ ///
+ /// Center the ImGui cursor for an item with a certain width.
+ ///
+ /// The width to center for.
+ public static void CenterCursorFor(int itemWidth)
+ {
+ var window = (int)ImGui.GetWindowWidth();
+ ImGui.SetCursorPosX((window / 2) - (itemWidth / 2));
+ }
+
///
/// Get data needed for each new frame.
///
diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs
index 11c8cb7f0..c58b99baa 100644
--- a/Dalamud/Interface/Internal/DalamudCommands.cs
+++ b/Dalamud/Interface/Internal/DalamudCommands.cs
@@ -72,6 +72,18 @@ namespace Dalamud.Interface.Internal
ShowInHelp = false,
});
+ commandManager.AddHandler("/xlstats", new CommandInfo(this.OnTogglePluginStats)
+ {
+ HelpMessage = Loc.Localize("DalamudPluginStats", "Draw plugin statistics window"),
+ ShowInHelp = false,
+ });
+
+ commandManager.AddHandler("/xlbranch", new CommandInfo(this.OnToggleBranchSwitcher)
+ {
+ HelpMessage = Loc.Localize("DalamudBranchSwitcher", "Draw branch switcher"),
+ ShowInHelp = false,
+ });
+
commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu)
{
HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"),
@@ -267,6 +279,16 @@ namespace Dalamud.Interface.Internal
Service.Get().ToggleDevMenu();
}
+ private void OnTogglePluginStats(string command, string arguments)
+ {
+ Service.Get().TogglePluginStatsWindow();
+ }
+
+ private void OnToggleBranchSwitcher(string command, string arguments)
+ {
+ Service.Get().ToggleBranchSwitcher();
+ }
+
private void OnDebugDrawDataMenu(string command, string arguments)
{
var dalamudInterface = Service.Get();
diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs
index 503771197..b985522a9 100644
--- a/Dalamud/Interface/Internal/DalamudInterface.cs
+++ b/Dalamud/Interface/Internal/DalamudInterface.cs
@@ -12,6 +12,7 @@ using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui;
using Dalamud.Game.Internal;
+using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Windows;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
@@ -249,6 +250,11 @@ namespace Dalamud.Interface.Internal
///
public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true;
+ ///
+ /// Opens the on the plugin changelogs.
+ ///
+ public void OpenPluginInstallerPluginChangelogs() => this.pluginWindow.OpenPluginChangelogs();
+
///
/// Opens the .
///
@@ -369,12 +375,17 @@ namespace Dalamud.Interface.Internal
/// Toggles the .
///
public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle();
-
+
///
/// Toggles the .
///
public void ToggleProfilerWindow() => this.profilerWindow.Toggle();
+ ///
+ /// Toggles the .
+ ///
+ public void ToggleBranchSwitcher() => this.branchSwitcherWindow.Toggle();
+
#endregion
private void OnDraw()
@@ -438,12 +449,9 @@ namespace Dalamud.Interface.Internal
if (!this.isImGuiDrawDevMenu && !condition.Any())
{
- var config = Service.Get();
-
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
- ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1));
ImGui.PushStyleColor(ImGuiCol.TextSelectedBg, new Vector4(0, 0, 0, 1));
ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0, 0, 0, 1));
ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 1));
@@ -467,8 +475,26 @@ namespace Dalamud.Interface.Internal
ImGui.End();
}
+ if (EnvironmentConfiguration.DalamudForceMinHook)
+ {
+ ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always);
+ ImGui.SetNextWindowBgAlpha(1);
+
+ if (ImGui.Begin(
+ "Disclaimer",
+ ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground |
+ ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove |
+ ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoMouseInputs |
+ ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings))
+ {
+ ImGui.TextColored(ImGuiColors.DalamudRed, "Is force MinHook!");
+ }
+
+ ImGui.End();
+ }
+
ImGui.PopStyleVar(4);
- ImGui.PopStyleColor(8);
+ ImGui.PopStyleColor(7);
}
}
@@ -594,6 +620,16 @@ namespace Dalamud.Interface.Internal
Service.Get().Unload();
}
+ if (ImGui.MenuItem("Restart game"))
+ {
+ [DllImport("kernel32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static extern void RaiseException(uint dwExceptionCode, uint dwExceptionFlags, uint nNumberOfArguments, IntPtr lpArguments);
+
+ RaiseException(0x12345678, 0, 0, IntPtr.Zero);
+ Process.GetCurrentProcess().Kill();
+ }
+
if (ImGui.MenuItem("Kill game"))
{
Process.GetCurrentProcess().Kill();
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 6b2145c85..aae840239 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -1030,22 +1030,43 @@ namespace Dalamud.Interface.Internal
if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
{
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = gamepadState.Raw(GamepadButtons.South);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = gamepadState.Raw(GamepadButtons.East);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = gamepadState.Raw(GamepadButtons.North);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = gamepadState.Raw(GamepadButtons.West);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = gamepadState.Raw(GamepadButtons.DpadLeft);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = gamepadState.Raw(GamepadButtons.DpadRight);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = gamepadState.Raw(GamepadButtons.DpadUp);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = gamepadState.Raw(GamepadButtons.DpadDown);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = gamepadState.LeftStickLeft;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = gamepadState.LeftStickRight;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = gamepadState.LeftStickUp;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = gamepadState.LeftStickDown;
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = gamepadState.Raw(GamepadButtons.L1);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = gamepadState.Raw(GamepadButtons.R1);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = gamepadState.Raw(GamepadButtons.L2);
- ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = gamepadState.Raw(GamepadButtons.R2);
+ var northButton = gamepadState.Raw(GamepadButtons.North) != 0;
+ var eastButton = gamepadState.Raw(GamepadButtons.East) != 0;
+ var southButton = gamepadState.Raw(GamepadButtons.South) != 0;
+ var westButton = gamepadState.Raw(GamepadButtons.West) != 0;
+ var dPadUp = gamepadState.Raw(GamepadButtons.DpadUp) != 0;
+ var dPadRight = gamepadState.Raw(GamepadButtons.DpadRight) != 0;
+ var dPadDown = gamepadState.Raw(GamepadButtons.DpadDown) != 0;
+ var dPadLeft = gamepadState.Raw(GamepadButtons.DpadLeft) != 0;
+ var leftStickUp = gamepadState.LeftStickUp;
+ var leftStickRight = gamepadState.LeftStickRight;
+ var leftStickDown = gamepadState.LeftStickDown;
+ var leftStickLeft = gamepadState.LeftStickLeft;
+ var l1Button = gamepadState.Raw(GamepadButtons.L1) != 0;
+ var l2Button = gamepadState.Raw(GamepadButtons.L2) != 0;
+ var r1Button = gamepadState.Raw(GamepadButtons.R1) != 0;
+ var r2Button = gamepadState.Raw(GamepadButtons.R2) != 0;
+
+ var io = ImGui.GetIO();
+ io.AddKeyEvent(ImGuiKey.GamepadFaceUp, northButton);
+ io.AddKeyEvent(ImGuiKey.GamepadFaceRight, eastButton);
+ io.AddKeyEvent(ImGuiKey.GamepadFaceDown, southButton);
+ io.AddKeyEvent(ImGuiKey.GamepadFaceLeft, westButton);
+
+ io.AddKeyEvent(ImGuiKey.GamepadDpadUp, dPadUp);
+ io.AddKeyEvent(ImGuiKey.GamepadDpadRight, dPadRight);
+ io.AddKeyEvent(ImGuiKey.GamepadDpadDown, dPadDown);
+ io.AddKeyEvent(ImGuiKey.GamepadDpadLeft, dPadLeft);
+
+ io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickUp, leftStickUp != 0, leftStickUp);
+ io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickRight, leftStickRight != 0, leftStickRight);
+ io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickDown, leftStickDown != 0, leftStickDown);
+ io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickLeft, leftStickLeft != 0, leftStickLeft);
+
+ io.AddKeyEvent(ImGuiKey.GamepadL1, l1Button);
+ io.AddKeyEvent(ImGuiKey.GamepadL2, l2Button);
+ io.AddKeyEvent(ImGuiKey.GamepadR1, r1Button);
+ io.AddKeyEvent(ImGuiKey.GamepadR2, r2Button);
if (gamepadState.Pressed(GamepadButtons.R3) > 0)
{
diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
index bea3bdd77..43661c9ab 100644
--- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
@@ -43,6 +43,7 @@ namespace Dalamud.Interface.Internal.Windows
public const int PluginIconHeight = 512;
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}";
+ private const string MainRepoDip17ImageUrl = "https://raw.githubusercontent.com/goatcorp/PluginDistD17/main/{0}/{1}/images/{2}";
private readonly BlockingCollection>> downloadQueue = new();
private readonly BlockingCollection> loadQueue = new();
@@ -54,6 +55,7 @@ namespace Dalamud.Interface.Internal.Windows
private readonly ConcurrentDictionary pluginImagesMap = new();
private readonly Task emptyTextureTask;
+ private readonly Task disabledIconTask;
private readonly Task defaultIconTask;
private readonly Task troubleIconTask;
private readonly Task updateIconTask;
@@ -71,6 +73,7 @@ namespace Dalamud.Interface.Internal.Windows
this.emptyTextureTask = imwst.ContinueWith(task => task.Result.Manager.LoadImageRaw(new byte[64], 8, 8, 4)!);
this.defaultIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))) ?? this.emptyTextureTask).Unwrap();
+ this.disabledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "disabledIcon.png"))) ?? this.emptyTextureTask).Unwrap();
this.troubleIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))) ?? this.emptyTextureTask).Unwrap();
this.updateIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))) ?? this.emptyTextureTask).Unwrap();
this.installedIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))) ?? this.emptyTextureTask).Unwrap();
@@ -91,6 +94,13 @@ namespace Dalamud.Interface.Internal.Windows
? this.emptyTextureTask.Result
: this.emptyTextureTask.GetAwaiter().GetResult();
+ ///
+ /// Gets the default plugin icon.
+ ///
+ public TextureWrap DisabledIcon => this.disabledIconTask.IsCompleted
+ ? this.disabledIconTask.Result
+ : this.disabledIconTask.GetAwaiter().GetResult();
+
///
/// Gets the default plugin icon.
///
@@ -644,6 +654,9 @@ namespace Dalamud.Interface.Internal.Windows
if (isThirdParty)
return manifest.IconUrl;
+ if (manifest.IsDip17Plugin)
+ return MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, "icon.png");
+
return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png");
}
@@ -663,7 +676,14 @@ namespace Dalamud.Interface.Internal.Windows
var output = new List();
for (var i = 1; i <= 5; i++)
{
- output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png"));
+ if (manifest.IsDip17Plugin)
+ {
+ output.Add(MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, $"image{i}.png"));
+ }
+ else
+ {
+ output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png"));
+ }
}
return output;
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs
index 0326081f5..1133090e0 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs
@@ -21,10 +21,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (plugin.Manifest.Changelog.IsNullOrEmpty())
throw new ArgumentException("Manifest has no changelog.");
- var version = plugin.AssemblyName?.Version;
- version ??= plugin.Manifest.Testing
- ? plugin.Manifest.TestingAssemblyVersion
- : plugin.Manifest.AssemblyVersion;
+ var version = plugin.Manifest.EffectiveVersion;
this.Version = version!.ToString();
}
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
index c670c9fa9..4a08ffe06 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
@@ -14,6 +14,7 @@ using Dalamud.Game.Command;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.Notifications;
+using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing;
using Dalamud.Logging.Internal;
using Dalamud.Plugin;
@@ -59,14 +60,17 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
private bool errorModalDrawing = true;
private bool errorModalOnNextFrame = false;
private string errorModalMessage = string.Empty;
+ private TaskCompletionSource? errorModalTaskCompletionSource;
private bool feedbackModalDrawing = true;
private bool feedbackModalOnNextFrame = false;
+ private bool feedbackModalOnNextFrameDontClear = false;
private string feedbackModalBody = string.Empty;
private string feedbackModalContact = string.Empty;
private bool feedbackModalIncludeException = false;
private PluginManifest? feedbackPlugin = null;
private bool feedbackIsTesting = false;
+ private bool feedbackIsAnonymous = false;
private int updatePluginCount = 0;
private List? updatedPlugins;
@@ -83,6 +87,9 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
private OperationStatus installStatus = OperationStatus.Idle;
private OperationStatus updateStatus = OperationStatus.Idle;
+ private OperationStatus enableDisableStatus = OperationStatus.Idle;
+
+ private LoadingIndicatorKind loadingIndicatorKind = LoadingIndicatorKind.Unknown;
///
/// Initializes a new instance of the class.
@@ -130,6 +137,17 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
Complete,
}
+ private enum LoadingIndicatorKind
+ {
+ Unknown,
+ EnablingSingle,
+ DisablingSingle,
+ UpdatingSingle,
+ UpdatingAll,
+ Installing,
+ Manager,
+ }
+
private enum PluginSortKind
{
Alphabetical,
@@ -139,6 +157,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
NotInstalled,
}
+ private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress ||
+ this.updateStatus == OperationStatus.InProgress ||
+ this.enableDisableStatus == OperationStatus.InProgress;
+
///
public void Dispose()
{
@@ -184,6 +206,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
this.DrawFooter();
this.DrawErrorModal();
this.DrawFeedbackModal();
+ this.DrawProgressOverlay();
}
///
@@ -194,6 +217,119 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
this.imageCache.ClearIconCache();
}
+ ///
+ /// Open the window on the plugin changelogs.
+ ///
+ public void OpenPluginChangelogs()
+ {
+ this.categoryManager.CurrentGroupIdx = 3;
+ this.categoryManager.CurrentCategoryIdx = 2;
+ this.IsOpen = true;
+ }
+
+ private void DrawProgressOverlay()
+ {
+ var pluginManager = Service.Get();
+
+ var isWaitingManager = !pluginManager.PluginsReady ||
+ !pluginManager.ReposReady;
+ var isLoading = this.AnyOperationInProgress ||
+ isWaitingManager;
+
+ if (isWaitingManager)
+ this.loadingIndicatorKind = LoadingIndicatorKind.Manager;
+
+ if (!isLoading)
+ return;
+
+ ImGui.SetCursorPos(Vector2.Zero);
+
+ var windowSize = ImGui.GetWindowSize();
+ var titleHeight = ImGui.GetFontSize() + (ImGui.GetStyle().FramePadding.Y * 2);
+
+ if (ImGui.BeginChild("###installerLoadingFrame", new Vector2(-1, -1), false))
+ {
+ ImGui.GetWindowDrawList().PushClipRectFullScreen();
+ ImGui.GetWindowDrawList().AddRectFilled(
+ ImGui.GetWindowPos() + new Vector2(0, titleHeight),
+ ImGui.GetWindowPos() + windowSize,
+ 0xCC000000,
+ ImGui.GetStyle().WindowRounding,
+ ImDrawFlags.RoundCornersBottom);
+ ImGui.PopClipRect();
+
+ ImGui.SetCursorPosY(windowSize.Y / 2);
+
+ switch (this.loadingIndicatorKind)
+ {
+ case LoadingIndicatorKind.Unknown:
+ ImGuiHelpers.CenteredText("Doing something, not sure what!");
+ break;
+ case LoadingIndicatorKind.EnablingSingle:
+ ImGuiHelpers.CenteredText("Enabling plugin...");
+ break;
+ case LoadingIndicatorKind.DisablingSingle:
+ ImGuiHelpers.CenteredText("Disabling plugin...");
+ break;
+ case LoadingIndicatorKind.UpdatingSingle:
+ ImGuiHelpers.CenteredText("Updating plugin...");
+ break;
+ case LoadingIndicatorKind.UpdatingAll:
+ ImGuiHelpers.CenteredText("Updating plugins...");
+ break;
+ case LoadingIndicatorKind.Installing:
+ ImGuiHelpers.CenteredText("Installing plugin...");
+ break;
+ case LoadingIndicatorKind.Manager:
+ {
+ if (pluginManager.PluginsReady && !pluginManager.ReposReady)
+ {
+ ImGuiHelpers.CenteredText("Loading repositories...");
+ }
+ else if (!pluginManager.PluginsReady && pluginManager.ReposReady)
+ {
+ ImGuiHelpers.CenteredText("Loading installed plugins...");
+ }
+ else
+ {
+ ImGuiHelpers.CenteredText("Loading repositories and plugins...");
+ }
+
+ var currentProgress = 0;
+ var total = 0;
+
+ var pendingRepos = pluginManager.Repos.ToArray()
+ .Where(x => (x.State != PluginRepositoryState.Success &&
+ x.State != PluginRepositoryState.Fail) &&
+ x.IsEnabled)
+ .ToArray();
+ var allRepoCount =
+ pluginManager.Repos.Count(x => x.State != PluginRepositoryState.Fail && x.IsEnabled);
+
+ foreach (var repo in pendingRepos)
+ {
+ ImGuiHelpers.CenteredText($"{repo.PluginMasterUrl}: {repo.State}");
+ }
+
+ currentProgress += allRepoCount - pendingRepos.Length;
+ total += allRepoCount;
+
+ if (currentProgress != total)
+ {
+ ImGui.SetCursorPosX(windowSize.X / 3);
+ ImGui.ProgressBar(currentProgress / (float)total, new Vector2(windowSize.X / 3, 50));
+ }
+ }
+
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ ImGui.EndChild();
+ }
+ }
+
private void DrawHeader()
{
var style = ImGui.GetStyle();
@@ -201,8 +337,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale));
- var searchInputWidth = 240 * ImGuiHelpers.GlobalScale;
- var searchClearButtonWidth = 40 * ImGuiHelpers.GlobalScale;
+ var searchInputWidth = 180 * ImGuiHelpers.GlobalScale;
+ var searchClearButtonWidth = 25 * ImGuiHelpers.GlobalScale;
var sortByText = Locs.SortBy_Label;
var sortByTextWidth = ImGui.CalcTextSize(sortByText).X;
@@ -225,9 +361,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
ImGui.SameLine();
// Shift down a little to align with the middle of the header text
- ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2);
+ var downShift = ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2;
+ ImGui.SetCursorPosY(downShift);
- ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth - searchClearButtonWidth);
+ ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - (style.ItemSpacing.X * 2) - searchInputWidth - searchClearButtonWidth);
var searchTextChanged = false;
ImGui.SetNextItemWidth(searchInputWidth);
@@ -238,6 +375,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
100);
ImGui.SameLine();
+ ImGui.SetCursorPosY(downShift);
ImGui.SetNextItemWidth(searchClearButtonWidth);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
@@ -250,7 +388,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
this.UpdateCategoriesOnSearchChange();
ImGui.SameLine();
- ImGui.SetCursorPosX(windowSize.X - sortSelectWidth);
+ ImGui.SetCursorPosY(downShift);
ImGui.SetNextItemWidth(selectableWidth);
if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton))
{
@@ -336,8 +474,9 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (ImGui.Button(Locs.FooterButton_UpdatePlugins))
{
this.updateStatus = OperationStatus.InProgress;
+ this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll;
- Task.Run(() => pluginManager.UpdatePluginsAsync())
+ Task.Run(() => pluginManager.UpdatePluginsAsync(true, false))
.ContinueWith(task =>
{
this.updateStatus = OperationStatus.Complete;
@@ -372,7 +511,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (this.updatePluginCount > 0)
{
- PluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox);
+ Service.Get().PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox);
notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success);
var installedGroupIdx = this.categoryManager.GroupList.TakeWhile(
@@ -404,6 +543,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40)))
{
ImGui.CloseCurrentPopup();
+ errorModalTaskCompletionSource?.SetResult();
}
ImGui.EndPopup();
@@ -446,7 +586,43 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
ImGui.Spacing();
- ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100);
+ if (ImGui.Checkbox(Locs.FeedbackModal_ContactAnonymous, ref this.feedbackIsAnonymous))
+ {
+ if (this.feedbackIsAnonymous)
+ this.feedbackModalContact = string.Empty;
+ }
+
+ if (this.feedbackIsAnonymous)
+ {
+ ImGui.BeginDisabled();
+ ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 0);
+ ImGui.EndDisabled();
+ ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
+ ImGui.Text(Locs.FeedbackModal_ContactAnonymousWarning);
+ ImGui.PopStyleColor();
+ }
+ else
+ {
+ ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100);
+
+ ImGui.SameLine();
+
+ if (ImGui.Button(Locs.FeedbackModal_ContactInformationDiscordButton))
+ {
+ Process.Start(new ProcessStartInfo(Locs.FeedbackModal_ContactInformationDiscordUrl)
+ {
+ UseShellExecute = true,
+ });
+ }
+
+ ImGui.Text(Locs.FeedbackModal_ContactInformationHelp);
+
+ ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
+ ImGui.Text(Locs.FeedbackModal_ContactInformationWarning);
+ ImGui.PopStyleColor();
+ }
+
+ ImGui.Spacing();
ImGui.Checkbox(Locs.FeedbackModal_IncludeLastError, ref this.feedbackModalIncludeException);
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_IncludeLastErrorHint);
@@ -460,25 +636,57 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40)))
{
- if (this.feedbackPlugin != null)
+ if (!this.feedbackIsAnonymous && string.IsNullOrWhiteSpace(this.feedbackModalContact))
{
- Task.Run(async () => await BugBait.SendFeedback(this.feedbackPlugin, this.feedbackIsTesting, this.feedbackModalBody, this.feedbackModalContact, this.feedbackModalIncludeException))
- .ContinueWith(
- t =>
- {
- var notif = Service.Get();
- if (t.IsCanceled || t.IsFaulted)
- notif.AddNotification(Locs.FeedbackModal_NotificationError, Locs.FeedbackModal_Title, NotificationType.Error);
- else
- notif.AddNotification(Locs.FeedbackModal_NotificationSuccess, Locs.FeedbackModal_Title, NotificationType.Success);
- });
+ this.ShowErrorModal(Locs.FeedbackModal_ContactInformationRequired)
+ .ContinueWith(_ =>
+ {
+ this.feedbackModalOnNextFrameDontClear = true;
+ this.feedbackModalOnNextFrame = true;
+ });
}
else
{
- Log.Error("FeedbackPlugin was null.");
- }
+ if (this.feedbackPlugin != null)
+ {
+ Task.Run(async () => await BugBait.SendFeedback(
+ this.feedbackPlugin,
+ this.feedbackIsTesting,
+ this.feedbackModalBody,
+ this.feedbackModalContact,
+ this.feedbackModalIncludeException))
+ .ContinueWith(
+ t =>
+ {
+ var notif = Service.Get();
+ if (t.IsCanceled || t.IsFaulted)
+ {
+ notif.AddNotification(
+ Locs.FeedbackModal_NotificationError,
+ Locs.FeedbackModal_Title,
+ NotificationType.Error);
+ }
+ else
+ {
+ notif.AddNotification(
+ Locs.FeedbackModal_NotificationSuccess,
+ Locs.FeedbackModal_Title,
+ NotificationType.Success);
+ }
+ });
+ }
+ else
+ {
+ Log.Error("FeedbackPlugin was null.");
+ }
- ImGui.CloseCurrentPopup();
+ if (!string.IsNullOrWhiteSpace(this.feedbackModalContact))
+ {
+ Service.Get().LastFeedbackContactDetails = this.feedbackModalContact;
+ }
+
+ ImGui.CloseCurrentPopup();
+ }
}
ImGui.EndPopup();
@@ -489,9 +697,17 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
ImGui.OpenPopup(modalTitle);
this.feedbackModalOnNextFrame = false;
this.feedbackModalDrawing = true;
- this.feedbackModalBody = string.Empty;
- this.feedbackModalContact = string.Empty;
- this.feedbackModalIncludeException = false;
+ if (!this.feedbackModalOnNextFrameDontClear)
+ {
+ this.feedbackModalBody = string.Empty;
+ this.feedbackModalContact = Service.Get().LastFeedbackContactDetails;
+ this.feedbackModalIncludeException = false;
+ this.feedbackIsAnonymous = false;
+ }
+ else
+ {
+ this.feedbackModalOnNextFrameDontClear = false;
+ }
}
}
@@ -1104,6 +1320,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
ImGui.SetCursorPos(startCursor);
+ var pluginDisabled = plugin is { IsDisabled: true };
+
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
var cursorBeforeImage = ImGui.GetCursorPos();
var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos();
@@ -1116,7 +1334,18 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
iconTex = cachedIconTex;
}
+ if (pluginDisabled)
+ {
+ ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
+ }
+
ImGui.Image(iconTex.ImGuiHandle, iconSize);
+
+ if (pluginDisabled)
+ {
+ ImGui.PopStyleVar();
+ }
+
ImGui.SameLine();
ImGui.SetCursorPos(cursorBeforeImage);
}
@@ -1125,8 +1354,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (updateAvailable)
ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize);
- else if (trouble)
+ else if (trouble && !pluginDisabled)
ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize);
+ else if (pluginDisabled)
+ ImGui.Image(this.imageCache.DisabledIcon.ImGuiHandle, iconSize);
else if (isLoaded && isThirdParty)
ImGui.Image(this.imageCache.ThirdInstalledIcon.ImGuiHandle, iconSize);
else if (isThirdParty)
@@ -1357,6 +1588,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (ImGui.Button($"{buttonText}##{buttonText}{index}"))
{
this.installStatus = OperationStatus.InProgress;
+ this.loadingIndicatorKind = LoadingIndicatorKind.Installing;
Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting, PluginLoadReason.Installer))
.ContinueWith(task =>
@@ -1450,14 +1682,18 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
{
var configuration = Service.Get();
var commandManager = Service.Get();
- var pluginManager = Service.Get();
- var startInfo = Service.Get();
var trouble = false;
// Name
var label = plugin.Manifest.Name;
+ // Dev
+ if (plugin.IsDev)
+ {
+ label += Locs.PluginTitleMod_DevPlugin;
+ }
+
// Testing
if (plugin.Manifest.Testing)
{
@@ -1478,7 +1714,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
}
// Load error
- if (plugin.State is PluginState.LoadError or PluginState.DependencyResolutionFailed && plugin.CheckPolicy())
+ if (plugin.State is PluginState.LoadError or PluginState.DependencyResolutionFailed && plugin.CheckPolicy()
+ && !plugin.IsOutdated && !plugin.IsBanned && !plugin.IsOrphaned)
{
label += Locs.PluginTitleMod_LoadError;
trouble = true;
@@ -1538,6 +1775,12 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
trouble = true;
}
+ // Scheduled for deletion
+ if (plugin.Manifest.ScheduledForDeletion)
+ {
+ label += Locs.PluginTitleMod_ScheduledForDeletion;
+ }
+
ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}");
var hasChangelog = !plugin.Manifest.Changelog.IsNullOrEmpty();
@@ -1616,17 +1859,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
this.DrawUpdateSinglePluginButton(availablePluginUpdate);
ImGui.SameLine();
- var version = plugin.AssemblyName?.Version;
- version ??= plugin.Manifest.Testing
- ? plugin.Manifest.TestingAssemblyVersion
- : plugin.Manifest.AssemblyVersion;
- ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{version}");
-
- if (plugin.IsDev)
- {
- ImGui.SameLine();
- ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin);
- }
+ ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.EffectiveVersion}");
ImGuiHelpers.ScaledDummy(5);
@@ -1637,7 +1870,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (hasChangelog)
{
- if (ImGui.TreeNode($"Changelog (v{plugin.Manifest.AssemblyVersion})"))
+ if (ImGui.TreeNode($"Changelog (v{plugin.Manifest.EffectiveVersion})"))
{
this.DrawInstalledPluginChangelog(plugin.Manifest);
}
@@ -1701,10 +1934,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
private void DrawPluginControlButton(LocalPlugin plugin)
{
- var configuration = Service.Get();
var notifications = Service.Get();
var pluginManager = Service.Get();
- var startInfo = Service.Get();
// Disable everything if the updater is running or another plugin is operating
var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress;
@@ -1718,92 +1949,101 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
// Disable everything if the plugin failed to load
disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
- if (plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading)
- {
- ImGuiComponents.DisabledButton(Locs.PluginButton_Working);
- }
- else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed)
- {
- if (pluginManager.SafeMode)
- {
- ImGuiComponents.DisabledButton(Locs.PluginButton_SafeMode);
- }
- else if (disabled)
- {
- ImGuiComponents.DisabledButton(Locs.PluginButton_Disable);
- }
- else
- {
- if (ImGui.Button(Locs.PluginButton_Disable))
- {
- Task.Run(() =>
- {
- var unloadTask = Task.Run(() => plugin.UnloadAsync())
- .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name));
+ // Disable everything if we're working
+ disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading;
- unloadTask.Wait();
- if (!unloadTask.Result)
- return;
+ var toggleId = plugin.Manifest.InternalName;
+ var isLoadedAndUnloadable = plugin.State == PluginState.Loaded ||
+ plugin.State == PluginState.DependencyResolutionFailed;
- var disableTask = Task.Run(() => plugin.Disable())
- .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name));
+ StyleModelV1.DalamudStandard.Push();
- disableTask.Wait();
- if (!disableTask.Result)
- return;
-
- if (!plugin.IsDev)
- {
- pluginManager.RemovePlugin(plugin);
- }
-
- notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success);
- });
- }
- }
-
- if (plugin.State == PluginState.Loaded)
- {
- // Only if the plugin isn't broken.
- this.DrawOpenPluginSettingsButton(plugin);
- }
- }
- else if (plugin.State == PluginState.Unloaded)
- {
- if (disabled)
- {
- ImGuiComponents.DisabledButton(Locs.PluginButton_Load);
- }
- else
- {
- if (ImGui.Button(Locs.PluginButton_Load))
- {
- Task.Run(() =>
- {
- var enableTask = Task.Run(() => plugin.Enable())
- .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name));
-
- enableTask.Wait();
- if (!enableTask.Result)
- return;
-
- var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer))
- .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name));
-
- loadTask.Wait();
- if (!loadTask.Result)
- return;
- });
- }
- }
- }
- else if (plugin.State == PluginState.UnloadError)
+ if (plugin.State == PluginState.UnloadError)
{
ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown);
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed);
}
+ else if (disabled)
+ {
+ ImGuiComponents.DisabledToggleButton(toggleId, isLoadedAndUnloadable);
+ }
+ else
+ {
+ if (ImGuiComponents.ToggleButton(toggleId, ref isLoadedAndUnloadable))
+ {
+ if (!isLoadedAndUnloadable)
+ {
+ this.enableDisableStatus = OperationStatus.InProgress;
+ this.loadingIndicatorKind = LoadingIndicatorKind.DisablingSingle;
+
+ Task.Run(() =>
+ {
+ var unloadTask = Task.Run(() => plugin.UnloadAsync())
+ .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name));
+
+ unloadTask.Wait();
+ if (!unloadTask.Result)
+ {
+ this.enableDisableStatus = OperationStatus.Complete;
+ return;
+ }
+
+ var disableTask = Task.Run(() => plugin.Disable())
+ .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name));
+
+ disableTask.Wait();
+ this.enableDisableStatus = OperationStatus.Complete;
+
+ if (!disableTask.Result)
+ return;
+
+ notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success);
+ });
+ }
+ else
+ {
+ this.enableDisableStatus = OperationStatus.InProgress;
+ this.loadingIndicatorKind = LoadingIndicatorKind.EnablingSingle;
+
+ Task.Run(() =>
+ {
+ var enableTask = Task.Run(() => plugin.Enable())
+ .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name));
+
+ enableTask.Wait();
+ if (!enableTask.Result)
+ {
+ this.enableDisableStatus = OperationStatus.Complete;
+ return;
+ }
+
+ var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer))
+ .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name));
+
+ loadTask.Wait();
+ this.enableDisableStatus = OperationStatus.Complete;
+
+ if (!loadTask.Result)
+ return;
+
+ notifications.AddNotification(Locs.Notifications_PluginEnabled(plugin.Manifest.Name), Locs.Notifications_PluginEnabledTitle, NotificationType.Success);
+ });
+ }
+ }
+ }
+
+ StyleModelV1.DalamudStandard.Pop();
+
+ ImGui.SameLine();
+ ImGuiHelpers.ScaledDummy(15, 0);
+
+ if (plugin.State == PluginState.Loaded)
+ {
+ // Only if the plugin isn't broken.
+ this.DrawOpenPluginSettingsButton(plugin);
+ }
}
private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update)
@@ -1815,6 +2055,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (ImGuiComponents.IconButton(FontAwesomeIcon.Download))
{
this.installStatus = OperationStatus.InProgress;
+ this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingSingle;
Task.Run(async () => await pluginManager.UpdateSinglePluginAsync(update, true, false))
.ContinueWith(task =>
@@ -1883,6 +2124,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
private void DrawDevPluginButtons(LocalPlugin localPlugin)
{
+ ImGui.SameLine();
+
var configuration = Service.Get();
if (localPlugin is LocalDevPlugin plugin)
@@ -1931,18 +2174,20 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
private void DrawDeletePluginButton(LocalPlugin plugin)
{
- var unloaded = plugin.State == PluginState.Unloaded || plugin.State == PluginState.LoadError;
+ /*var unloaded = plugin.State == PluginState.Unloaded || plugin.State == PluginState.LoadError;
// When policy check fails, the plugin is never loaded
var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned || plugin.IsOrphaned || !plugin.CheckPolicy());
if (!showButton)
- return;
+ return;*/
var pluginManager = Service.Get();
+ var devNotDeletable = plugin.IsDev && plugin.State != PluginState.Unloaded && plugin.State != PluginState.DependencyResolutionFailed;
+
ImGui.SameLine();
- if (plugin.HasEverStartedLoad)
+ if (plugin.State == PluginState.Loaded || devNotDeletable)
{
ImGui.PushFont(InterfaceManager.IconFont);
ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString());
@@ -1950,7 +2195,9 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
if (ImGui.IsItemHovered())
{
- ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePluginRestricted);
+ ImGui.SetTooltip(plugin.State == PluginState.Loaded
+ ? Locs.PluginButtonToolTip_DeletePluginLoaded
+ : Locs.PluginButtonToolTip_DeletePluginRestricted);
}
}
else
@@ -1959,22 +2206,45 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
{
try
{
- plugin.DllFile.Delete();
- pluginManager.RemovePlugin(plugin);
+ if (plugin.IsDev)
+ {
+ plugin.DllFile.Delete();
+ }
+ else
+ {
+ plugin.ScheduleDeletion(!plugin.Manifest.ScheduledForDeletion);
+ }
+
+ if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed)
+ {
+ pluginManager.RemovePlugin(plugin);
+ }
}
catch (Exception ex)
{
Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}");
- this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name);
- this.errorModalDrawing = true;
- this.errorModalOnNextFrame = true;
+ this.ShowErrorModal(Locs.ErrorModal_DeleteFail(plugin.Name));
}
}
if (ImGui.IsItemHovered())
{
- ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin);
+ string tooltipMessage;
+ if (plugin.Manifest.ScheduledForDeletion)
+ {
+ tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduledCancel;
+ }
+ else if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed)
+ {
+ tooltipMessage = Locs.PluginButtonToolTip_DeletePlugin;
+ }
+ else
+ {
+ tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduled;
+ }
+
+ ImGui.SetTooltip(tooltipMessage);
}
}
}
@@ -2202,11 +2472,13 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
return true;
}
- private void ShowErrorModal(string message)
+ private Task ShowErrorModal(string message)
{
this.errorModalMessage = message;
this.errorModalDrawing = true;
this.errorModalOnNextFrame = true;
+ this.errorModalTaskCompletionSource = new TaskCompletionSource();
+ return this.errorModalTaskCompletionSource!.Task;
}
private void UpdateCategoriesOnSearchChange()
@@ -2299,6 +2571,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
public static string PluginTitleMod_TestingVersion => Loc.Localize("InstallerTestingVersion", " (testing version)");
+ public static string PluginTitleMod_DevPlugin => Loc.Localize("InstallerDevPlugin", " (dev plugin)");
+
public static string PluginTitleMod_UpdateFailed => Loc.Localize("InstallerUpdateFailed", " (update failed)");
public static string PluginTitleMod_LoadError => Loc.Localize("InstallerLoadError", " (load error)");
@@ -2308,9 +2582,11 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)");
public static string PluginTitleMod_BannedError => Loc.Localize("InstallerBannedError", " (automatically disabled)");
-
+
public static string PluginTitleMod_OrphanedError => Loc.Localize("InstallerOrphanedError", " (unknown repository)");
+ public static string PluginTitleMod_ScheduledForDeletion => Loc.Localize("InstallerScheduledForDeletion", " (scheduled for deletion)");
+
public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!");
#endregion
@@ -2340,9 +2616,6 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
public static string PluginBody_Plugin3rdPartyRepo(string url) => Loc.Localize("InstallerPlugin3rdPartyRepo", "From custom plugin repository {0}").Format(url);
public static string PluginBody_AvailableDevPlugin => Loc.Localize("InstallerDevPlugin", " This plugin is available in one of your repos, please remove it from the devPlugins folder.");
-
- public static string PluginBody_DeleteDevPlugin => Loc.Localize("InstallerDeleteDevPlugin ", " To delete this plugin, please remove it from the devPlugins folder.");
-
public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author.");
public static string PluginBody_Orphaned => Loc.Localize("InstallerOrphanedPluginBody ", "This plugin's source repository is no longer available. You may need to reinstall it from its repository, or re-add the repository.");
@@ -2384,7 +2657,13 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin");
- public static string PluginButtonToolTip_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete - please try restarting the game.");
+ public static string PluginButtonToolTip_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete right now - please restart the game.");
+
+ public static string PluginButtonToolTip_DeletePluginScheduled => Loc.Localize("InstallerDeletePluginScheduled", "Delete plugin on next restart");
+
+ public static string PluginButtonToolTip_DeletePluginScheduledCancel => Loc.Localize("InstallerDeletePluginScheduledCancel", "Cancel scheduled deletion");
+
+ public static string PluginButtonToolTip_DeletePluginLoaded => Loc.Localize("InstallerDeletePluginLoaded", "Disable this plugin before deleting it.");
public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL");
@@ -2416,6 +2695,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
public static string Notifications_PluginDisabled(string name) => Loc.Localize("NotificationsPluginDisabled", "'{0}' was disabled.").Format(name);
+ public static string Notifications_PluginEnabledTitle => Loc.Localize("NotificationsPluginEnabledTitle", "Plugin enabled!");
+
+ public static string Notifications_PluginEnabled(string name) => Loc.Localize("NotificationsPluginEnabled", "'{0}' was enabled.").Format(name);
+
#endregion
#region Footer
@@ -2478,12 +2761,26 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
public static string FeedbackModal_Title => Loc.Localize("InstallerFeedback", "Send Feedback");
- public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.\nYou can include your Discord tag or email address if you wish to give them the opportunity to answer.\n\nIf you put your Discord name into the contact information field, please join the XIVLauncher discord server so we can get in touch.").Format(pluginName);
+ public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.").Format(pluginName);
public static string FeedbackModal_HasUpdate => Loc.Localize("InstallerFeedbackHasUpdate", "A new version of this plugin is available, please update before reporting bugs.");
+ public static string FeedbackModal_ContactAnonymous => Loc.Localize("InstallerFeedbackContactAnonymous", "Submit feedback anonymously");
+
+ public static string FeedbackModal_ContactAnonymousWarning => Loc.Localize("InstallerFeedbackContactAnonymousWarning", "No response will be forthcoming.\nUntick \"{0}\" and provide contact information if you need help.").Format(FeedbackModal_ContactAnonymous);
+
public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information");
+ public static string FeedbackModal_ContactInformationHelp => Loc.Localize("InstallerFeedbackContactInfoHelp", "Discord usernames and e-mail addresses are accepted.\nIf you submit a Discord username, please join our discord server so that we can reach out to you easier.");
+
+ public static string FeedbackModal_ContactInformationWarning => Loc.Localize("InstallerFeedbackContactInfoWarning", "Do not submit in-game character names.");
+
+ public static string FeedbackModal_ContactInformationRequired => Loc.Localize("InstallerFeedbackContactInfoRequired", "Contact information has not been provided. If you do not want to provide contact information, tick on \"{0}\" above.").Format(FeedbackModal_ContactAnonymous);
+
+ public static string FeedbackModal_ContactInformationDiscordButton => Loc.Localize("ContactInformationDiscordButton", "Join Goat Place Discord");
+
+ public static string FeedbackModal_ContactInformationDiscordUrl => Loc.Localize("ContactInformationDiscordUrl", "https://goat.place/");
+
public static string FeedbackModal_IncludeLastError => Loc.Localize("InstallerFeedbackIncludeLastError", "Include last error message");
public static string FeedbackModal_IncludeLastErrorHint => Loc.Localize("InstallerFeedbackIncludeLastErrorHint", "This option can give the plugin developer useful feedback on what exactly went wrong.");
diff --git a/Dalamud/Interface/Style/StyleModel.cs b/Dalamud/Interface/Style/StyleModel.cs
index 978ef78dc..a66a8ff81 100644
--- a/Dalamud/Interface/Style/StyleModel.cs
+++ b/Dalamud/Interface/Style/StyleModel.cs
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
-
+using System.Numerics;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Colors;
using Dalamud.Utility;
+using ImGuiNET;
using Newtonsoft.Json;
using Serilog;
@@ -15,6 +16,10 @@ namespace Dalamud.Interface.Style
///
public abstract class StyleModel
{
+ private static int NumPushedStyles = 0;
+ private static int NumPushedColors = 0;
+ private static bool HasPushedOnce = false;
+
///
/// Gets or sets the name of the style model.
///
@@ -84,7 +89,7 @@ namespace Dalamud.Interface.Style
configuration.SavedStyles = new List();
configuration.SavedStyles.AddRange(configuration.SavedStylesOld);
- Log.Information("Transferred {0} styles", configuration.SavedStyles.Count);
+ Log.Information("Transferred {NumStyles} styles", configuration.SavedStyles.Count);
configuration.SavedStylesOld = null;
configuration.Save();
@@ -123,6 +128,60 @@ namespace Dalamud.Interface.Style
///
/// Pop this style model from the ImGui style/color stack.
///
- public abstract void Pop();
+ public void Pop()
+ {
+ if (!HasPushedOnce)
+ throw new InvalidOperationException("Wasn't pushed at least once.");
+
+ ImGui.PopStyleVar(NumPushedStyles);
+ ImGui.PopStyleColor(NumPushedColors);
+ }
+
+ ///
+ /// Push a style var.
+ ///
+ /// Style kind.
+ /// Style var.
+ protected void PushStyleHelper(ImGuiStyleVar style, float arg)
+ {
+ ImGui.PushStyleVar(style, arg);
+
+ if (!HasPushedOnce)
+ NumPushedStyles++;
+ }
+
+ ///
+ /// Push a style var.
+ ///
+ /// Style kind.
+ /// Style var.
+ protected void PushStyleHelper(ImGuiStyleVar style, Vector2 arg)
+ {
+ ImGui.PushStyleVar(style, arg);
+
+ if (!HasPushedOnce)
+ NumPushedStyles++;
+ }
+
+ ///
+ /// Push a style color.
+ ///
+ /// Color kind.
+ /// Color value.
+ protected void PushColorHelper(ImGuiCol color, Vector4 value)
+ {
+ ImGui.PushStyleColor(color, value);
+
+ if (!HasPushedOnce)
+ NumPushedColors++;
+ }
+
+ ///
+ /// Indicate that you have pushed.
+ ///
+ protected void DonePushing()
+ {
+ HasPushedOnce = true;
+ }
}
}
diff --git a/Dalamud/Interface/Style/StyleModelV1.cs b/Dalamud/Interface/Style/StyleModelV1.cs
index 0d10387c4..54aff8f84 100644
--- a/Dalamud/Interface/Style/StyleModelV1.cs
+++ b/Dalamud/Interface/Style/StyleModelV1.cs
@@ -487,13 +487,41 @@ namespace Dalamud.Interface.Style
///
public override void Push()
{
- throw new NotImplementedException();
- }
+ this.PushStyleHelper(ImGuiStyleVar.Alpha, this.Alpha);
+ this.PushStyleHelper(ImGuiStyleVar.WindowPadding, this.WindowPadding);
+ this.PushStyleHelper(ImGuiStyleVar.WindowRounding, this.WindowRounding);
+ this.PushStyleHelper(ImGuiStyleVar.WindowBorderSize, this.WindowBorderSize);
+ this.PushStyleHelper(ImGuiStyleVar.WindowTitleAlign, this.WindowTitleAlign);
+ this.PushStyleHelper(ImGuiStyleVar.ChildRounding, this.ChildRounding);
+ this.PushStyleHelper(ImGuiStyleVar.ChildBorderSize, this.ChildBorderSize);
+ this.PushStyleHelper(ImGuiStyleVar.PopupRounding, this.PopupRounding);
+ this.PushStyleHelper(ImGuiStyleVar.PopupBorderSize, this.PopupBorderSize);
+ this.PushStyleHelper(ImGuiStyleVar.FramePadding, this.FramePadding);
+ this.PushStyleHelper(ImGuiStyleVar.FrameRounding, this.FrameRounding);
+ this.PushStyleHelper(ImGuiStyleVar.FrameBorderSize, this.FrameBorderSize);
+ this.PushStyleHelper(ImGuiStyleVar.ItemSpacing, this.ItemSpacing);
+ this.PushStyleHelper(ImGuiStyleVar.ItemInnerSpacing, this.ItemInnerSpacing);
+ this.PushStyleHelper(ImGuiStyleVar.CellPadding, this.CellPadding);
+ this.PushStyleHelper(ImGuiStyleVar.IndentSpacing, this.IndentSpacing);
+ this.PushStyleHelper(ImGuiStyleVar.ScrollbarSize, this.ScrollbarSize);
+ this.PushStyleHelper(ImGuiStyleVar.ScrollbarRounding, this.ScrollbarRounding);
+ this.PushStyleHelper(ImGuiStyleVar.GrabMinSize, this.GrabMinSize);
+ this.PushStyleHelper(ImGuiStyleVar.GrabRounding, this.GrabRounding);
+ this.PushStyleHelper(ImGuiStyleVar.TabRounding, this.TabRounding);
+ this.PushStyleHelper(ImGuiStyleVar.ButtonTextAlign, this.ButtonTextAlign);
+ this.PushStyleHelper(ImGuiStyleVar.SelectableTextAlign, this.SelectableTextAlign);
- ///
- public override void Pop()
- {
- throw new NotImplementedException();
+ foreach (var imGuiCol in Enum.GetValues())
+ {
+ if (imGuiCol == ImGuiCol.COUNT)
+ {
+ continue;
+ }
+
+ this.PushColorHelper(imGuiCol, this.Colors[imGuiCol.ToString()]);
+ }
+
+ this.DonePushing();
}
}
}
diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs
index 4569a1d07..b19f89bbb 100644
--- a/Dalamud/Plugin/Internal/PluginManager.cs
+++ b/Dalamud/Plugin/Internal/PluginManager.cs
@@ -17,6 +17,8 @@ using Dalamud.Game;
using Dalamud.Game.Gui;
using Dalamud.Game.Gui.Dtr;
using Dalamud.Game.Text;
+using Dalamud.Game.Text.SeStringHandling;
+using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal.Exceptions;
@@ -50,6 +52,8 @@ internal partial class PluginManager : IDisposable, IServiceType
private readonly DirectoryInfo devPluginDirectory;
private readonly BannedPlugin[]? bannedPlugins;
+ private readonly DalamudLinkPayload openInstallerWindowPluginChangelogsLink;
+
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service.Get();
@@ -69,6 +73,23 @@ 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);
+ var safeModeFile = Path.Combine(appdata, "XIVLauncher", ".dalamud_safemode");
+
+ if (File.Exists(safeModeFile))
+ {
+ this.SafeMode = true;
+ File.Delete(safeModeFile);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Couldn't check safe mode file");
+ }
+
if (this.SafeMode)
{
this.configuration.PluginSafeMode = false;
@@ -84,6 +105,11 @@ internal partial class PluginManager : IDisposable, IServiceType
throw new InvalidDataException("Couldn't deserialize banned plugins manifest.");
}
+ this.openInstallerWindowPluginChangelogsLink = Service.Get().AddChatLinkHandler("Dalamud", 1003, (i, m) =>
+ {
+ Service.GetNullable()?.OpenPluginInstallerPluginChangelogs();
+ });
+
this.ApplyPatches();
}
@@ -125,7 +151,7 @@ internal partial class PluginManager : IDisposable, IServiceType
///
/// Gets a value indicating whether all added repos are not in progress.
///
- public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress);
+ public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress || repo.State != PluginRepositoryState.Fail);
///
/// Gets a value indicating whether the plugin manager started in safe mode.
@@ -152,25 +178,38 @@ internal partial class PluginManager : IDisposable, IServiceType
///
/// The list of updated plugin metadata.
/// The header text to send to chat prior to any update info.
- public static void PrintUpdatedPlugins(List? updateMetadata, string header)
+ public void PrintUpdatedPlugins(List? updateMetadata, string header)
{
var chatGui = Service.Get();
if (updateMetadata is { Count: > 0 })
{
- chatGui.Print(header);
+ chatGui.PrintChat(new XivChatEntry
+ {
+ Message = new SeString(new List()
+ {
+ new TextPayload(header),
+ new TextPayload(" ["),
+ new UIForegroundPayload(500),
+ this.openInstallerWindowPluginChangelogsLink,
+ new TextPayload(Loc.Localize("DalamudInstallerPluginChangelogHelp", "Open plugin changelogs") + " "),
+ RawPayload.LinkTerminator,
+ new UIForegroundPayload(0),
+ new TextPayload("]"),
+ }),
+ });
foreach (var metadata in updateMetadata)
{
if (metadata.WasUpdated)
{
- chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version));
+ chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version) + (metadata.HasChangelog ? " " : string.Empty));
}
else
{
chatGui.PrintChat(new XivChatEntry
{
- Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version),
+ Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version) + (metadata.HasChangelog ? " " : string.Empty),
Type = XivChatType.Urgent,
});
}
@@ -226,10 +265,12 @@ internal partial class PluginManager : IDisposable, IServiceType
///
public void Dispose()
{
- if (this.InstalledPlugins.Any())
+ var disposablePlugins =
+ this.InstalledPlugins.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray();
+ if (disposablePlugins.Any())
{
// Unload them first, just in case some of plugin codes are still running via callbacks initiated externally.
- foreach (var plugin in this.InstalledPlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync))
+ foreach (var plugin in disposablePlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync))
{
try
{
@@ -241,7 +282,7 @@ internal partial class PluginManager : IDisposable, IServiceType
}
}
- Task.WaitAll(this.InstalledPlugins
+ Task.WaitAll(disposablePlugins
.Where(plugin => plugin.Manifest.CanUnloadAsync)
.Select(plugin => Task.Run(async () =>
{
@@ -261,7 +302,7 @@ internal partial class PluginManager : IDisposable, IServiceType
// Now that we've waited enough, dispose the whole plugin.
// Since plugins should have been unloaded above, this should be done quickly.
- foreach (var plugin in this.InstalledPlugins)
+ foreach (var plugin in disposablePlugins)
plugin.ExplicitDisposeIgnoreExceptions($"Error disposing {plugin.Name}", Log);
}
@@ -307,6 +348,7 @@ internal partial class PluginManager : IDisposable, IServiceType
// Add installed plugins. These are expected to be in a specific format so we can look for exactly that.
foreach (var pluginDir in this.pluginDirectory.GetDirectories())
{
+ var versionsDefs = new List();
foreach (var versionDir in pluginDir.GetDirectories())
{
var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll"));
@@ -317,7 +359,16 @@ internal partial class PluginManager : IDisposable, IServiceType
var manifest = LocalPluginManifest.Load(manifestFile);
- pluginDefs.Add(new PluginDef(dllFile, manifest, false));
+ versionsDefs.Add(new PluginDef(dllFile, manifest, false));
+ }
+
+ try
+ {
+ pluginDefs.Add(versionsDefs.OrderByDescending(x => x.Manifest!.EffectiveVersion).First());
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Couldn't choose best version for plugin: {Name}", pluginDir.Name);
}
}
@@ -482,7 +533,9 @@ internal partial class PluginManager : IDisposable, IServiceType
/// A representing the asynchronous operation.
public async Task ReloadPluginMastersAsync(bool notify = true)
{
+ Log.Information("Now reloading all PluginMasters...");
await Task.WhenAll(this.Repos.Select(repo => repo.ReloadPluginMasterAsync()));
+ Log.Information("PluginMasters reloaded, now refiltering...");
this.RefilterPluginMasters(notify);
}
@@ -712,10 +765,14 @@ internal partial class PluginManager : IDisposable, IServiceType
{
try
{
- if (plugin.IsDisabled)
- plugin.Enable();
-
- await plugin.LoadAsync(reason);
+ if (!plugin.IsDisabled)
+ {
+ await plugin.LoadAsync(reason);
+ }
+ else
+ {
+ Log.Verbose($"{name} was disabled");
+ }
}
catch (InvalidPluginException)
{
@@ -832,10 +889,17 @@ internal partial class PluginManager : IDisposable, IServiceType
}
else
{
- foreach (var versionDir in versionDirs)
+ for (var i = 0; i < versionDirs.Length; i++)
{
+ var versionDir = versionDirs[i];
try
{
+ if (i != 0)
+ {
+ Log.Information($"Old version: cleaning up {versionDir.FullName}");
+ versionDir.Delete(true);
+ continue;
+ }
var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll"));
if (!dllFile.Exists)
{
@@ -849,6 +913,21 @@ internal partial class PluginManager : IDisposable, IServiceType
{
Log.Information($"Missing manifest: cleaning up {versionDir.FullName}");
versionDir.Delete(true);
+ continue;
+ }
+
+ if (manifestFile.Length == 0)
+ {
+ Log.Information($"Manifest empty: cleaning up {versionDir.FullName}");
+ versionDir.Delete(true);
+ continue;
+ }
+
+ var manifest = LocalPluginManifest.Load(manifestFile);
+ if (manifest.ScheduledForDeletion)
+ {
+ Log.Information($"Scheduled deletion: cleaning up {versionDir.FullName}");
+ versionDir.Delete(true);
}
}
catch (Exception ex)
@@ -870,7 +949,7 @@ internal partial class PluginManager : IDisposable, IServiceType
///
/// Perform a dry run, don't install anything.
/// Success or failure and a list of updated plugin metadata.
- public async Task> UpdatePluginsAsync(bool dryRun = false)
+ public async Task> UpdatePluginsAsync(bool ignoreDisabled, bool dryRun)
{
Log.Information("Starting plugin update");
@@ -883,6 +962,12 @@ internal partial class PluginManager : IDisposable, IServiceType
if (plugin.InstalledPlugin.IsDev)
continue;
+ if (plugin.InstalledPlugin.Manifest.Disabled && ignoreDisabled)
+ continue;
+
+ if (plugin.InstalledPlugin.Manifest.ScheduledForDeletion)
+ continue;
+
var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun);
if (result != null)
updatedList.Add(result);
@@ -914,6 +999,7 @@ internal partial class PluginManager : IDisposable, IServiceType
? metadata.UpdateManifest.TestingAssemblyVersion
: metadata.UpdateManifest.AssemblyVersion)!,
WasUpdated = true,
+ HasChangelog = !metadata.UpdateManifest.Changelog.IsNullOrWhitespace(),
};
if (!dryRun)
@@ -954,7 +1040,9 @@ internal partial class PluginManager : IDisposable, IServiceType
{
try
{
- plugin.Disable();
+ if (!plugin.IsDisabled)
+ plugin.Disable();
+
lock (this.pluginListLock)
{
this.InstalledPlugins = this.InstalledPlugins.Remove(plugin);
diff --git a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
index 2043b8c6b..e32f0f4c7 100644
--- a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
+++ b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
@@ -138,9 +138,9 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
return;
}
- if (this.State != PluginState.Loaded)
+ if (this.State != PluginState.Loaded && this.State != PluginState.LoadError)
{
- Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}.");
+ Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded} nor {PluginState.LoadError}.");
return;
}
diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
index f1647e217..d9e4719c9 100644
--- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
+++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs
@@ -560,6 +560,7 @@ internal class LocalPlugin : IDisposable
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled");
this.Manifest.Disabled = false;
+ this.Manifest.ScheduledForDeletion = false;
this.SaveManifest();
}
@@ -614,6 +615,16 @@ internal class LocalPlugin : IDisposable
this.SaveManifest();
}
+ ///
+ /// Schedule the deletion of this plugin on next cleanup.
+ ///
+ /// Schedule or cancel the deletion.
+ public void ScheduleDeletion(bool status = true)
+ {
+ this.Manifest.ScheduledForDeletion = status;
+ this.SaveManifest();
+ }
+
private static void SetupLoaderConfig(LoaderConfig config)
{
config.IsUnloadable = true;
diff --git a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs
index 0853b19be..634fbc75c 100644
--- a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs
+++ b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
using Dalamud.Utility;
@@ -23,6 +24,11 @@ internal record LocalPluginManifest : PluginManifest
///
public bool Testing { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the plugin should be deleted during the next cleanup.
+ ///
+ public bool ScheduledForDeletion { get; set; }
+
///
/// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was
/// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null
@@ -36,6 +42,11 @@ internal record LocalPluginManifest : PluginManifest
///
public bool IsThirdParty => !this.InstalledFromUrl.IsNullOrEmpty() && this.InstalledFromUrl != PluginRepository.MainRepoUrl;
+ ///
+ /// Gets the effective version of this plugin.
+ ///
+ public Version EffectiveVersion => this.Testing && this.TestingAssemblyVersion != null ? this.TestingAssemblyVersion : this.AssemblyVersion;
+
///
/// Save a plugin manifest to file.
///
diff --git a/Dalamud/Plugin/Internal/Types/PluginManifest.cs b/Dalamud/Plugin/Internal/Types/PluginManifest.cs
index c4d712a90..a1b0e6a71 100644
--- a/Dalamud/Plugin/Internal/Types/PluginManifest.cs
+++ b/Dalamud/Plugin/Internal/Types/PluginManifest.cs
@@ -181,4 +181,17 @@ internal record PluginManifest
/// Gets a message that is shown to users when sending feedback.
///
public string? FeedbackMessage { get; init; }
+
+ ///
+ /// Gets a value indicating whether this plugin is DIP17.
+ /// To be removed.
+ ///
+ [JsonProperty("_isDip17Plugin")]
+ public bool IsDip17Plugin { get; init; } = false;
+
+ ///
+ /// Gets the DIP17 channel name.
+ ///
+ [JsonProperty("_Dip17Channel")]
+ public string? Dip17Channel { get; init; }
}
diff --git a/Dalamud/Plugin/Internal/Types/PluginRepository.cs b/Dalamud/Plugin/Internal/Types/PluginRepository.cs
index 82e4fb13d..5c7659d90 100644
--- a/Dalamud/Plugin/Internal/Types/PluginRepository.cs
+++ b/Dalamud/Plugin/Internal/Types/PluginRepository.cs
@@ -24,6 +24,7 @@ internal class PluginRepository
private static readonly HttpClient HttpClient = new()
{
+ Timeout = TimeSpan.FromSeconds(20),
DefaultRequestHeaders =
{
CacheControl = new CacheControlHeaderValue
@@ -109,7 +110,7 @@ internal class PluginRepository
this.PluginMaster = pluginMaster.AsReadOnly();
- Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}");
+ Log.Information($"Successfully fetched repo: {this.PluginMasterUrl}");
this.State = PluginRepositoryState.Success;
}
catch (Exception ex)
diff --git a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs
index 02eba7ea7..24ca5fe0f 100644
--- a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs
+++ b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs
@@ -26,4 +26,9 @@ internal class PluginUpdateStatus
/// Gets or sets a value indicating whether the plugin was updated.
///
public bool WasUpdated { get; set; }
+
+ ///
+ /// Gets a value indicating whether the plugin has a changelog if it was updated.
+ ///
+ public bool HasChangelog { get; init; }
}
diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp
new file mode 100644
index 000000000..f0be81a73
--- /dev/null
+++ b/DalamudCrashHandler/DalamudCrashHandler.cpp
@@ -0,0 +1,633 @@
+#include
+#include
+#include
+#include