Merge branch 'master' into api13

This commit is contained in:
Asriel 2025-09-02 18:23:34 -07:00 committed by GitHub
commit ed5fa493f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
289 changed files with 2454 additions and 1264 deletions

32
.github/workflows/backup.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: Back up code to other forges
on:
schedule:
- cron: '0 2 * * *' # Run every day at 2 AM
workflow_dispatch: # Allow manual trigger
jobs:
push-to-forges:
runs-on: ubuntu-latest
if: github.repository == 'goatcorp/Dalamud'
steps:
- name: Checkout the repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd #v0.9.1
with:
ssh-private-key: |
${{ secrets.MIRROR_GITLAB_SYNC_KEY }}
${{ secrets.MIRROR_CODEBERG_SYNC_KEY }}
- name: Add remotes & push
env:
GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=accept-new"
run: |
git remote add gitlab git@gitlab.com:goatcorp/Dalamud.git
git push gitlab --all --force
git remote add codeberg git@codeberg.org:goatcorp/Dalamud.git
git push codeberg --all --force

View file

@ -26,6 +26,38 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
RT_MANIFEST_THEMES RT_MANIFEST "themes.manifest"
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_APPNAME "Dalamud Boot"
IDS_MSVCRT_ACTION_OPENDOWNLOAD
"Download Microsoft Visual C++ Redistributable 2022\nExit the game and download the latest setup file from Microsoft."
IDS_MSVCRT_ACTION_IGNORE
"Ignore and Continue\nAttempt to continue with the currently installed version.\nDalamud or plugins may fail to load."
IDS_MSVCRT_DIALOG_MAININSTRUCTION
"Outdated Microsoft Visual C++ Redistributable"
IDS_MSVCRT_DIALOG_CONTENT
"The Microsoft Visual C++ Redistributable version detected on this computer (v{0}.{1}.{2}.{3}) is out of date and may not work with Dalamud."
IDS_MSVCRT_DOWNLOADURL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
IDS_INITIALIZEFAIL_ACTION_ABORT "Abort\nExit the game."
IDS_INITIALIZEFAIL_ACTION_CONTINUE
"Load game without Dalamud\nThe game will launch without Dalamud enabled."
IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION "Failed to load Dalamud."
IDS_INITIALIZEFAIL_DIALOG_CONTENT
"An error is preventing Dalamud from being loaded along with the game."
END
STRINGTABLE
BEGIN
IDS_INITIALIZEFAIL_DIALOG_FOOTER
"Last operation: {0}\nHRESULT: 0x{1:08X}\nDescription: {2}"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////

View file

@ -48,7 +48,7 @@
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard>stdcpp23</LanguageStandard>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -133,6 +133,10 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="DalamudStartInfo.cpp" />
<ClCompile Include="error_info.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="hooks.cpp" />
<ClCompile Include="logging.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
@ -176,6 +180,7 @@
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\trampoline.h" />
<ClInclude Include="crashhandler_shared.h" />
<ClInclude Include="DalamudStartInfo.h" />
<ClInclude Include="error_info.h" />
<ClInclude Include="hooks.h" />
<ClInclude Include="logging.h" />
<ClInclude Include="ntdll.h" />

View file

@ -76,6 +76,9 @@
<ClCompile Include="ntdll.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="error_info.cpp">
<Filter>Common Boot</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
@ -146,6 +149,9 @@
<ClInclude Include="ntdll.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="error_info.h">
<Filter>Common Boot</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Dalamud.Boot.rc" />

View file

@ -9,10 +9,125 @@
#include "utils.h"
#include "veh.h"
#include "xivfixes.h"
#include "resource.h"
HMODULE g_hModule;
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
static void CheckMsvcrtVersion() {
// Commit introducing inline mutex ctor: tagged vs-2022-17.14 (2024-06-18)
// - https://github.com/microsoft/STL/commit/22a88260db4d754bbc067e2002430144d6ec5391
// MSVC Redist versions:
// - https://github.com/abbodi1406/vcredist/blob/master/source_links/README.md
// - 14.40.33810.0 dsig 2024-04-28
// - 14.40.33816.0 dsig 2024-09-11
constexpr WORD RequiredMsvcrtVersionComponents[] = {14, 40, 33816, 0};
constexpr auto RequiredMsvcrtVersion = 0ULL
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[0]) << 48)
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[1]) << 32)
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[2]) << 16)
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[3]) << 0);
constexpr const wchar_t* RuntimeDllNames[] = {
#ifdef _DEBUG
L"msvcp140d.dll",
L"vcruntime140d.dll",
L"vcruntime140_1d.dll",
#else
L"msvcp140.dll",
L"vcruntime140.dll",
L"vcruntime140_1.dll",
#endif
};
uint64_t lowestVersion = 0;
for (const auto& runtimeDllName : RuntimeDllNames) {
const utils::loaded_module mod(GetModuleHandleW(runtimeDllName));
if (!mod) {
logging::E("MSVCRT DLL not found: {}", runtimeDllName);
continue;
}
const auto path = mod.path()
.transform([](const auto& p) { return p.wstring(); })
.value_or(runtimeDllName);
if (const auto versionResult = mod.get_file_version()) {
const auto& versionFull = versionResult->get();
logging::I("MSVCRT DLL {} has version {}.", path, utils::format_file_version(versionFull));
const auto version = 0ULL |
(static_cast<uint64_t>(versionFull.dwFileVersionMS) << 32) |
(static_cast<uint64_t>(versionFull.dwFileVersionLS) << 0);
if (version < RequiredMsvcrtVersion && (lowestVersion == 0 || lowestVersion > version))
lowestVersion = version;
} else {
logging::E("Failed to detect MSVCRT DLL version for {}: {}", path, versionResult.error().describe());
}
}
if (!lowestVersion)
return;
enum IdTaskDialogAction {
IdTaskDialogActionOpenDownload = 101,
IdTaskDialogActionIgnore,
};
const TASKDIALOG_BUTTON buttons[]{
{IdTaskDialogActionOpenDownload, MAKEINTRESOURCEW(IDS_MSVCRT_ACTION_OPENDOWNLOAD)},
{IdTaskDialogActionIgnore, MAKEINTRESOURCEW(IDS_MSVCRT_ACTION_IGNORE)},
};
const WORD lowestVersionComponents[]{
static_cast<WORD>(lowestVersion >> 48),
static_cast<WORD>(lowestVersion >> 32),
static_cast<WORD>(lowestVersion >> 16),
static_cast<WORD>(lowestVersion >> 0),
};
const auto dialogContent = std::vformat(
utils::get_string_resource(IDS_MSVCRT_DIALOG_CONTENT),
std::make_wformat_args(
lowestVersionComponents[0],
lowestVersionComponents[1],
lowestVersionComponents[2],
lowestVersionComponents[3]));
const TASKDIALOGCONFIG config{
.cbSize = sizeof config,
.hInstance = g_hModule,
.dwFlags = TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS,
.pszWindowTitle = MAKEINTRESOURCEW(IDS_APPNAME),
.pszMainIcon = MAKEINTRESOURCEW(IDI_ICON1),
.pszMainInstruction = MAKEINTRESOURCEW(IDS_MSVCRT_DIALOG_MAININSTRUCTION),
.pszContent = dialogContent.c_str(),
.cButtons = _countof(buttons),
.pButtons = buttons,
.nDefaultButton = IdTaskDialogActionOpenDownload,
};
int buttonPressed;
if (utils::scoped_dpi_awareness_context ctx;
FAILED(TaskDialogIndirect(&config, &buttonPressed, nullptr, nullptr)))
buttonPressed = IdTaskDialogActionOpenDownload;
switch (buttonPressed) {
case IdTaskDialogActionOpenDownload:
ShellExecuteW(
nullptr,
L"open",
utils::get_string_resource(IDS_MSVCRT_DOWNLOADURL).c_str(),
nullptr,
nullptr,
SW_SHOW);
ExitProcess(0);
break;
}
}
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
g_startInfo.from_envvars();
@ -24,7 +139,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
}
if (g_startInfo.BootShowConsole)
ConsoleSetup(L"Dalamud Boot");
ConsoleSetup(utils::get_string_resource(IDS_APPNAME).c_str());
logging::update_dll_load_status(true);
@ -94,6 +209,8 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
CheckMsvcrtVersion();
if (g_startInfo.BootDebugDirectX) {
logging::I("Enabling DirectX Debugging.");
@ -159,7 +276,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
if (minHookLoaded) {
logging::I("Applying fixes...");
xivfixes::apply_all(true);
std::thread([] { xivfixes::apply_all(true); }).join();
logging::I("Fixes OK");
} else {
logging::W("Skipping fixes, as MinHook has failed to load.");
@ -170,11 +287,14 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
while (!IsDebuggerPresent())
Sleep(100);
logging::I("Debugger attached.");
__debugbreak();
}
const auto fs_module_path = utils::get_module_path(g_hModule);
const auto runtimeconfig_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
const auto module_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.dll").wstring();
const auto fs_module_path = utils::loaded_module(g_hModule).path();
if (!fs_module_path)
return fs_module_path.error();
const auto runtimeconfig_path = std::filesystem::path(*fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
const auto module_path = std::filesystem::path(*fs_module_path).replace_filename(L"Dalamud.dll").wstring();
// ============================== CLR ========================================= //

View file

@ -0,0 +1,26 @@
#include "error_info.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
DalamudBootError::DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription, long hresult) noexcept
: m_dalamudErrorDescription(dalamudErrorDescription)
, m_hresult(hresult) {
}
DalamudBootError::DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription) noexcept
: DalamudBootError(dalamudErrorDescription, E_FAIL) {
}
const char* DalamudBootError::describe() const {
switch (m_dalamudErrorDescription) {
case DalamudBootErrorDescription::ModuleResourceLoadFail:
return "Failed to load resource.";
case DalamudBootErrorDescription::ModuleResourceVersionReadFail:
return "Failed to query version information.";
case DalamudBootErrorDescription::ModuleResourceVersionSignatureFail:
return "Invalid version info found.";
default:
return "(unavailable)";
}
}

42
Dalamud.Boot/error_info.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include <expected>
#include <string>
typedef unsigned long DWORD;
typedef _Return_type_success_(return >= 0) long HRESULT;
enum class DalamudBootErrorDescription {
None,
ModulePathResolutionFail,
ModuleResourceLoadFail,
ModuleResourceVersionReadFail,
ModuleResourceVersionSignatureFail,
};
class DalamudBootError {
DalamudBootErrorDescription m_dalamudErrorDescription;
long m_hresult;
public:
DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription, long hresult) noexcept;
DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription) noexcept;
const char* describe() const;
operator HRESULT() const {
return m_hresult;
}
};
template<typename T>
using DalamudExpected = std::expected<
std::conditional_t<
std::is_reference_v<T>,
std::reference_wrapper<std::remove_reference_t<T>>,
T
>,
DalamudBootError
>;
using DalamudUnexpected = std::unexpected<DalamudBootError>;

View file

@ -84,18 +84,12 @@ void hooks::getprocaddress_singleton_import_hook::initialize() {
const auto dllName = unicode::convert<std::string>(pData->Loaded.FullDllName->Buffer);
utils::loaded_module mod(pData->Loaded.DllBase);
std::wstring version, description;
try {
version = utils::format_file_version(mod.get_file_version());
} catch (...) {
version = L"<unknown>";
}
const auto version = mod.get_file_version()
.transform([](const auto& v) { return utils::format_file_version(v.get()); })
.value_or(L"<unknown>");
try {
description = mod.get_description();
} catch (...) {
description = L"<unknown>";
}
const auto description = mod.get_description()
.value_or(L"<unknown>");
logging::I(R"({} "{}" ("{}" ver {}) has been loaded at 0x{:X} ~ 0x{:X} (0x{:X}); finding import table items to hook.)",
LogTag, dllName, description, version,
@ -125,7 +119,9 @@ void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loade
if (mod.is_current_process())
return;
const auto path = unicode::convert<std::string>(mod.path().wstring());
const auto path = mod.path()
.transform([](const auto& p) { return unicode::convert<std::string>(p.wstring()); })
.value_or("<unknown>");
for (const auto& [hModule, targetFns] : m_targetFns) {
for (const auto& [targetFn, pfnThunk] : targetFns) {
@ -133,7 +129,7 @@ void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loade
if (void* pGetProcAddressImport; mod.find_imported_function_pointer(dllName.c_str(), targetFn.c_str(), 0, pGetProcAddressImport)) {
auto& hook = m_hooks[hModule][targetFn][mod];
if (!hook) {
logging::I("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, unicode::convert<std::string>(mod.path().wstring()));
logging::I("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, path);
hook.emplace(std::format("getprocaddress_singleton_import_hook::hook_module({}!{})", dllName, targetFn), static_cast<void**>(pGetProcAddressImport), pfnThunk);
}

View file

@ -11,6 +11,9 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
// https://developercommunity.visualstudio.com/t/Access-violation-with-std::mutex::lock-a/10664660
#define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR
// Windows Header Files (1)
#include <Windows.h>
@ -21,6 +24,7 @@
#include <iphlpapi.h>
#include <PathCch.h>
#include <Psapi.h>
#include <shellapi.h>
#include <ShlObj.h>
#include <Shlwapi.h>
#include <SubAuth.h>
@ -51,6 +55,7 @@
#include <set>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
// https://www.akenotsuki.com/misc/srell/en/

View file

@ -3,12 +3,23 @@
// Used by Dalamud.Boot.rc
//
#define IDI_ICON1 101
#define IDS_APPNAME 102
#define IDS_MSVCRT_ACTION_OPENDOWNLOAD 103
#define IDS_MSVCRT_ACTION_IGNORE 104
#define IDS_MSVCRT_DIALOG_MAININSTRUCTION 105
#define IDS_MSVCRT_DIALOG_CONTENT 106
#define IDS_MSVCRT_DOWNLOADURL 107
#define IDS_INITIALIZEFAIL_ACTION_ABORT 108
#define IDS_INITIALIZEFAIL_ACTION_CONTINUE 109
#define IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION 110
#define IDS_INITIALIZEFAIL_DIALOG_CONTENT 111
#define IDS_INITIALIZEFAIL_DIALOG_FOOTER 112
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101

View file

@ -2,6 +2,7 @@
#include "logging.h"
#include "utils.h"
#include "resource.h"
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
@ -379,12 +380,50 @@ extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointPara
auto desc = err.Description();
if (desc.length() == 0)
desc = err.ErrorMessage();
if (MessageBoxW(nullptr, std::format(
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\n{}\n{}",
enum IdTaskDialogAction {
IdTaskDialogActionAbort = 101,
IdTaskDialogActionContinue,
};
const TASKDIALOG_BUTTON buttons[]{
{IdTaskDialogActionAbort, MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_ACTION_ABORT)},
{IdTaskDialogActionContinue, MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_ACTION_CONTINUE)},
};
const auto hru32 = static_cast<uint32_t>(hr);
const auto footer = std::vformat(
utils::get_string_resource(IDS_INITIALIZEFAIL_DIALOG_FOOTER),
std::make_wformat_args(
last_operation,
desc.GetBSTR()).c_str(),
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
hru32,
desc.GetBSTR()));
const TASKDIALOGCONFIG config{
.cbSize = sizeof config,
.hInstance = g_hModule,
.dwFlags = TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS | TDF_EXPAND_FOOTER_AREA,
.pszWindowTitle = MAKEINTRESOURCEW(IDS_APPNAME),
.pszMainIcon = MAKEINTRESOURCEW(IDI_ICON1),
.pszMainInstruction = MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION),
.pszContent = MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_DIALOG_CONTENT),
.cButtons = _countof(buttons),
.pButtons = buttons,
.nDefaultButton = IdTaskDialogActionAbort,
.pszFooter = footer.c_str(),
};
int buttonPressed;
if (utils::scoped_dpi_awareness_context ctx;
FAILED(TaskDialogIndirect(&config, &buttonPressed, nullptr, nullptr)))
buttonPressed = IdTaskDialogActionAbort;
switch (buttonPressed) {
case IdTaskDialogActionAbort:
ExitProcess(-1);
break;
}
if (hMainThreadContinue) {
CloseHandle(hMainThreadContinue);
hMainThreadContinue = nullptr;

View file

@ -3,22 +3,27 @@
#include "utils.h"
std::filesystem::path utils::loaded_module::path() const {
std::wstring buf(MAX_PATH, L'\0');
for (;;) {
if (const auto len = GetModuleFileNameExW(GetCurrentProcess(), m_hModule, &buf[0], static_cast<DWORD>(buf.size())); len != buf.size()) {
if (buf.empty())
throw std::runtime_error(std::format("Failed to resolve module path: Win32 error {}", GetLastError()));
DalamudExpected<std::filesystem::path> utils::loaded_module::path() const {
for (std::wstring buf(MAX_PATH, L'\0');; buf.resize(buf.size() * 2)) {
if (const auto len = GetModuleFileNameW(m_hModule, &buf[0], static_cast<DWORD>(buf.size()));
len != buf.size()) {
if (!len) {
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModulePathResolutionFail,
HRESULT_FROM_WIN32(GetLastError()));
}
buf.resize(len);
return buf;
}
if (buf.size() * 2 < PATHCCH_MAX_CCH)
buf.resize(buf.size() * 2);
else if (auto p = std::filesystem::path(buf); exists(p))
return p;
else
throw std::runtime_error("Failed to resolve module path: no amount of buffer size would fit the data");
if (buf.size() > PATHCCH_MAX_CCH) {
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModulePathResolutionFail,
E_OUTOFMEMORY);
}
}
}
@ -144,21 +149,24 @@ void* utils::loaded_module::get_imported_function_pointer(const char* pcszDllNam
throw std::runtime_error(std::format("Failed to find import for {}!{} ({}).", pcszDllName, pcszFunctionName ? pcszFunctionName : "<unnamed>", hintOrOrdinal));
}
std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)> utils::loaded_module::get_resource(LPCWSTR lpName, LPCWSTR lpType) const {
DalamudExpected<std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>> utils::loaded_module::get_resource(LPCWSTR lpName, LPCWSTR lpType) const {
const auto hres = FindResourceW(m_hModule, lpName, lpType);
if (!hres)
throw std::runtime_error("No such resource");
return DalamudUnexpected(std::in_place, DalamudBootErrorDescription::ModuleResourceLoadFail, GetLastError());
const auto hRes = LoadResource(m_hModule, hres);
if (!hRes)
throw std::runtime_error("LoadResource failure");
return DalamudUnexpected(std::in_place, DalamudBootErrorDescription::ModuleResourceLoadFail, GetLastError());
return {hRes, &FreeResource};
return std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>(hRes, &FreeResource);
}
std::wstring utils::loaded_module::get_description() const {
const auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
const auto pBlock = LockResource(rsrc.get());
DalamudExpected<std::wstring> utils::loaded_module::get_description() const {
auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
if (!rsrc)
return DalamudUnexpected(std::move(rsrc.error()));
const auto pBlock = LockResource(rsrc->get());
struct LANGANDCODEPAGE {
WORD wLanguage;
@ -166,44 +174,65 @@ std::wstring utils::loaded_module::get_description() const {
} * lpTranslate;
UINT cbTranslate;
if (!VerQueryValueW(pBlock,
TEXT("\\VarFileInfo\\Translation"),
L"\\VarFileInfo\\Translation",
reinterpret_cast<LPVOID*>(&lpTranslate),
&cbTranslate)) {
throw std::runtime_error("Invalid version information (1)");
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
HRESULT_FROM_WIN32(GetLastError()));
}
for (size_t i = 0; i < (cbTranslate / sizeof(LANGANDCODEPAGE)); i++) {
wchar_t subblockNameBuf[64];
*std::format_to_n(
subblockNameBuf,
_countof(subblockNameBuf),
L"\\StringFileInfo\\{:04x}{:04x}\\FileDescription",
lpTranslate[i].wLanguage,
lpTranslate[i].wCodePage).out = 0;;
wchar_t* buf = nullptr;
UINT size = 0;
if (!VerQueryValueW(pBlock,
std::format(L"\\StringFileInfo\\{:04x}{:04x}\\FileDescription",
lpTranslate[i].wLanguage,
lpTranslate[i].wCodePage).c_str(),
reinterpret_cast<LPVOID*>(&buf),
&size)) {
if (!VerQueryValueW(pBlock, subblockNameBuf, reinterpret_cast<LPVOID*>(&buf), &size))
continue;
}
auto currName = std::wstring_view(buf, size);
while (!currName.empty() && currName.back() == L'\0')
currName = currName.substr(0, currName.size() - 1);
if (const auto p = currName.find(L'\0'); p != std::string::npos)
currName = currName.substr(0, p);
if (currName.empty())
continue;
return std::wstring(currName);
}
throw std::runtime_error("Invalid version information (2)");
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
}
VS_FIXEDFILEINFO utils::loaded_module::get_file_version() const {
const auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
const auto pBlock = LockResource(rsrc.get());
std::expected<std::reference_wrapper<const VS_FIXEDFILEINFO>, DalamudBootError> utils::loaded_module::get_file_version() const {
auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
if (!rsrc)
return DalamudUnexpected(std::move(rsrc.error()));
const auto pBlock = LockResource(rsrc->get());
UINT size = 0;
LPVOID lpBuffer = nullptr;
if (!VerQueryValueW(pBlock, L"\\", &lpBuffer, &size))
throw std::runtime_error("Failed to query version information.");
if (!VerQueryValueW(pBlock, L"\\", &lpBuffer, &size)) {
return std::unexpected<DalamudBootError>(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
HRESULT_FROM_WIN32(GetLastError()));
}
const VS_FIXEDFILEINFO& versionInfo = *static_cast<const VS_FIXEDFILEINFO*>(lpBuffer);
if (versionInfo.dwSignature != 0xfeef04bd)
throw std::runtime_error("Invalid version info found.");
if (versionInfo.dwSignature != 0xfeef04bd) {
return std::unexpected<DalamudBootError>(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionSignatureFail);
}
return versionInfo;
}
@ -589,17 +618,10 @@ bool utils::is_running_on_wine() {
return g_startInfo.Platform != "WINDOWS";
}
std::filesystem::path utils::get_module_path(HMODULE hModule) {
std::wstring buf(MAX_PATH, L'\0');
while (true) {
if (const auto res = GetModuleFileNameW(hModule, &buf[0], static_cast<int>(buf.size())); !res)
throw std::runtime_error(std::format("GetModuleFileName failure: 0x{:X}", GetLastError()));
else if (res < buf.size()) {
buf.resize(res);
return buf;
} else
buf.resize(buf.size() * 2);
}
std::wstring utils::get_string_resource(uint32_t resId) {
LPCWSTR pstr;
const auto len = LoadStringW(g_hModule, resId, reinterpret_cast<LPWSTR>(&pstr), 0);
return std::wstring(pstr, len);
}
HWND utils::try_find_game_window() {
@ -677,3 +699,22 @@ std::wstring utils::format_win32_error(DWORD err) {
return std::format(L"Win32 error ({}=0x{:X})", err, err);
}
utils::scoped_dpi_awareness_context::scoped_dpi_awareness_context()
: scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) {
}
utils::scoped_dpi_awareness_context::scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT context) {
const auto user32 = GetModuleHandleW(L"user32.dll");
m_setThreadDpiAwarenessContext =
user32
? reinterpret_cast<decltype(&SetThreadDpiAwarenessContext)>(
GetProcAddress(user32, "SetThreadDpiAwarenessContext"))
: nullptr;
m_old = m_setThreadDpiAwarenessContext ? m_setThreadDpiAwarenessContext(context) : DPI_AWARENESS_CONTEXT_UNAWARE;
}
utils::scoped_dpi_awareness_context::~scoped_dpi_awareness_context() {
if (m_setThreadDpiAwarenessContext)
m_setThreadDpiAwarenessContext(m_old);
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <expected>
#include <filesystem>
#include <functional>
#include <span>
@ -7,6 +8,7 @@
#include <memory>
#include <vector>
#include "error_info.h"
#include "unicode.h"
namespace utils {
@ -18,14 +20,13 @@ namespace utils {
loaded_module(void* hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
loaded_module(size_t hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
std::filesystem::path path() const;
DalamudExpected<std::filesystem::path> path() const;
bool is_current_process() const { return m_hModule == GetModuleHandleW(nullptr); }
bool owns_address(const void* pAddress) const;
operator HMODULE() const {
return m_hModule;
}
operator HMODULE() const { return m_hModule; }
operator bool() const { return m_hModule; }
size_t address_int() const { return reinterpret_cast<size_t>(m_hModule); }
size_t image_size() const { return is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage; }
@ -58,9 +59,9 @@ namespace utils {
void* get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) const;
template<typename TFn> TFn** get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { return reinterpret_cast<TFn**>(get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)); }
[[nodiscard]] std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)> get_resource(LPCWSTR lpName, LPCWSTR lpType) const;
[[nodiscard]] std::wstring get_description() const;
[[nodiscard]] VS_FIXEDFILEINFO get_file_version() const;
[[nodiscard]] DalamudExpected<std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>> get_resource(LPCWSTR lpName, LPCWSTR lpType) const;
[[nodiscard]] DalamudExpected<std::wstring> get_description() const;
[[nodiscard]] DalamudExpected<const VS_FIXEDFILEINFO&> get_file_version() const;
static loaded_module current_process();
static std::vector<loaded_module> all_modules();
@ -269,7 +270,7 @@ namespace utils {
bool is_running_on_wine();
std::filesystem::path get_module_path(HMODULE hModule);
std::wstring get_string_resource(uint32_t resId);
/// @brief Find the game main window.
/// @return Handle to the game main window, or nullptr if it doesn't exist (yet).
@ -280,4 +281,18 @@ namespace utils {
std::wstring escape_shell_arg(const std::wstring& arg);
std::wstring format_win32_error(DWORD err);
class scoped_dpi_awareness_context {
DPI_AWARENESS_CONTEXT m_old;
decltype(&SetThreadDpiAwarenessContext) m_setThreadDpiAwarenessContext;
public:
scoped_dpi_awareness_context();
scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT);
~scoped_dpi_awareness_context();
scoped_dpi_awareness_context(const scoped_dpi_awareness_context&) = delete;
scoped_dpi_awareness_context(scoped_dpi_awareness_context&&) = delete;
scoped_dpi_awareness_context& operator=(const scoped_dpi_awareness_context&) = delete;
scoped_dpi_awareness_context& operator=(scoped_dpi_awareness_context&&) = delete;
};
}

View file

@ -102,9 +102,13 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
return false;
}
static void append_injector_launch_args(std::vector<std::wstring>& args)
static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstring>& args)
{
args.emplace_back(L"--game=\"" + utils::loaded_module::current_process().path().wstring() + L"\"");
if (auto path = utils::loaded_module::current_process().path())
args.emplace_back(L"--game=\"" + path->wstring() + L"\"");
else
return DalamudUnexpected(std::in_place, std::move(path.error()));
switch (g_startInfo.DalamudLoadMethod) {
case DalamudStartInfo::LoadMethod::Entrypoint:
args.emplace_back(L"--mode=entrypoint");
@ -155,6 +159,8 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
args.emplace_back(szArgList[i]);
LocalFree(szArgList);
}
return {};
}
LONG exception_handler(EXCEPTION_POINTERS* ex)
@ -358,11 +364,20 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
args.emplace_back(std::format(L"--process-handle={}", reinterpret_cast<size_t>(hInheritableCurrentProcess)));
args.emplace_back(std::format(L"--exception-info-pipe-read-handle={}", reinterpret_cast<size_t>(hReadPipeInheritable->get())));
args.emplace_back(std::format(L"--asset-directory={}", unicode::convert<std::wstring>(g_startInfo.AssetDirectory)));
if (const auto path = utils::loaded_module(g_hModule).path()) {
args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
? utils::loaded_module(g_hModule).path().parent_path().wstring()
? path->parent_path().wstring()
: std::filesystem::path(unicode::convert<std::wstring>(g_startInfo.BootLogPath)).parent_path().wstring()));
} else {
logging::W("Failed to read path of the Dalamud Boot module: {}", path.error().describe());
return false;
}
args.emplace_back(L"--");
append_injector_launch_args(args);
if (auto r = append_injector_launch_args(args); !r) {
logging::W("Failed to generate injector launch args: {}", r.error().describe());
return false;
}
for (const auto& arg : args)
{

View file

@ -8,12 +8,6 @@
#include "ntdll.h"
#include "utils.h"
template<typename T>
static std::span<T> assume_nonempty_span(std::span<T> t, const char* descr) {
if (t.empty())
throw std::runtime_error(std::format("Unexpected empty span found: {}", descr));
return t;
}
void xivfixes::unhook_dll(bool bApply) {
static const auto LogTag = "[xivfixes:unhook_dll]";
static const auto LogTagW = L"[xivfixes:unhook_dll]";
@ -23,44 +17,56 @@ void xivfixes::unhook_dll(bool bApply) {
const auto mods = utils::loaded_module::all_modules();
const auto test_module = [&](size_t i, const utils::loaded_module & mod) {
std::filesystem::path path;
try {
path = mod.path();
std::wstring version, description;
try {
version = utils::format_file_version(mod.get_file_version());
} catch (...) {
version = L"<unknown>";
}
try {
description = mod.get_description();
} catch (...) {
description = L"<unknown>";
}
logging::I(R"({} [{}/{}] Module 0x{:X} ~ 0x{:X} (0x{:X}): "{}" ("{}" ver {}))", LogTagW, i + 1, mods.size(), mod.address_int(), mod.address_int() + mod.image_size(), mod.image_size(), path.wstring(), description, version);
} catch (const std::exception& e) {
logging::W("{} [{}/{}] Module 0x{:X}: Failed to resolve path: {}", LogTag, i + 1, mods.size(), mod.address_int(), e.what());
for (size_t i = 0; i < mods.size(); i++) {
const auto& mod = mods[i];
const auto path = mod.path();
if (!path) {
logging::W(
"{} [{}/{}] Module 0x{:X}: Failed to resolve path: {}",
LogTag,
i + 1,
mods.size(),
mod.address_int(),
path.error().describe());
return;
}
const auto moduleName = unicode::convert<std::string>(path.filename().wstring());
const auto version = mod.get_file_version()
.transform([](const auto& v) { return utils::format_file_version(v.get()); })
.value_or(L"<unknown>");
const auto description = mod.get_description()
.value_or(L"<unknown>");
logging::I(
R"({} [{}/{}] Module 0x{:X} ~ 0x{:X} (0x{:X}): "{}" ("{}" ver {}))",
LogTagW,
i + 1,
mods.size(),
mod.address_int(),
mod.address_int() + mod.image_size(),
mod.image_size(),
path->wstring(),
description,
version);
const auto moduleName = unicode::convert<std::string>(path->filename().wstring());
std::vector<char> buf;
std::string formatBuf;
try {
const auto& sectionHeader = mod.section_header(".text");
const auto section = assume_nonempty_span(mod.span_as<char>(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize), ".text[VA:VA+VS]");
auto hFsDllRaw = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
const auto section = mod.span_as<char>(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize);
if (section.empty()) {
logging::W("{} Error: .text[VA:VA + VS] is empty", LogTag);
return;
}
auto hFsDllRaw = CreateFileW(path->c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (hFsDllRaw == INVALID_HANDLE_VALUE) {
logging::W("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError());
return;
}
auto hFsDll = std::unique_ptr<void, decltype(CloseHandle)*>(hFsDllRaw, &CloseHandle);
buf.resize(section.size());
auto hFsDll = std::unique_ptr<void, decltype(&CloseHandle)>(hFsDllRaw, &CloseHandle);
std::vector<char> buf(section.size());
SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT);
if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast<DWORD>(buf.size()), &read, nullptr)) {
if (read < section.size_bytes()) {
@ -72,28 +78,29 @@ void xivfixes::unhook_dll(bool bApply) {
return;
}
const auto doRestore = g_startInfo.BootUnhookDlls.contains(unicode::convert<std::string>(path.filename().u8string()));
const auto doRestore = g_startInfo.BootUnhookDlls.contains(unicode::convert<std::string>(path->filename().u8string()));
try {
std::optional<utils::memory_tenderizer> tenderizer;
for (size_t i = 0, instructionLength = 1, printed = 0; i < buf.size(); i += instructionLength) {
if (section[i] == buf[i]) {
std::string formatBuf;
for (size_t inst = 0, instructionLength = 1, printed = 0; inst < buf.size(); inst += instructionLength) {
if (section[inst] == buf[inst]) {
instructionLength = 1;
continue;
}
const auto rva = sectionHeader.VirtualAddress + i;
const auto rva = sectionHeader.VirtualAddress + inst;
nmd_x86_instruction instruction{};
if (!nmd_x86_decode(&section[i], section.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
if (!nmd_x86_decode(&section[inst], section.size() - inst, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
instructionLength = 1;
if (printed < 64) {
logging::W("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[i]));
logging::W("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[inst]));
printed++;
}
} else {
instructionLength = instruction.length;
if (printed < 64) {
formatBuf.resize(128);
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(&section[i]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES);
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(&section[inst]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES);
formatBuf.resize(strnlen(&formatBuf[0], formatBuf.size()));
const auto& directory = mod.data_directory(IMAGE_DIRECTORY_ENTRY_EXPORT);
@ -103,25 +110,25 @@ void xivfixes::unhook_dll(bool bApply) {
const auto functions = mod.span_as<DWORD>(exportDirectory.AddressOfFunctions, exportDirectory.NumberOfFunctions);
std::string resolvedExportName;
for (size_t j = 0; j < names.size(); ++j) {
for (size_t nameIndex = 0; nameIndex < names.size(); ++nameIndex) {
std::string_view name;
if (const char* pcszName = mod.address_as<char>(names[j]); pcszName < mod.address() || pcszName >= mod.address() + mod.image_size()) {
if (const char* pcszName = mod.address_as<char>(names[nameIndex]); pcszName < mod.address() || pcszName >= mod.address() + mod.image_size()) {
if (IsBadReadPtr(pcszName, 256)) {
logging::W("{} Name #{} points to an invalid address outside the executable. Skipping.", LogTag, j);
logging::W("{} Name #{} points to an invalid address outside the executable. Skipping.", LogTag, nameIndex);
continue;
}
name = std::string_view(pcszName, strnlen(pcszName, 256));
logging::W("{} Name #{} points to a seemingly valid address outside the executable: {}", LogTag, j, name);
logging::W("{} Name #{} points to a seemingly valid address outside the executable: {}", LogTag, nameIndex, name);
}
if (ordinals[j] >= functions.size()) {
logging::W("{} Ordinal #{} points to function index #{} >= #{}. Skipping.", LogTag, j, ordinals[j], functions.size());
if (ordinals[nameIndex] >= functions.size()) {
logging::W("{} Ordinal #{} points to function index #{} >= #{}. Skipping.", LogTag, nameIndex, ordinals[nameIndex], functions.size());
continue;
}
const auto rva = functions[ordinals[j]];
if (rva == &section[i] - mod.address()) {
const auto rva = functions[ordinals[nameIndex]];
if (rva == &section[inst] - mod.address()) {
resolvedExportName = std::format("[export:{}]", name);
break;
}
@ -135,7 +142,7 @@ void xivfixes::unhook_dll(bool bApply) {
if (doRestore) {
if (!tenderizer)
tenderizer.emplace(section, PAGE_EXECUTE_READWRITE);
memcpy(&section[i], &buf[i], instructionLength);
memcpy(&section[inst], &buf[inst], instructionLength);
}
}
@ -147,22 +154,8 @@ void xivfixes::unhook_dll(bool bApply) {
} catch (const std::exception& e) {
logging::W("{} Error: {}", LogTag, e.what());
}
};
// This is needed since try and __try cannot be used in the same function. Lambdas circumvent the limitation.
const auto windows_exception_handler = [&]() {
for (size_t i = 0; i < mods.size(); i++) {
const auto& mod = mods[i];
__try {
test_module(i, mod);
} __except (EXCEPTION_EXECUTE_HANDLER) {
logging::W("{} Error: Access Violation", LogTag);
}
}
};
windows_exception_handler();
}
using TFnGetInputDeviceManager = void* ();
static TFnGetInputDeviceManager* GetGetInputDeviceManager(HWND hwnd) {
@ -294,13 +287,11 @@ static bool is_xivalex(const std::filesystem::path& dllPath) {
static bool is_openprocess_already_dealt_with() {
static const auto s_value = [] {
for (const auto& mod : utils::loaded_module::all_modules()) {
try {
if (is_xivalex(mod.path()))
const auto path = mod.path().value_or({});
if (path.empty())
continue;
if (is_xivalex(path))
return true;
} catch (...) {
// pass
}
}
return false;
}();
@ -650,43 +641,22 @@ void xivfixes::symbol_load_patches(bool bApply) {
void xivfixes::disable_game_debugging_protection(bool bApply) {
static const char* LogTag = "[xivfixes:disable_game_debugging_protection]";
static const std::vector<uint8_t> patchBytes = {
0x31, 0xC0, // XOR EAX, EAX
0x90, // NOP
0x90, // NOP
0x90, // NOP
0x90 // NOP
};
if (!bApply)
return;
static std::optional<hooks::import_hook<decltype(IsDebuggerPresent)>> s_hookIsDebuggerPresent;
if (bApply) {
if (!g_startInfo.BootEnabledGameFixes.contains("disable_game_debugging_protection")) {
logging::I("{} Turned off via environment variable.", LogTag);
return;
}
// Find IsDebuggerPresent in Framework.Tick()
const char* matchPtr = utils::signature_finder()
.look_in(utils::loaded_module(g_hGameInstance), ".text")
.look_for_hex("FF 15 ?? ?? ?? ?? 85 C0 74 13 41")
.find_one()
.Match.data();
if (!matchPtr) {
logging::E("{} Failed to find signature.", LogTag);
return;
}
void* address = const_cast<void*>(static_cast<const void*>(matchPtr));
DWORD oldProtect;
if (VirtualProtect(address, patchBytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect)) {
memcpy(address, patchBytes.data(), patchBytes.size());
VirtualProtect(address, patchBytes.size(), oldProtect, &oldProtect);
logging::I("{} Patch applied at address 0x{:X}.", LogTag, reinterpret_cast<uintptr_t>(address));
s_hookIsDebuggerPresent.emplace("kernel32.dll!IsDebuggerPresent", "kernel32.dll", "IsDebuggerPresent", 0);
s_hookIsDebuggerPresent->set_detour([]() { return false; });
logging::I("{} Enable", LogTag);
} else {
logging::E("{} Failed to change memory protection.", LogTag);
if (s_hookIsDebuggerPresent) {
logging::I("{} Disable", LogTag);
s_hookIsDebuggerPresent.reset();
}
}
}

View file

@ -38,7 +38,7 @@
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard>stdcpp23</LanguageStandard>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>

View file

@ -6,7 +6,7 @@
<PropertyGroup Label="Feature">
<Description>XIV Launcher addon framework</Description>
<DalamudVersion>13.0.0.0</DalamudVersion>
<DalamudVersion>13.0.0.3</DalamudVersion>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version>
<FileVersion>$(DalamudVersion)</FileVersion>
@ -68,7 +68,7 @@
<PackageReference Include="goatcorp.Reloaded.Assembler" Version="1.0.14-goatcorp3" />
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageReference Include="Lumina" Version="$(LuminaVersion)" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.7" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View file

@ -38,7 +38,7 @@ public abstract unsafe class AddonArgs
/// </summary>
/// <param name="name">The name to check.</param>
/// <returns>Whether it is the case.</returns>
internal bool IsAddon(ReadOnlySpan<char> name)
internal bool IsAddon(string name)
{
if (this.Addon.IsNull)
return false;
@ -46,12 +46,10 @@ public abstract unsafe class AddonArgs
if (name.Length is 0 or > 32)
return false;
var addonName = this.Addon.Name;
if (string.IsNullOrEmpty(addonName))
if (string.IsNullOrEmpty(this.Addon.Name))
return false;
return name == addonName;
return name == this.Addon.Name;
}
/// <summary>

View file

@ -38,7 +38,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
private readonly ClientStateAddressResolver address;
private readonly Hook<EventFramework.Delegates.SetTerritoryTypeId> setupTerritoryTypeHook;
private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook;
private readonly Hook<LogoutCallbackInterface.Delegates.OnLogout> onLogoutHook;
private Hook<LogoutCallbackInterface.Delegates.OnLogout> onLogoutHook;
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get();
@ -64,14 +64,14 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
this.setupTerritoryTypeHook = Hook<EventFramework.Delegates.SetTerritoryTypeId>.FromAddress(setTerritoryTypeAddr, this.SetupTerritoryTypeDetour);
this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour);
this.onLogoutHook = Hook<LogoutCallbackInterface.Delegates.OnLogout>.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour);
this.framework.Update += this.FrameworkOnOnUpdateEvent;
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
this.setupTerritoryTypeHook.Enable();
this.uiModuleHandlePacketHook.Enable();
this.onLogoutHook.Enable();
this.framework.RunOnTick(this.Setup);
}
private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet);
@ -180,8 +180,25 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
}
private unsafe void Setup()
{
this.onLogoutHook = Hook<LogoutCallbackInterface.Delegates.OnLogout>.FromAddress((nint)AgentLobby.Instance()->LogoutCallbackInterface.VirtualTable->OnLogout, this.OnLogoutDetour);
this.onLogoutHook.Enable();
this.TerritoryType = (ushort)GameMain.Instance()->CurrentTerritoryTypeId;
}
private unsafe void SetupTerritoryTypeDetour(EventFramework* eventFramework, ushort territoryType)
{
this.SetTerritoryType(territoryType);
this.setupTerritoryTypeHook.Original(eventFramework, territoryType);
}
private unsafe void SetTerritoryType(ushort territoryType)
{
if (this.TerritoryType == territoryType)
return;
Log.Debug("TerritoryType changed: {0}", territoryType);
this.TerritoryType = territoryType;
@ -207,8 +224,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
}
}
}
this.setupTerritoryTypeHook.Original(eventFramework, territoryType);
}
private unsafe void UIModuleHandlePacketDetour(

View file

@ -1031,6 +1031,13 @@ public enum SystemConfigOption
[GameConfigOption("TitleScreenType", ConfigType.UInt)]
TitleScreenType,
/// <summary>
/// System option with the internal name DisplayObjectLimitType2.
/// This option is a UInt.
/// </summary>
[GameConfigOption("DisplayObjectLimitType2", ConfigType.UInt)]
DisplayObjectLimitType2,
/// <summary>
/// System option with the internal name AccessibilitySoundVisualEnable.
/// This option is a UInt.
@ -1115,6 +1122,13 @@ public enum SystemConfigOption
[GameConfigOption("CameraZoom", ConfigType.UInt)]
CameraZoom,
/// <summary>
/// System option with the internal name DynamicAroundRangeMode.
/// This option is a UInt.
/// </summary>
[GameConfigOption("DynamicAroundRangeMode", ConfigType.UInt)]
DynamicAroundRangeMode,
/// <summary>
/// System option with the internal name DynamicRezoType.
/// This option is a UInt.

View file

@ -2032,6 +2032,13 @@ public enum UiConfigOption
[GameConfigOption("NamePlateDispJobIconInInstanceOther", ConfigType.UInt)]
NamePlateDispJobIconInInstanceOther,
/// <summary>
/// UiConfig option with the internal name LogNamePlateDispEnemyCast.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogNamePlateDispEnemyCast", ConfigType.UInt)]
LogNamePlateDispEnemyCast,
/// <summary>
/// UiConfig option with the internal name ActiveInfo.
/// This option is a UInt.
@ -2690,6 +2697,594 @@ public enum UiConfigOption
[GameConfigOption("LogColorOtherClass", ConfigType.UInt)]
LogColorOtherClass,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleEnableChatBubble.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleEnableChatBubble", ConfigType.UInt)]
LogChatBubbleEnableChatBubble,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShowMax.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShowMax", ConfigType.UInt)]
LogChatBubbleShowMax,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShowOwnMessage.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShowOwnMessage", ConfigType.UInt)]
LogChatBubbleShowOwnMessage,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShowDuringBattle.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShowDuringBattle", ConfigType.UInt)]
LogChatBubbleShowDuringBattle,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleSizeType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleSizeType", ConfigType.UInt)]
LogChatBubbleSizeType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShowLargePvP.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShowLargePvP", ConfigType.UInt)]
LogChatBubbleShowLargePvP,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShowQuickChat.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShowQuickChat", ConfigType.UInt)]
LogChatBubbleShowQuickChat,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleDispRowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleDispRowType", ConfigType.UInt)]
LogChatBubbleDispRowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleSayShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleSayShowType", ConfigType.UInt)]
LogChatBubbleSayShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleSayFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleSayFontColor", ConfigType.UInt)]
LogChatBubbleSayFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleSayWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleSayWindowColor", ConfigType.UInt)]
LogChatBubbleSayWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleYellShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleYellShowType", ConfigType.UInt)]
LogChatBubbleYellShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleYellFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleYellFontColor", ConfigType.UInt)]
LogChatBubbleYellFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleYellWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleYellWindowColor", ConfigType.UInt)]
LogChatBubbleYellWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShoutShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShoutShowType", ConfigType.UInt)]
LogChatBubbleShoutShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShoutFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShoutFontColor", ConfigType.UInt)]
LogChatBubbleShoutFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleShoutWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleShoutWindowColor", ConfigType.UInt)]
LogChatBubbleShoutWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleTellShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleTellShowType", ConfigType.UInt)]
LogChatBubbleTellShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleTellFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleTellFontColor", ConfigType.UInt)]
LogChatBubbleTellFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleTellWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleTellWindowColor", ConfigType.UInt)]
LogChatBubbleTellWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubblePartyShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubblePartyShowType", ConfigType.UInt)]
LogChatBubblePartyShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubblePartyFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubblePartyFontColor", ConfigType.UInt)]
LogChatBubblePartyFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubblePartyWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubblePartyWindowColor", ConfigType.UInt)]
LogChatBubblePartyWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleAllianceShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleAllianceShowType", ConfigType.UInt)]
LogChatBubbleAllianceShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleAllianceFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleAllianceFontColor", ConfigType.UInt)]
LogChatBubbleAllianceFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleAllianceWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleAllianceWindowColor", ConfigType.UInt)]
LogChatBubbleAllianceWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleFcShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleFcShowType", ConfigType.UInt)]
LogChatBubbleFcShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleFcFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleFcFontColor", ConfigType.UInt)]
LogChatBubbleFcFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleFcWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleFcWindowColor", ConfigType.UInt)]
LogChatBubbleFcWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleBeginnerShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleBeginnerShowType", ConfigType.UInt)]
LogChatBubbleBeginnerShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleBeginnerFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleBeginnerFontColor", ConfigType.UInt)]
LogChatBubbleBeginnerFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleBeginnerWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleBeginnerWindowColor", ConfigType.UInt)]
LogChatBubbleBeginnerWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubblePvpteamShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubblePvpteamShowType", ConfigType.UInt)]
LogChatBubblePvpteamShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubblePvpteamFontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubblePvpteamFontColor", ConfigType.UInt)]
LogChatBubblePvpteamFontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubblePvpteamWindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubblePvpteamWindowColor", ConfigType.UInt)]
LogChatBubblePvpteamWindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs1ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs1ShowType", ConfigType.UInt)]
LogChatBubbleLs1ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs1FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs1FontColor", ConfigType.UInt)]
LogChatBubbleLs1FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs1WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs1WindowColor", ConfigType.UInt)]
LogChatBubbleLs1WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs2ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs2ShowType", ConfigType.UInt)]
LogChatBubbleLs2ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs2FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs2FontColor", ConfigType.UInt)]
LogChatBubbleLs2FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs2WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs2WindowColor", ConfigType.UInt)]
LogChatBubbleLs2WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs3ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs3ShowType", ConfigType.UInt)]
LogChatBubbleLs3ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs3FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs3FontColor", ConfigType.UInt)]
LogChatBubbleLs3FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs3WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs3WindowColor", ConfigType.UInt)]
LogChatBubbleLs3WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs4ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs4ShowType", ConfigType.UInt)]
LogChatBubbleLs4ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs4FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs4FontColor", ConfigType.UInt)]
LogChatBubbleLs4FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs4WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs4WindowColor", ConfigType.UInt)]
LogChatBubbleLs4WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs5ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs5ShowType", ConfigType.UInt)]
LogChatBubbleLs5ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs5FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs5FontColor", ConfigType.UInt)]
LogChatBubbleLs5FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs5WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs5WindowColor", ConfigType.UInt)]
LogChatBubbleLs5WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs6ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs6ShowType", ConfigType.UInt)]
LogChatBubbleLs6ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs6FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs6FontColor", ConfigType.UInt)]
LogChatBubbleLs6FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs6WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs6WindowColor", ConfigType.UInt)]
LogChatBubbleLs6WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs7ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs7ShowType", ConfigType.UInt)]
LogChatBubbleLs7ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs7FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs7FontColor", ConfigType.UInt)]
LogChatBubbleLs7FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs7WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs7WindowColor", ConfigType.UInt)]
LogChatBubbleLs7WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs8ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs8ShowType", ConfigType.UInt)]
LogChatBubbleLs8ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs8FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs8FontColor", ConfigType.UInt)]
LogChatBubbleLs8FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleLs8WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleLs8WindowColor", ConfigType.UInt)]
LogChatBubbleLs8WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls1ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls1ShowType", ConfigType.UInt)]
LogChatBubbleCwls1ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls1FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls1FontColor", ConfigType.UInt)]
LogChatBubbleCwls1FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls1WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls1WindowColor", ConfigType.UInt)]
LogChatBubbleCwls1WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls2ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls2ShowType", ConfigType.UInt)]
LogChatBubbleCwls2ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls2FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls2FontColor", ConfigType.UInt)]
LogChatBubbleCwls2FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls2WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls2WindowColor", ConfigType.UInt)]
LogChatBubbleCwls2WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls3ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls3ShowType", ConfigType.UInt)]
LogChatBubbleCwls3ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls3FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls3FontColor", ConfigType.UInt)]
LogChatBubbleCwls3FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls3WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls3WindowColor", ConfigType.UInt)]
LogChatBubbleCwls3WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls4ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls4ShowType", ConfigType.UInt)]
LogChatBubbleCwls4ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls4FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls4FontColor", ConfigType.UInt)]
LogChatBubbleCwls4FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls4WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls4WindowColor", ConfigType.UInt)]
LogChatBubbleCwls4WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls5ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls5ShowType", ConfigType.UInt)]
LogChatBubbleCwls5ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls5FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls5FontColor", ConfigType.UInt)]
LogChatBubbleCwls5FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls5WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls5WindowColor", ConfigType.UInt)]
LogChatBubbleCwls5WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls6ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls6ShowType", ConfigType.UInt)]
LogChatBubbleCwls6ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls6FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls6FontColor", ConfigType.UInt)]
LogChatBubbleCwls6FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls6WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls6WindowColor", ConfigType.UInt)]
LogChatBubbleCwls6WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls7ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls7ShowType", ConfigType.UInt)]
LogChatBubbleCwls7ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls7FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls7FontColor", ConfigType.UInt)]
LogChatBubbleCwls7FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls7WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls7WindowColor", ConfigType.UInt)]
LogChatBubbleCwls7WindowColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls8ShowType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls8ShowType", ConfigType.UInt)]
LogChatBubbleCwls8ShowType,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls8FontColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls8FontColor", ConfigType.UInt)]
LogChatBubbleCwls8FontColor,
/// <summary>
/// UiConfig option with the internal name LogChatBubbleCwls8WindowColor.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogChatBubbleCwls8WindowColor", ConfigType.UInt)]
LogChatBubbleCwls8WindowColor,
/// <summary>
/// UiConfig option with the internal name LogPermeationRateInput.
/// This option is a UInt.
/// </summary>
[GameConfigOption("LogPermeationRateInput", ConfigType.UInt)]
LogPermeationRateInput,
/// <summary>
/// UiConfig option with the internal name ChatType.
/// This option is a UInt.
@ -3453,6 +4048,27 @@ public enum UiConfigOption
[GameConfigOption("GPoseMotionFilterAction", ConfigType.UInt)]
GPoseMotionFilterAction,
/// <summary>
/// UiConfig option with the internal name GPoseRollRotationCameraCorrection.
/// This option is a UInt.
/// </summary>
[GameConfigOption("GPoseRollRotationCameraCorrection", ConfigType.UInt)]
GPoseRollRotationCameraCorrection,
/// <summary>
/// UiConfig option with the internal name GPoseInitCameraFocus.
/// This option is a UInt.
/// </summary>
[GameConfigOption("GPoseInitCameraFocus", ConfigType.UInt)]
GPoseInitCameraFocus,
/// <summary>
/// UiConfig option with the internal name GposePortraitRotateType.
/// This option is a UInt.
/// </summary>
[GameConfigOption("GposePortraitRotateType", ConfigType.UInt)]
GposePortraitRotateType,
/// <summary>
/// UiConfig option with the internal name LsListSortPriority.
/// This option is a UInt.

View file

@ -41,7 +41,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
private static readonly ModuleLog Log = new("ChatGui");
private readonly Queue<XivChatEntry> chatQueue = new();
private readonly Dictionary<(string PluginName, Guid CommandId), Action<Guid, SeString>> dalamudLinkHandlers = new();
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
private readonly Hook<PrintMessageDelegate> printMessageHook;
private readonly Hook<InventoryItem.Delegates.Copy> inventoryItemCopyHook;
@ -50,7 +50,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
private ImmutableDictionary<(string PluginName, Guid CommandId), Action<Guid, SeString>>? dalamudLinkHandlersCopy;
private ImmutableDictionary<(string PluginName, uint CommandId), Action<uint, SeString>>? dalamudLinkHandlersCopy;
private uint dalamudChatHandlerId = 1000;
[ServiceManager.ServiceConstructor]
private ChatGui()
@ -86,7 +87,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
public byte LastLinkedItemFlags { get; private set; }
/// <inheritdoc/>
public IReadOnlyDictionary<(string PluginName, Guid CommandId), Action<Guid, SeString>> RegisteredLinkHandlers
public IReadOnlyDictionary<(string PluginName, uint CommandId), Action<uint, SeString>> RegisteredLinkHandlers
{
get
{
@ -164,19 +165,33 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
#region Chat Links
/// <inheritdoc/>
public DalamudLinkPayload AddChatLinkHandler(Action<Guid, SeString> commandAction)
/// <summary>
/// Register a chat link handler.
/// </summary>
/// <remarks>Internal use only.</remarks>
/// <param name="commandAction">The action to be executed.</param>
/// <returns>Returns an SeString payload for the link.</returns>
public DalamudLinkPayload AddChatLinkHandler(Action<uint, SeString> commandAction)
{
return this.AddChatLinkHandler("Dalamud", commandAction);
return this.AddChatLinkHandler("Dalamud", this.dalamudChatHandlerId++, commandAction);
}
/// <inheritdoc/>
public void RemoveChatLinkHandler(Guid commandId)
/// <remarks>Internal use only.</remarks>
public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action<uint, SeString> commandAction)
{
return this.AddChatLinkHandler("Dalamud", commandId, commandAction);
}
/// <inheritdoc/>
/// <remarks>Internal use only.</remarks>
public void RemoveChatLinkHandler(uint commandId)
{
this.RemoveChatLinkHandler("Dalamud", commandId);
}
/// <inheritdoc/>
/// <remarks>Internal use only.</remarks>
public void RemoveChatLinkHandler()
{
this.RemoveChatLinkHandler("Dalamud");
@ -240,11 +255,11 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
/// Create a link handler.
/// </summary>
/// <param name="pluginName">The name of the plugin handling the link.</param>
/// <param name="commandId">The ID of the command to run.</param>
/// <param name="commandAction">The command action itself.</param>
/// <returns>A payload for handling.</returns>
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, Action<Guid, SeString> commandAction)
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
{
var commandId = Guid.CreateVersion7();
var payload = new DalamudLinkPayload { Plugin = pluginName, CommandId = commandId };
lock (this.dalamudLinkHandlers)
{
@ -277,7 +292,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
/// </summary>
/// <param name="pluginName">The name of the plugin handling the link.</param>
/// <param name="commandId">The ID of the command to be removed.</param>
internal void RemoveChatLinkHandler(string pluginName, Guid commandId)
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
{
lock (this.dalamudLinkHandlers)
{
@ -400,13 +415,13 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
if (!terminatedSender.SequenceEqual(possiblyModifiedSenderData))
{
Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(terminatedSender)} -> {parsedSender}");
Log.Verbose($"HandlePrintMessageDetour Sender modified: {new ReadOnlySeStringSpan(terminatedSender).ToMacroString()} -> {new ReadOnlySeStringSpan(possiblyModifiedSenderData).ToMacroString()}");
sender->SetString(possiblyModifiedSenderData);
}
if (!terminatedMessage.SequenceEqual(possiblyModifiedMessageData))
{
Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(terminatedMessage)} -> {parsedMessage}");
Log.Verbose($"HandlePrintMessageDetour Message modified: {new ReadOnlySeStringSpan(terminatedMessage).ToMacroString()} -> {new ReadOnlySeStringSpan(possiblyModifiedMessageData).ToMacroString()}");
message->SetString(possiblyModifiedMessageData);
}
@ -534,7 +549,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags;
/// <inheritdoc/>
public IReadOnlyDictionary<(string PluginName, Guid CommandId), Action<Guid, SeString>> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers;
public IReadOnlyDictionary<(string PluginName, uint CommandId), Action<uint, SeString>> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers;
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
@ -551,11 +566,11 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
}
/// <inheritdoc/>
public DalamudLinkPayload AddChatLinkHandler(Action<Guid, SeString> commandAction)
=> this.chatGuiService.AddChatLinkHandler(this.plugin.InternalName, commandAction);
public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action<uint, SeString> commandAction)
=> this.chatGuiService.AddChatLinkHandler(this.plugin.InternalName, commandId, commandAction);
/// <inheritdoc/>
public void RemoveChatLinkHandler(Guid commandId)
public void RemoveChatLinkHandler(uint commandId)
=> this.chatGuiService.RemoveChatLinkHandler(this.plugin.InternalName, commandId);
/// <inheritdoc/>

View file

@ -586,8 +586,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
newTextNode->LineSpacing = 12;
newTextNode->AlignmentFontType = 5;
newTextNode->FontSize = 14;
newTextNode->TextFlags = (byte)TextFlags.Edge;
newTextNode->TextFlags2 = 0;
newTextNode->TextFlags = TextFlags.Edge;
if (this.emptyString == null)
this.emptyString = Utf8String.FromString(" ");
@ -642,7 +641,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
break;
case AddonEventType.MouseClick:
dtrBarEntry.OnClick?.Invoke(new AddonMouseEventData(eventData));
dtrBarEntry.OnClick?.Invoke(DtrInteractionEvent.FromMouseEvent(new AddonMouseEventData(eventData)));
break;
}
}

View file

@ -43,6 +43,11 @@ public interface IReadOnlyDtrBarEntry
/// Gets a value indicating whether the user has hidden this entry from view through the Dalamud settings.
/// </summary>
public bool UserHidden { get; }
/// <summary>
/// Gets an action to be invoked when the user clicks on the dtr entry.
/// </summary>
public Action<DtrInteractionEvent>? OnClick { get; }
}
/// <summary>
@ -68,7 +73,7 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry
/// <summary>
/// Gets or sets an action to be invoked when the user clicks on the dtr entry.
/// </summary>
public Action<AddonMouseEventData>? OnClick { get; set; }
public new Action<DtrInteractionEvent>? OnClick { get; set; }
/// <summary>
/// Remove this entry from the bar.
@ -118,7 +123,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
public SeString? Tooltip { get; set; }
/// <inheritdoc/>
public Action<AddonMouseEventData>? OnClick { get; set; }
public Action<DtrInteractionEvent>? OnClick { get; set; }
/// <inheritdoc/>
public bool HasClickAction => this.OnClick != null;

View file

@ -0,0 +1,59 @@
using System.Numerics;
using Dalamud.Game.Addon.Events.EventDataTypes;
namespace Dalamud.Game.Gui.Dtr;
/// <summary>
/// Represents an interaction event from the DTR system.
/// </summary>
public class DtrInteractionEvent
{
/// <summary>
/// Gets the type of mouse click (left or right).
/// </summary>
public MouseClickType ClickType { get; init; }
/// <summary>
/// Gets the modifier keys that were held during the click.
/// </summary>
public ClickModifierKeys ModifierKeys { get; init; }
/// <summary>
/// Gets the scroll direction of the mouse wheel, if applicable.
/// </summary>
public MouseScrollDirection ScrollDirection { get; init; }
/// <summary>
/// Gets lower-level mouse data, if this event came from native UI.
///
/// Can only be set by Dalamud. If null, this event was manually created.
/// </summary>
public AddonMouseEventData? AtkEventSource { get; private init; }
/// <summary>
/// Gets the position of the mouse cursor when the event occurred.
/// </summary>
public Vector2 Position { get; init; }
/// <summary>
/// Helper to create a <see cref="DtrInteractionEvent"/> from an <see cref="AddonMouseEventData"/>.
/// </summary>
/// <param name="ev">The event.</param>
/// <returns>A better event.</returns>
public static DtrInteractionEvent FromMouseEvent(AddonMouseEventData ev)
{
return new DtrInteractionEvent
{
AtkEventSource = ev,
ClickType = ev.IsLeftClick ? MouseClickType.Left : MouseClickType.Right,
ModifierKeys = (ev.IsAltHeld ? ClickModifierKeys.Alt : 0) |
(ev.IsControlHeld ? ClickModifierKeys.Ctrl : 0) |
(ev.IsShiftHeld ? ClickModifierKeys.Shift : 0),
ScrollDirection = ev.IsScrollUp ? MouseScrollDirection.Up :
ev.IsScrollDown ? MouseScrollDirection.Down :
MouseScrollDirection.None,
Position = ev.Position,
};
}
}

View file

@ -0,0 +1,65 @@
namespace Dalamud.Game.Gui.Dtr;
/// <summary>
/// An enum representing the mouse click types.
/// </summary>
public enum MouseClickType
{
/// <summary>
/// A left click.
/// </summary>
Left,
/// <summary>
/// A right click.
/// </summary>
Right,
}
/// <summary>
/// Modifier keys that can be held during a mouse click event.
/// </summary>
[Flags]
public enum ClickModifierKeys
{
/// <summary>
/// No modifiers were present.
/// </summary>
None = 0,
/// <summary>
/// The CTRL key was held.
/// </summary>
Ctrl = 1 << 0,
/// <summary>
/// The ALT key was held.
/// </summary>
Alt = 1 << 1,
/// <summary>
/// The SHIFT key was held.
/// </summary>
Shift = 1 << 2,
}
/// <summary>
/// Possible directions for scroll wheel events.
/// </summary>
public enum MouseScrollDirection
{
/// <summary>
/// No scrolling.
/// </summary>
None = 0,
/// <summary>
/// A scroll up event.
/// </summary>
Up = 1,
/// <summary>
/// A scroll down event.
/// </summary>
Down = -1,
}

View file

@ -343,11 +343,11 @@ public class SigScanner : IDisposable, ISigScanner
break;
var scanRet = mBase + index;
mBase = scanRet + 1;
if (this.IsCopy)
scanRet -= this.moduleCopyOffset;
yield return scanRet;
mBase = scanRet + 1;
}
}

View file

@ -121,6 +121,15 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
return this.Evaluate(ReadOnlySeString.FromMacroString(macroString).AsSpan(), localParameters, language);
}
/// <inheritdoc/>
public ReadOnlySeString EvaluateMacroString(
ReadOnlySpan<byte> macroString,
Span<SeStringParameter> localParameters = default,
ClientLanguage? language = null)
{
return this.Evaluate(ReadOnlySeString.FromMacroString(macroString).AsSpan(), localParameters, language);
}
/// <inheritdoc/>
public ReadOnlySeString EvaluateFromAddon(
uint addonId,
@ -247,6 +256,9 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
case MacroCode.Switch:
return this.TryResolveSwitch(in context, payload);
case MacroCode.SwitchPlatform:
return this.TryResolveSwitchPlatform(in context, payload);
case MacroCode.PcName:
return this.TryResolvePcName(in context, payload);
@ -450,6 +462,29 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
return false;
}
private bool TryResolveSwitchPlatform(in SeStringContext context, in ReadOnlySePayloadSpan payload)
{
if (!payload.TryGetExpression(out var expr1))
return false;
if (!expr1.TryGetInt(out var intVal))
return false;
// Our version of the game uses IsMacClient() here and the
// Xbox version seems to always return 7 for the platform.
var platform = Util.IsWine() ? 5 : 3;
// The sheet is seeminly split into first 20 rows for wired controllers
// and the last 20 rows for wireless controllers.
var rowId = (uint)((20 * ((intVal - 1) / 20)) + (platform - 4 < 2 ? 2 : 1));
if (!this.dataManager.GetExcelSheet<Platform>().TryGetRow(rowId, out var platformRow))
return false;
context.Builder.Append(platformRow.Name);
return true;
}
private unsafe bool TryResolvePcName(in SeStringContext context, in ReadOnlySePayloadSpan payload)
{
if (!payload.TryGetExpression(out var eEntityId))

View file

@ -77,4 +77,6 @@ public readonly struct SeStringParameter
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
public static implicit operator SeStringParameter(string value) => new(value);
public static implicit operator SeStringParameter(ReadOnlySpan<byte> value) => new(value);
}

View file

@ -16,7 +16,7 @@ public class DalamudLinkPayload : Payload
public override PayloadType Type => PayloadType.DalamudLink;
/// <summary>Gets the plugin command ID to be linked.</summary>
public Guid CommandId { get; internal set; }
public uint CommandId { get; internal set; }
/// <summary>Gets an optional extra integer value 1.</summary>
public int Extra1 { get; internal set; }
@ -40,7 +40,7 @@ public class DalamudLinkPayload : Payload
var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get();
var res = ssb.BeginMacro(MacroCode.Link)
.AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1)
.AppendStringExpression(this.CommandId.ToString())
.AppendUIntExpression(this.CommandId)
.AppendIntExpression(this.Extra1)
.AppendIntExpression(this.Extra2)
.BeginStringExpression()
@ -72,15 +72,15 @@ public class DalamudLinkPayload : Payload
if (!pluginExpression.TryGetString(out var pluginString))
return;
if (!commandIdExpression.TryGetString(out var commandId))
if (!commandIdExpression.TryGetUInt(out var commandId))
return;
this.Plugin = pluginString.ExtractText();
this.CommandId = Guid.Parse(commandId.ExtractText());
this.CommandId = commandId;
}
else
{
if (!commandIdExpression.TryGetString(out var commandId))
if (!commandIdExpression.TryGetUInt(out var commandId))
return;
if (!extra1Expression.TryGetInt(out var extra1))
@ -102,7 +102,7 @@ public class DalamudLinkPayload : Payload
return;
}
this.CommandId = Guid.Parse(commandId.ExtractText());
this.CommandId = commandId;
this.Extra1 = extra1;
this.Extra2 = extra2;
this.Plugin = extraData[0];

View file

@ -0,0 +1,4 @@
namespace Dalamud.Interface.ImGuiBackend.Delegates;
/// <summary>Delegate to be called when ImGui should be used to layout now.</summary>
public delegate void ImGuiBuildUiDelegate();

View file

@ -0,0 +1,4 @@
namespace Dalamud.Interface.ImGuiBackend.Delegates;
/// <summary>Delegate to be called on new input frame.</summary>
public delegate void ImGuiNewInputFrameDelegate();

View file

@ -0,0 +1,4 @@
namespace Dalamud.Interface.ImGuiBackend.Delegates;
/// <summary>Delegate to be called on new render frame.</summary>
public delegate void ImGuiNewRenderFrameDelegate();

View file

@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using Dalamud.Bindings.ImGui;
using Dalamud.Bindings.ImGuizmo;
using Dalamud.Bindings.ImPlot;
using Dalamud.Interface.ImGuiBackend.Delegates;
using Dalamud.Interface.ImGuiBackend.Helpers;
using Dalamud.Interface.ImGuiBackend.InputHandler;
using Dalamud.Interface.ImGuiBackend.Renderers;
@ -93,13 +94,13 @@ internal sealed unsafe class Dx11Win32Backend : IWin32Backend
~Dx11Win32Backend() => this.ReleaseUnmanagedResources();
/// <inheritdoc/>
public event IImGuiBackend.BuildUiDelegate? BuildUi;
public event ImGuiBuildUiDelegate? BuildUi;
/// <inheritdoc/>
public event IImGuiBackend.NewInputFrameDelegate? NewInputFrame;
public event ImGuiNewInputFrameDelegate? NewInputFrame;
/// <inheritdoc/>
public event IImGuiBackend.NewRenderFrameDelegate? NewRenderFrame;
public event ImGuiNewRenderFrameDelegate? NewRenderFrame;
/// <inheritdoc/>
public bool UpdateCursor

View file

@ -1,4 +1,5 @@
using Dalamud.Interface.ImGuiBackend.InputHandler;
using Dalamud.Interface.ImGuiBackend.Delegates;
using Dalamud.Interface.ImGuiBackend.InputHandler;
using Dalamud.Interface.ImGuiBackend.Renderers;
namespace Dalamud.Interface.ImGuiBackend;
@ -6,23 +7,14 @@ namespace Dalamud.Interface.ImGuiBackend;
/// <summary>Backend for ImGui.</summary>
internal interface IImGuiBackend : IDisposable
{
/// <summary>Delegate to be called when ImGui should be used to layout now.</summary>
public delegate void BuildUiDelegate();
/// <summary>Delegate to be called on new input frame.</summary>
public delegate void NewInputFrameDelegate();
/// <summary>Delegaet to be called on new render frame.</summary>
public delegate void NewRenderFrameDelegate();
/// <summary>User methods invoked every ImGui frame to construct custom UIs.</summary>
event BuildUiDelegate? BuildUi;
event ImGuiBuildUiDelegate? BuildUi;
/// <summary>User methods invoked every ImGui frame on handling inputs.</summary>
event NewInputFrameDelegate? NewInputFrame;
event ImGuiNewInputFrameDelegate? NewInputFrame;
/// <summary>User methods invoked every ImGui frame on handling renders.</summary>
event NewRenderFrameDelegate? NewRenderFrame;
event ImGuiNewRenderFrameDelegate? NewRenderFrame;
/// <summary>Gets or sets a value indicating whether the cursor should be overridden with the ImGui cursor.
/// </summary>

View file

@ -1,19 +1,18 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Bindings.ImGui;
using Dalamud.Memory;
using Serilog;
using TerraFX.Interop.Windows;
using static Dalamud.Interface.ImGuiBackend.Helpers.ImGuiViewportHelpers;
using static TerraFX.Interop.Windows.Windows;
using ERROR = TerraFX.Interop.Windows.ERROR;
@ -245,6 +244,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
return default(LRESULT);
}
if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow))
ImGui.ClearWindowFocus();
break;
}
@ -371,6 +373,14 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
case WM.WM_DISPLAYCHANGE:
this.viewportHandler.UpdateMonitors();
break;
case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd:
if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
ReleaseCapture();
ImGui.GetIO().WantCaptureMouse = false;
ImGui.ClearWindowFocus();
break;
}
return null;
@ -531,7 +541,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
// We still want to return MA_NOACTIVATE
// https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mouseactivate
return 0x3;
return MA.MA_NOACTIVATE;
case WM.WM_NCHITTEST:
// Let mouse pass-through the window. This will allow the backend to set io.MouseHoveredViewport properly (which is OPTIONAL).
// The ImGuiViewportFlags_NoInputs flag is set while dragging a viewport, as want to detect the window behind the one we are dragging.
@ -539,8 +549,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
// your main loop after calling UpdatePlatformWindows(). Iterate all viewports/platform windows and pass the flag to your windowing system.
if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoInputs))
{
// https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest
return -1;
return HTTRANSPARENT;
}
break;
@ -575,51 +584,50 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
private struct ViewportHandler : IDisposable
{
[SuppressMessage("ReSharper", "CollectionNeverQueried.Local", Justification = "Keeping references alive")]
private readonly List<object> delegateReferences = new();
private static readonly string WindowClassName = typeof(ViewportHandler).FullName!;
private Win32InputHandler input;
private nint classNamePtr;
private bool wantUpdateMonitors = true;
public ViewportHandler(Win32InputHandler input)
{
this.input = input;
this.classNamePtr = Marshal.StringToHGlobalUni("ImGui Platform");
var pio = ImGui.GetPlatformIO();
pio.PlatformCreateWindow = this.RegisterFunctionPointer<CreateWindowDelegate>(this.OnCreateWindow);
pio.PlatformDestroyWindow = this.RegisterFunctionPointer<DestroyWindowDelegate>(this.OnDestroyWindow);
pio.PlatformShowWindow = this.RegisterFunctionPointer<ShowWindowDelegate>(this.OnShowWindow);
pio.PlatformSetWindowPos = this.RegisterFunctionPointer<SetWindowPosDelegate>(this.OnSetWindowPos);
pio.PlatformGetWindowPos = this.RegisterFunctionPointer<GetWindowPosDelegate>(this.OnGetWindowPos);
pio.PlatformSetWindowSize = this.RegisterFunctionPointer<SetWindowSizeDelegate>(this.OnSetWindowSize);
pio.PlatformGetWindowSize = this.RegisterFunctionPointer<GetWindowSizeDelegate>(this.OnGetWindowSize);
pio.PlatformSetWindowFocus = this.RegisterFunctionPointer<SetWindowFocusDelegate>(this.OnSetWindowFocus);
pio.PlatformGetWindowFocus = this.RegisterFunctionPointer<GetWindowFocusDelegate>(this.OnGetWindowFocus);
pio.PlatformGetWindowMinimized =
this.RegisterFunctionPointer<GetWindowMinimizedDelegate>(this.OnGetWindowMinimized);
pio.PlatformSetWindowTitle = this.RegisterFunctionPointer<SetWindowTitleDelegate>(this.OnSetWindowTitle);
pio.PlatformSetWindowAlpha = this.RegisterFunctionPointer<SetWindowAlphaDelegate>(this.OnSetWindowAlpha);
pio.PlatformUpdateWindow = this.RegisterFunctionPointer<UpdateWindowDelegate>(this.OnUpdateWindow);
pio.PlatformCreateWindow = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, void>)&OnCreateWindow;
pio.PlatformDestroyWindow = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, void>)&OnDestroyWindow;
pio.PlatformShowWindow = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, void>)&OnShowWindow;
pio.PlatformSetWindowPos = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, Vector2, void>)&OnSetWindowPos;
pio.PlatformGetWindowPos = (delegate* unmanaged[Cdecl]<Vector2*, ImGuiViewportPtr, Vector2*>)&OnGetWindowPos;
pio.PlatformSetWindowSize = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, Vector2, void>)&OnSetWindowSize;
pio.PlatformGetWindowSize = (delegate* unmanaged[Cdecl]<Vector2*, ImGuiViewportPtr, Vector2*>)&OnGetWindowSize;
pio.PlatformSetWindowFocus = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, void>)&OnSetWindowFocus;
pio.PlatformGetWindowFocus = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, byte>)&OnGetWindowFocus;
pio.PlatformGetWindowMinimized = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, byte>)&OnGetWindowMinimized;
pio.PlatformSetWindowTitle = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, byte*, void>)&OnSetWindowTitle;
pio.PlatformSetWindowAlpha = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, float, void>)&OnSetWindowAlpha;
pio.PlatformUpdateWindow = (delegate* unmanaged[Cdecl]<ImGuiViewportPtr, void>)&OnUpdateWindow;
// pio.Platform_SetImeInputPos = this.RegisterFunctionPointer<SetImeInputPosDelegate>(this.OnSetImeInputPos);
// pio.Platform_GetWindowDpiScale = this.RegisterFunctionPointer<GetWindowDpiScaleDelegate>(this.OnGetWindowDpiScale);
// pio.Platform_ChangedViewport = this.RegisterFunctionPointer<ChangedViewportDelegate>(this.OnChangedViewport);
fixed (char* windowClassNamePtr = WindowClassName)
{
var wcex = new WNDCLASSEXW
{
cbSize = (uint)sizeof(WNDCLASSEXW),
style = CS.CS_HREDRAW | CS.CS_VREDRAW,
hInstance = GetModuleHandleW(null),
hInstance = (HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module),
hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND),
lpfnWndProc = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal
.GetFunctionPointerForDelegate(this.input.wndProcDelegate),
lpszClassName = (ushort*)this.classNamePtr,
lpszClassName = (ushort*)windowClassNamePtr,
};
if (RegisterClassExW(&wcex) == 0)
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ?? new("RegisterClassEx Fail");
}
// Register main window handle (which is owned by the main application, not by us)
// This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports.
@ -647,11 +655,11 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
ImGui.GetPlatformIO().Handle->Monitors = default;
}
if (this.classNamePtr != 0)
fixed (char* windowClassNamePtr = WindowClassName)
{
UnregisterClassW((ushort*)this.classNamePtr, GetModuleHandleW(null));
Marshal.FreeHGlobal(this.classNamePtr);
this.classNamePtr = 0;
UnregisterClassW(
(ushort*)windowClassNamePtr,
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module));
}
pio.PlatformCreateWindow = null;
@ -740,13 +748,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
}
}
private void* RegisterFunctionPointer<T>(T obj)
{
this.delegateReferences.Add(obj);
return Marshal.GetFunctionPointerForDelegate(obj).ToPointer();
}
private void OnCreateWindow(ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnCreateWindow(ImGuiViewportPtr viewport)
{
var data = (ImGuiViewportDataWin32*)Marshal.AllocHGlobal(Marshal.SizeOf<ImGuiViewportDataWin32>());
viewport.PlatformUserData = data;
@ -774,12 +777,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
};
AdjustWindowRectEx(&rect, (uint)data->DwStyle, false, (uint)data->DwExStyle);
fixed (char* pwszWindowTitle = "Untitled")
fixed (char* windowClassNamePtr = WindowClassName)
{
data->Hwnd = CreateWindowExW(
(uint)data->DwExStyle,
(ushort*)this.classNamePtr,
(ushort*)pwszWindowTitle,
(ushort*)windowClassNamePtr,
(ushort*)windowClassNamePtr,
(uint)data->DwStyle,
rect.left,
rect.top,
@ -787,8 +790,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
rect.bottom - rect.top,
parentWindow,
default,
GetModuleHandleW(null),
default);
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module),
null);
}
data->HwndOwned = true;
@ -796,7 +799,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd;
}
private void OnDestroyWindow(ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnDestroyWindow(ImGuiViewportPtr viewport)
{
// This is also called on the main viewport for some reason, and we never set that viewport's PlatformUserData
if (viewport.PlatformUserData == null) return;
@ -807,7 +811,11 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
{
// Transfer capture so if we started dragging from a window that later disappears, we'll still receive the MOUSEUP event.
ReleaseCapture();
SetCapture(this.input.hWnd);
if (viewport.ParentViewportId != 0)
{
var parentViewport = ImGui.FindViewportByID(viewport.ParentViewportId);
SetCapture((HWND)parentViewport.PlatformHandle);
}
}
if (data->Hwnd != nint.Zero && data->HwndOwned)
@ -826,7 +834,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
viewport.PlatformUserData = viewport.PlatformHandle = null;
}
private void OnShowWindow(ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnShowWindow(ImGuiViewportPtr viewport)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
@ -836,7 +845,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
ShowWindow(data->Hwnd, SW.SW_SHOW);
}
private void OnUpdateWindow(ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnUpdateWindow(ImGuiViewportPtr viewport)
{
// (Optional) Update Win32 style if it changed _after_ creation.
// Generally they won't change unless configuration flags are changed, but advanced uses (such as manually rewriting viewport flags) make this useful.
@ -897,17 +907,18 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
}
}
private Vector2* OnGetWindowPos(Vector2* returnStorage, ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static Vector2* OnGetWindowPos(Vector2* returnValueStorage, ImGuiViewportPtr viewport)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
var pt = new POINT { x = 0, y = 0 };
ClientToScreen(data->Hwnd, &pt);
returnStorage->X = pt.x;
returnStorage->Y = pt.y;
return returnStorage;
*returnValueStorage = new(pt.x, pt.y);
return returnValueStorage;
}
private void OnSetWindowPos(ImGuiViewportPtr viewport, Vector2 pos)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnSetWindowPos(ImGuiViewportPtr viewport, Vector2 pos)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
var rect = new RECT((int)pos.X, (int)pos.Y, (int)pos.X, (int)pos.Y);
@ -924,17 +935,18 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
SWP.SWP_NOACTIVATE);
}
private Vector2* OnGetWindowSize(Vector2* returnStorage, ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static Vector2* OnGetWindowSize(Vector2* returnValueStorage, ImGuiViewportPtr viewport)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
RECT rect;
GetClientRect(data->Hwnd, &rect);
returnStorage->X = rect.right - rect.left;
returnStorage->Y = rect.bottom - rect.top;
return returnStorage;
*returnValueStorage = new(rect.right - rect.left, rect.bottom - rect.top);
return returnValueStorage;
}
private void OnSetWindowSize(ImGuiViewportPtr viewport, Vector2 size)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnSetWindowSize(ImGuiViewportPtr viewport, Vector2 size)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
@ -952,7 +964,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
SWP.SWP_NOACTIVATE);
}
private void OnSetWindowFocus(ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnSetWindowFocus(ImGuiViewportPtr viewport)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
@ -961,26 +974,30 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
SetFocus(data->Hwnd);
}
private bool OnGetWindowFocus(ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static byte OnGetWindowFocus(ImGuiViewportPtr viewport)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
return GetForegroundWindow() == data->Hwnd;
return GetForegroundWindow() == data->Hwnd ? (byte)1 : (byte)0;
}
private bool OnGetWindowMinimized(ImGuiViewportPtr viewport)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static byte OnGetWindowMinimized(ImGuiViewportPtr viewport)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
return IsIconic(data->Hwnd);
return IsIconic(data->Hwnd) ? (byte)1 : (byte)0;
}
private void OnSetWindowTitle(ImGuiViewportPtr viewport, string title)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnSetWindowTitle(ImGuiViewportPtr viewport, byte* title)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
fixed (char* pwszTitle = title)
fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title))
SetWindowTextW(data->Hwnd, (ushort*)pwszTitle);
}
private void OnSetWindowAlpha(ImGuiViewportPtr viewport, float alpha)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnSetWindowAlpha(ImGuiViewportPtr viewport, float alpha)
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
var style = GetWindowLongW(data->Hwnd, GWL.GWL_EXSTYLE);

View file

@ -223,15 +223,15 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
ImDrawDataPtr drawData,
bool clearRenderTarget)
{
// Do nothing when there's nothing to draw
if (drawData.IsNull || !drawData.Valid)
return;
// Avoid rendering when minimized
if (drawData.DisplaySize.X <= 0 || drawData.DisplaySize.Y <= 0)
return;
using var oldState = new D3D11DeviceContextStateBackup(this.featureLevel, this.context.Get());
// Setup desired DX state
this.SetupRenderState(drawData);
// Set up render target
this.context.Get()->OMSetRenderTargets(1, &renderTargetView, null);
if (clearRenderTarget)
{
@ -239,17 +239,17 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
this.context.Get()->ClearRenderTargetView(renderTargetView, (float*)&color);
}
if (!drawData.Valid || drawData.CmdListsCount == 0)
return;
// Stop if there's nothing to draw
var cmdLists = new Span<ImDrawListPtr>(drawData.Handle->CmdLists, drawData.Handle->CmdListsCount);
if (cmdLists.IsEmpty)
return;
// Create and grow vertex/index buffers if needed
if (this.vertexBufferSize < drawData.TotalVtxCount)
this.vertexBuffer.Dispose();
if (this.vertexBuffer.Get() is null)
{
this.vertexBufferSize = drawData.TotalVtxCount + 5000;
this.vertexBufferSize = drawData.TotalVtxCount + 8192;
var desc = new D3D11_BUFFER_DESC(
(uint)(sizeof(ImDrawVert) * this.vertexBufferSize),
(uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER,
@ -264,7 +264,7 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
this.indexBuffer.Dispose();
if (this.indexBuffer.Get() is null)
{
this.indexBufferSize = drawData.TotalIdxCount + 5000;
this.indexBufferSize = drawData.TotalIdxCount + 16384;
var desc = new D3D11_BUFFER_DESC(
(uint)(sizeof(ushort) * this.indexBufferSize),
(uint)D3D11_BIND_FLAG.D3D11_BIND_INDEX_BUFFER,
@ -275,9 +275,14 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
this.indexBuffer.Attach(buffer);
}
// Upload vertex/index data into a single contiguous GPU buffer
using var oldState = new D3D11DeviceContextStateBackup(this.featureLevel, this.context.Get());
// Setup desired DX state
this.SetupRenderState(drawData);
try
{
// Upload vertex/index data into a single contiguous GPU buffer.
var vertexData = default(D3D11_MAPPED_SUBRESOURCE);
var indexData = default(D3D11_MAPPED_SUBRESOURCE);
this.context.Get()->Map(
@ -306,26 +311,18 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
targetVertices = targetVertices[vertices.Length..];
targetIndices = targetIndices[indices.Length..];
}
}
finally
{
this.context.Get()->Unmap((ID3D11Resource*)this.vertexBuffer.Get(), 0);
this.context.Get()->Unmap((ID3D11Resource*)this.indexBuffer.Get(), 0);
}
// Setup orthographic projection matrix into our constant buffer.
// Our visible imgui space lies from DisplayPos (LT) to DisplayPos+DisplaySize (RB).
// DisplayPos is (0,0) for single viewport apps.
try
{
var data = default(D3D11_MAPPED_SUBRESOURCE);
var constantBufferData = default(D3D11_MAPPED_SUBRESOURCE);
this.context.Get()->Map(
(ID3D11Resource*)this.vertexConstantBuffer.Get(),
0,
D3D11_MAP.D3D11_MAP_WRITE_DISCARD,
0,
&data).ThrowOnError();
*(Matrix4x4*)data.pData = Matrix4x4.CreateOrthographicOffCenter(
&constantBufferData).ThrowOnError();
*(Matrix4x4*)constantBufferData.pData = Matrix4x4.CreateOrthographicOffCenter(
drawData.DisplayPos.X,
drawData.DisplayPos.X + drawData.DisplaySize.X,
drawData.DisplayPos.Y + drawData.DisplaySize.Y,
@ -335,6 +332,8 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
}
finally
{
this.context.Get()->Unmap((ID3D11Resource*)this.vertexBuffer.Get(), 0);
this.context.Get()->Unmap((ID3D11Resource*)this.indexBuffer.Get(), 0);
this.context.Get()->Unmap((ID3D11Resource*)this.vertexConstantBuffer.Get(), 0);
}
@ -343,8 +342,6 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
var vertexOffset = 0;
var indexOffset = 0;
var clipOff = new Vector4(drawData.DisplayPos, drawData.DisplayPos.X, drawData.DisplayPos.Y);
this.context.Get()->PSSetShader(this.pixelShader, null, 0);
this.context.Get()->PSSetSamplers(0, 1, this.sampler.GetAddressOf());
foreach (ref var cmdList in cmdLists)
{
var cmds = new ImVectorWrapper<ImDrawCmd>(cmdList.Handle->CmdBuffer.ToUntyped());
@ -467,7 +464,8 @@ internal unsafe partial class Dx11Renderer : IImGuiRenderer
buffer = this.vertexConstantBuffer.Get();
ctx->VSSetConstantBuffers(0, 1, &buffer);
// PS handled later
ctx->PSSetShader(this.pixelShader, null, 0);
ctx->PSSetSamplers(0, 1, this.sampler.GetAddressOf());
ctx->GSSetShader(null, null, 0);
ctx->HSSetShader(null, null, 0);

View file

@ -2,11 +2,9 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Unicode;
@ -46,24 +44,24 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
.ToDictionary(x => x.Item1, x => x.Name);
private static readonly UnicodeRange[] HanRange =
{
[
UnicodeRanges.CjkRadicalsSupplement,
UnicodeRanges.CjkSymbolsandPunctuation,
UnicodeRanges.CjkUnifiedIdeographsExtensionA,
UnicodeRanges.CjkUnifiedIdeographs,
UnicodeRanges.CjkCompatibilityIdeographs,
UnicodeRanges.CjkCompatibilityForms,
UnicodeRanges.CjkCompatibilityForms
// No more; Extension B~ are outside BMP range
};
];
private static readonly UnicodeRange[] HangulRange =
{
[
UnicodeRanges.HangulJamo,
UnicodeRanges.HangulSyllables,
UnicodeRanges.HangulCompatibilityJamo,
UnicodeRanges.HangulJamoExtendedA,
UnicodeRanges.HangulJamoExtendedB,
};
UnicodeRanges.HangulJamoExtendedB
];
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration dalamudConfiguration = Service<DalamudConfiguration>.Get();
@ -109,24 +107,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
private bool updateInputLanguage = true;
private bool updateImeStatusAgain;
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1003:Symbols should be spaced correctly", Justification = ".")]
static DalamudIme()
{
nint cimgui;
try
{
_ = ImGui.GetCurrentContext();
cimgui = Process.GetCurrentProcess().Modules.Cast<ProcessModule>()
.First(x => x.ModuleName == "cimgui.dll")
.BaseAddress;
}
catch
{
return;
}
}
[ServiceManager.ServiceConstructor]
private DalamudIme(InterfaceManager.InterfaceManagerWithScene imws)
{
@ -170,11 +150,11 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
if (!ImGui.GetIO().ConfigInputTextCursorBlink)
return true;
var textState = GetInputTextState();
if (textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0)
if (textState.ID == 0 || (textState.Flags & ImGuiInputTextFlags.ReadOnly) != 0)
return true;
if (textState->CursorAnim <= 0)
if (textState.CursorAnim <= 0)
return true;
return textState->CursorAnim % 1.2f <= 0.8f;
return textState.CursorAnim % 1.2f <= 0.8f;
}
}
@ -227,11 +207,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
}
}
private static ImGuiInputTextStateWrapper* GetInputTextState()
{
var ctx = ImGui.GetCurrentContext();
return (ImGuiInputTextStateWrapper*)&ctx.Handle->InputTextState;
}
private static ImGuiInputTextStatePtr GetInputTextState() => new(&ImGui.GetCurrentContext().Handle->InputTextState);
private static (string String, bool Supported) ToUcs2(char* data, int nc = -1)
{
@ -332,7 +308,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
try
{
var textState = GetInputTextState();
var invalidTarget = textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0;
var invalidTarget = textState.ID == 0 || (textState.Flags & ImGuiInputTextFlags.ReadOnly) != 0;
#if IMEDEBUG
switch (args.Message)
@ -564,17 +540,17 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
var textState = GetInputTextState();
if (this.temporaryUndoSelection is not null)
{
textState->Undo();
textState->SelectionTuple = this.temporaryUndoSelection.Value;
textState.Undo();
textState.SetSelectionTuple(this.temporaryUndoSelection.Value);
this.temporaryUndoSelection = null;
}
textState->SanitizeSelectionRange();
if (textState->ReplaceSelectionAndPushUndo(newString))
this.temporaryUndoSelection = textState->SelectionTuple;
textState.SanitizeSelectionRange();
if (textState.ReplaceSelectionAndPushUndo(newString))
this.temporaryUndoSelection = textState.GetSelectionTuple();
// Put the cursor at the beginning, so that the candidate window appears aligned with the text.
textState->SetSelectionRange(textState->SelectionTuple.Start, newString.Length, 0);
textState.SetSelectionRange(textState.GetSelectionTuple().Start, newString.Length, 0);
if (finalCommit)
{
@ -621,7 +597,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
this.temporaryUndoSelection = null;
var textState = GetInputTextState();
textState->Stb.SelectStart = textState->Stb.Cursor = textState->Stb.SelectEnd;
textState.Stb.SelectStart = textState.Stb.Cursor = textState.Stb.SelectEnd;
this.candidateStrings.Clear();
this.immCandNative = default;
@ -931,185 +907,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
}
}
/// <summary>
/// Ported from imstb_textedit.h.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0xE2C)]
private struct StbTextEditState
{
/// <summary>
/// Position of the text cursor within the string.
/// </summary>
public int Cursor;
/// <summary>
/// Selection start point.
/// </summary>
public int SelectStart;
/// <summary>
/// selection start and end point in characters; if equal, no selection.
/// </summary>
/// <remarks>
/// Note that start may be less than or greater than end (e.g. when dragging the mouse,
/// start is where the initial click was, and you can drag in either direction.)
/// </remarks>
public int SelectEnd;
/// <summary>
/// Each text field keeps its own insert mode state.
/// To keep an app-wide insert mode, copy this value in/out of the app state.
/// </summary>
public byte InsertMode;
/// <summary>
/// Page size in number of row.
/// This value MUST be set to >0 for pageup or pagedown in multilines documents.
/// </summary>
public int RowCountPerPage;
// Remainder is stb-private data.
}
[StructLayout(LayoutKind.Sequential)]
private struct ImGuiInputTextStateWrapper
{
public uint Id;
public int CurLenW;
public int CurLenA;
public ImVector<char> TextWRaw;
public ImVector<byte> TextARaw;
public ImVector<byte> InitialTextARaw;
public bool TextAIsValid;
public int BufCapacityA;
public float ScrollX;
public StbTextEditState Stb;
public float CursorAnim;
public bool CursorFollow;
public bool SelectedAllMouseLock;
public bool Edited;
public ImGuiInputTextFlags Flags;
public ImVectorWrapper<char> TextW => new((ImVector*)&this.ThisWrapperPtr->TextWRaw);
public (int Start, int End, int Cursor) SelectionTuple
{
get => (this.Stb.SelectStart, this.Stb.SelectEnd, this.Stb.Cursor);
set => (this.Stb.SelectStart, this.Stb.SelectEnd, this.Stb.Cursor) = value;
}
private ImGuiInputTextStateWrapper* ThisWrapperPtr => (ImGuiInputTextStateWrapper*)Unsafe.AsPointer(ref this);
private ImGuiInputTextState* ThisPtr => (ImGuiInputTextState*)Unsafe.AsPointer(ref this);
public void SetSelectionRange(int offset, int length, int relativeCursorOffset)
{
this.Stb.SelectStart = offset;
this.Stb.SelectEnd = offset + length;
if (relativeCursorOffset >= 0)
this.Stb.Cursor = this.Stb.SelectStart + relativeCursorOffset;
else
this.Stb.Cursor = this.Stb.SelectEnd + 1 + relativeCursorOffset;
this.SanitizeSelectionRange();
}
public void SanitizeSelectionRange()
{
ref var s = ref this.Stb.SelectStart;
ref var e = ref this.Stb.SelectEnd;
ref var c = ref this.Stb.Cursor;
s = Math.Clamp(s, 0, this.CurLenW);
e = Math.Clamp(e, 0, this.CurLenW);
c = Math.Clamp(c, 0, this.CurLenW);
if (s == e)
s = e = c;
if (s > e)
(s, e) = (e, s);
}
public void Undo() => ImGuiP.Custom_StbTextUndo(this.ThisPtr);
public bool MakeUndoReplace(int offset, int oldLength, int newLength)
{
if (oldLength == 0 && newLength == 0)
return false;
ImGuiP.Custom_StbTextMakeUndoReplace(this.ThisPtr, offset, oldLength, newLength);
return true;
}
public bool ReplaceSelectionAndPushUndo(ReadOnlySpan<char> newText)
{
var off = this.Stb.SelectStart;
var len = this.Stb.SelectEnd - this.Stb.SelectStart;
return this.MakeUndoReplace(off, len, newText.Length) && this.ReplaceChars(off, len, newText);
}
public bool ReplaceChars(int pos, int len, ReadOnlySpan<char> newText)
{
this.DeleteChars(pos, len);
return this.InsertChars(pos, newText);
}
// See imgui_widgets.cpp: STB_TEXTEDIT_DELETECHARS
public void DeleteChars(int pos, int n)
{
if (n == 0)
return;
var dst = this.TextW.Data + pos;
// We maintain our buffer length in both UTF-8 and wchar formats
this.Edited = true;
this.CurLenA -= Encoding.UTF8.GetByteCount(dst, n);
this.CurLenW -= n;
// Offset remaining text (FIXME-OPT: Use memmove)
var src = this.TextW.Data + pos + n;
int i;
for (i = 0; src[i] != 0; i++)
dst[i] = src[i];
dst[i] = '\0';
}
// See imgui_widgets.cpp: STB_TEXTEDIT_INSERTCHARS
public bool InsertChars(int pos, ReadOnlySpan<char> newText)
{
if (newText.Length == 0)
return true;
var isResizable = (this.Flags & ImGuiInputTextFlags.CallbackResize) != 0;
var textLen = this.CurLenW;
Debug.Assert(pos <= textLen, "pos <= text_len");
var newTextLenUtf8 = Encoding.UTF8.GetByteCount(newText);
if (!isResizable && newTextLenUtf8 + this.CurLenA + 1 > this.BufCapacityA)
return false;
// Grow internal buffer if needed
if (newText.Length + textLen + 1 > this.TextW.Length)
{
if (!isResizable)
return false;
Debug.Assert(textLen < this.TextW.Length, "text_len < this.TextW.Length");
this.TextW.Resize(textLen + Math.Clamp(newText.Length * 4, 32, Math.Max(256, newText.Length)) + 1);
}
var text = this.TextW.DataSpan;
if (pos != textLen)
text.Slice(pos, textLen - pos).CopyTo(text[(pos + newText.Length)..]);
newText.CopyTo(text[pos..]);
this.Edited = true;
this.CurLenW += newText.Length;
this.CurLenA += newTextLenUtf8;
this.TextW[this.CurLenW] = '\0';
return true;
}
}
#if IMEDEBUG
private static class ImeDebug
{

View file

@ -575,16 +575,6 @@ internal class DalamudInterface : IInternalDisposableService
if (this.isCreditsDarkening)
this.DrawCreditsDarkeningAnimation();
// Release focus of any ImGui window if we click into the game.
var io = ImGui.GetIO();
if (!io.WantCaptureMouse && (global::Windows.Win32.PInvoke.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0)
{
unsafe
{
ImGui.SetWindowFocus((byte*)null);
}
}
}
catch (Exception ex)
{
@ -856,9 +846,9 @@ internal class DalamudInterface : IInternalDisposableService
this.OpenBranchSwitcher();
}
ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false);
ImGui.MenuItem($"D: {Util.GetScmVersion()} CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false);
ImGui.MenuItem($"CLR: {Environment.Version}", false);
ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false, false);
ImGui.MenuItem($"D: {Util.GetScmVersion()} CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false, false);
ImGui.MenuItem($"CLR: {Environment.Version}", false, false);
ImGui.EndMenu();
}
@ -1021,8 +1011,8 @@ internal class DalamudInterface : IInternalDisposableService
}
ImGui.Separator();
ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false);
ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count(), false);
ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false, false);
ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count(), false, false);
ImGui.EndMenu();
}

View file

@ -0,0 +1,123 @@
using System.Diagnostics;
using System.Text;
using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Internal;
#pragma warning disable SA1600
internal static unsafe class ImGuiInputTextStatePtrExtensions
{
public static (int Start, int End, int Cursor) GetSelectionTuple(this ImGuiInputTextStatePtr self) =>
(self.Stb.SelectStart, self.Stb.SelectEnd, self.Stb.Cursor);
public static void SetSelectionTuple(this ImGuiInputTextStatePtr self, (int Start, int End, int Cursor) value) =>
(self.Stb.SelectStart, self.Stb.SelectEnd, self.Stb.Cursor) = value;
public static void SetSelectionRange(this ImGuiInputTextStatePtr self, int offset, int length, int relativeCursorOffset)
{
self.Stb.SelectStart = offset;
self.Stb.SelectEnd = offset + length;
if (relativeCursorOffset >= 0)
self.Stb.Cursor = self.Stb.SelectStart + relativeCursorOffset;
else
self.Stb.Cursor = self.Stb.SelectEnd + 1 + relativeCursorOffset;
self.SanitizeSelectionRange();
}
public static void SanitizeSelectionRange(this ImGuiInputTextStatePtr self)
{
ref var s = ref self.Stb.SelectStart;
ref var e = ref self.Stb.SelectEnd;
ref var c = ref self.Stb.Cursor;
s = Math.Clamp(s, 0, self.CurLenW);
e = Math.Clamp(e, 0, self.CurLenW);
c = Math.Clamp(c, 0, self.CurLenW);
if (s == e)
s = e = c;
if (s > e)
(s, e) = (e, s);
}
public static void Undo(this ImGuiInputTextStatePtr self) => ImGuiP.Custom_StbTextUndo(self);
public static bool MakeUndoReplace(this ImGuiInputTextStatePtr self, int offset, int oldLength, int newLength)
{
if (oldLength == 0 && newLength == 0)
return false;
ImGuiP.Custom_StbTextMakeUndoReplace(self, offset, oldLength, newLength);
return true;
}
public static bool ReplaceSelectionAndPushUndo(this ImGuiInputTextStatePtr self, ReadOnlySpan<char> newText)
{
var off = self.Stb.SelectStart;
var len = self.Stb.SelectEnd - self.Stb.SelectStart;
return self.MakeUndoReplace(off, len, newText.Length) && self.ReplaceChars(off, len, newText);
}
public static bool ReplaceChars(this ImGuiInputTextStatePtr self, int pos, int len, ReadOnlySpan<char> newText)
{
self.DeleteChars(pos, len);
return self.InsertChars(pos, newText);
}
// See imgui_widgets.cpp: STB_TEXTEDIT_DELETECHARS
public static void DeleteChars(this ImGuiInputTextStatePtr self, int pos, int n)
{
if (n == 0)
return;
var dst = (char*)self.TextW.Data + pos;
// We maintain our buffer length in both UTF-8 and wchar formats
self.Edited = true;
self.CurLenA -= Encoding.UTF8.GetByteCount(dst, n);
self.CurLenW -= n;
// Offset remaining text (FIXME-OPT: Use memmove)
var src = (char*)self.TextW.Data + pos + n;
int i;
for (i = 0; src[i] != 0; i++)
dst[i] = src[i];
dst[i] = '\0';
}
// See imgui_widgets.cpp: STB_TEXTEDIT_INSERTCHARS
public static bool InsertChars(this ImGuiInputTextStatePtr self, int pos, ReadOnlySpan<char> newText)
{
if (newText.Length == 0)
return true;
var isResizable = (self.Flags & ImGuiInputTextFlags.CallbackResize) != 0;
var textLen = self.CurLenW;
Debug.Assert(pos <= textLen, "pos <= text_len");
var newTextLenUtf8 = Encoding.UTF8.GetByteCount(newText);
if (!isResizable && newTextLenUtf8 + self.CurLenA + 1 > self.BufCapacityA)
return false;
// Grow internal buffer if needed
if (newText.Length + textLen + 1 > self.TextW.Size)
{
if (!isResizable)
return false;
Debug.Assert(textLen < self.TextW.Size, "text_len < self.TextW.Length");
self.TextW.Resize(textLen + Math.Clamp(newText.Length * 4, 32, Math.Max(256, newText.Length)) + 1);
}
var text = new Span<char>(self.TextW.Data, self.TextW.Size);
if (pos != textLen)
text.Slice(pos, textLen - pos).CopyTo(text[(pos + newText.Length)..]);
newText.CopyTo(text[pos..]);
self.Edited = true;
self.CurLenW += newText.Length;
self.CurLenA += newTextLenUtf8;
self.TextW[self.CurLenW] = '\0';
return true;
}
}

View file

@ -18,6 +18,7 @@ using Dalamud.Hooking;
using Dalamud.Hooking.Internal;
using Dalamud.Hooking.WndProcHook;
using Dalamud.Interface.ImGuiBackend;
using Dalamud.Interface.ImGuiBackend.Delegates;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification.Internal;
using Dalamud.Interface.Internal.Asserts;
@ -128,7 +129,7 @@ internal partial class InterfaceManager : IInternalDisposableService
/// <summary>
/// This event gets called each frame to facilitate ImGui drawing.
/// </summary>
public event IImGuiBackend.BuildUiDelegate? Draw;
public event ImGuiBuildUiDelegate? Draw;
/// <summary>
/// This event gets called when ResizeBuffers is called.
@ -634,29 +635,6 @@ internal partial class InterfaceManager : IInternalDisposableService
Service<InterfaceManagerWithScene>.ProvideException(ex);
Log.Error(ex, "Could not load ImGui dependencies.");
fixed (void* lpText =
"Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?")
{
fixed (void* lpCaption = "Dalamud Error")
{
var res = MessageBoxW(
default,
(ushort*)lpText,
(ushort*)lpCaption,
MB.MB_YESNO | MB.MB_TOPMOST | MB.MB_ICONERROR);
if (res == IDYES)
{
var psi = new ProcessStartInfo
{
FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe",
UseShellExecute = true,
};
Process.Start(psi);
}
}
}
Environment.Exit(-1);
// Doesn't reach here, but to make the compiler not complain

View file

@ -243,8 +243,6 @@ internal unsafe class UiDebug
ImGui.Text($"BGColor: #{textNode->BackgroundColor.R:X2}{textNode->BackgroundColor.G:X2}{textNode->BackgroundColor.B:X2}{textNode->BackgroundColor.A:X2}");
ImGui.Text($"TextFlags: {textNode->TextFlags}");
ImGui.SameLine();
ImGui.Text($"TextFlags2: {textNode->TextFlags2}");
break;
case NodeType.Counter:

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@ -117,9 +118,11 @@ public class AddonLifecycleWidget : IDataWindowWidget
{
ImGui.Columns(2);
var functionAddress = receiveEventListener.FunctionAddress;
ImGui.Text("Hook Address"u8);
ImGui.NextColumn();
ImGui.Text(receiveEventListener.FunctionAddress.ToString("X"));
ImGui.Text($"0x{functionAddress:X} (ffxiv_dx11.exe+{functionAddress - Process.GetCurrentProcess().MainModule!.BaseAddress:X})");
ImGui.NextColumn();
ImGui.Text("Hook Status"u8);

View file

@ -27,7 +27,7 @@ internal class UldWidget : IDataWindowWidget
{
// ULD styles can be hardcoded for now as they don't add new ones regularly. Can later try and find where to load these from in the game EXE.
private static readonly string[] ThemeDisplayNames = ["Dark", "Light", "Classic FF", "Clear Blue"];
private static readonly string[] ThemeBasePaths = ["ui/uld/", "ui/uld/light/", "ui/uld/third/", "ui/uld/fourth/"];
private static readonly string[] ThemeBasePaths = ["ui/uld/", "ui/uld/img01/", "ui/uld/img02/", "ui/uld/img03/"];
// 48 8D 15 ?? ?? ?? ?? is the part of the signatures that contain the string location offset
// 48 = 64 bit register prefix

View file

@ -2076,10 +2076,18 @@ internal class PluginInstallerWindow : Window, IDisposable
var isOpen = this.openPluginCollapsibles.Contains(index);
var sectionSize = ImGuiHelpers.GlobalScale * 66;
var tapeCursor = ImGui.GetCursorPos();
ImGui.Separator();
var childId = $"plugin_child_{label}_{plugin?.EffectiveWorkingPluginId}_{manifest.InternalName}";
const ImGuiWindowFlags childFlags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse;
using var pluginChild = ImRaii.Child(childId, new Vector2(ImGui.GetContentRegionAvail().X, sectionSize), false, childFlags);
if (!pluginChild)
{
return isOpen;
}
var startCursor = ImGui.GetCursorPos();
if (flags.HasFlag(PluginHeaderFlags.IsTesting))
@ -2115,7 +2123,7 @@ internal class PluginInstallerWindow : Window, IDisposable
}
}
DrawCautionTape(tapeCursor + new Vector2(0, 1), new Vector2(ImGui.GetWindowWidth(), sectionSize + ImGui.GetStyle().ItemSpacing.Y), ImGuiHelpers.GlobalScale * 40, 20);
DrawCautionTape(startCursor + new Vector2(0, 1), new Vector2(ImGui.GetWindowWidth(), sectionSize + ImGui.GetStyle().ItemSpacing.Y), ImGuiHelpers.GlobalScale * 40, 20);
}
ImGui.PushStyleColor(ImGuiCol.Button, isOpen ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero);
@ -2124,7 +2132,7 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f));
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
ImGui.SetCursorPos(tapeCursor);
ImGui.SetCursorPos(startCursor);
if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetContentRegionAvail().X, sectionSize + ImGui.GetStyle().ItemSpacing.Y)))
{

View file

@ -477,7 +477,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
var textNode = addon->GetTextNodeById(3);
// look and feel init. should be harmless to set.
textNode->TextFlags |= (byte)TextFlags.MultiLine;
textNode->TextFlags |= TextFlags.MultiLine;
textNode->AlignmentType = AlignmentType.TopLeft;
var containsDalamudVersionString = textNode->OriginalTextPointer.Value == textNode->NodeText.StringPtr.Value;

View file

@ -122,6 +122,34 @@ public interface IUiBuilder
/// </summary>
IFontSpec DefaultFontSpec { get; }
/// <summary>
/// Gets the default Dalamud font size in points.
/// </summary>
public float FontDefaultSizePt { get; }
/// <summary>
/// Gets the default Dalamud font size in pixels.
/// </summary>
public float FontDefaultSizePx { get; }
/// <summary>
/// Gets the default Dalamud font - supporting all game languages and icons.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// </summary>
public ImFontPtr FontDefault { get; }
/// <summary>
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// </summary>
public ImFontPtr FontIcon { get; }
/// <summary>
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
/// </summary>
public ImFontPtr FontMono { get; }
/// <summary>
/// Gets the game's active Direct3D device.
/// </summary>
@ -380,6 +408,21 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
/// </summary>
public IFontSpec DefaultFontSpec => Service<FontAtlasFactory>.Get().DefaultFontSpec;
/// <inheritdoc/>
public float FontDefaultSizePt => Service<FontAtlasFactory>.Get().DefaultFontSpec.SizePt;
/// <inheritdoc/>
public float FontDefaultSizePx => Service<FontAtlasFactory>.Get().DefaultFontSpec.SizePx;
/// <inheritdoc/>
public ImFontPtr FontDefault => InterfaceManager.DefaultFont;
/// <inheritdoc/>
public ImFontPtr FontIcon => InterfaceManager.IconFont;
/// <inheritdoc/>
public ImFontPtr FontMono => InterfaceManager.MonoFont;
/// <summary>
/// Gets the handle to the default Dalamud font - supporting all game languages and icons.
/// </summary>
@ -541,15 +584,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
internal static bool DoStats { get; set; } = false;
#endif
private void OnDefaultStyleChanged()
=> this.DefaultStyleChanged.InvokeSafely();
private void OnDefaultGlobalScaleChanged()
=> this.DefaultGlobalScaleChanged.InvokeSafely();
private void OnDefaultFontChanged()
=> this.DefaultFontChanged.InvokeSafely();
/// <summary>
/// Gets a value indicating whether this UiBuilder has a configuration UI registered.
/// </summary>
@ -796,6 +830,15 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
this.ResizeBuffers?.InvokeSafely();
}
private void OnDefaultStyleChanged()
=> this.DefaultStyleChanged.InvokeSafely();
private void OnDefaultGlobalScaleChanged()
=> this.DefaultGlobalScaleChanged.InvokeSafely();
private void OnDefaultFontChanged()
=> this.DefaultFontChanged.InvokeSafely();
private class FontHandleWrapper : IFontHandle
{
private IFontHandle? wrapped;

View file

@ -203,8 +203,8 @@ public static partial class ImGuiHelpers
ImGui.SetClipboardText(textCopy.IsNull ? text.Span : textCopy.Span);
}
text.Dispose();
textCopy.Dispose();
text.Recycle();
textCopy.Recycle();
}
/// <summary>Draws a SeString.</summary>

View file

@ -2,10 +2,8 @@ using System.Collections.Generic;
namespace Dalamud.Plugin;
/// <summary>
/// Contains data about changes to the list of active plugins.
/// </summary>
public class ActivePluginsChangedEventArgs : EventArgs
/// <inheritdoc cref="IActivePluginsChangedEventArgs" />
public class ActivePluginsChangedEventArgs : EventArgs, IActivePluginsChangedEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivePluginsChangedEventArgs"/> class
@ -19,13 +17,9 @@ public class ActivePluginsChangedEventArgs : EventArgs
this.AffectedInternalNames = affectedInternalNames;
}
/// <summary>
/// Gets the invalidation kind that caused this event to be fired.
/// </summary>
/// <inheritdoc/>
public PluginListInvalidationKind Kind { get; }
/// <summary>
/// Gets the InternalNames of affected plugins.
/// </summary>
/// <inheritdoc/>
public IEnumerable<string> AffectedInternalNames { get; }
}

View file

@ -489,7 +489,7 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
/// Dispatch the active plugins changed event.
/// </summary>
/// <param name="args">The event arguments containing information about the change.</param>
internal void NotifyActivePluginsChanged(ActivePluginsChangedEventArgs args)
internal void NotifyActivePluginsChanged(IActivePluginsChangedEventArgs args)
{
foreach (var action in Delegate.EnumerateInvocationList(this.ActivePluginsChanged))
{

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Dalamud.Plugin;
/// <summary>
/// Contains data about changes to the list of active plugins.
/// </summary>
public interface IActivePluginsChangedEventArgs
{
/// <summary>
/// Gets the invalidation kind that caused this event to be fired.
/// </summary>
PluginListInvalidationKind Kind { get; }
/// <summary>
/// Gets the InternalNames of affected plugins.
/// </summary>
IEnumerable<string> AffectedInternalNames { get; }
}

View file

@ -33,7 +33,7 @@ public interface IDalamudPluginInterface
/// Delegate for events that listen to changes to the list of active plugins.
/// </summary>
/// <param name="args">The event arguments containing information about the change.</param>
public delegate void ActivePluginsChangedDelegate(ActivePluginsChangedEventArgs args);
public delegate void ActivePluginsChangedDelegate(IActivePluginsChangedEventArgs args);
/// <summary>
/// Event that gets fired when loc is changed

View file

@ -1240,7 +1240,7 @@ internal class PluginManager : IInternalDisposableService
}
return this.bannedPlugins.Any(ban => (ban.Name == manifest.InternalName || ban.Name == Hash.GetStringSha256Hash(manifest.InternalName))
&& ban.AssemblyVersion >= versionToCheck);
&& (ban.AssemblyVersion == null || ban.AssemblyVersion >= versionToCheck));
}
/// <summary>

View file

@ -17,7 +17,7 @@ internal struct BannedPlugin
/// Gets plugin assembly version.
/// </summary>
[JsonProperty]
public Version AssemblyVersion { get; private set; }
public Version? AssemblyVersion { get; private set; }
/// <summary>
/// Gets reason for the ban.

View file

@ -83,20 +83,21 @@ public interface IChatGui
/// <summary>
/// Gets the dictionary of Dalamud Link Handlers.
/// </summary>
public IReadOnlyDictionary<(string PluginName, Guid CommandId), Action<Guid, SeString>> RegisteredLinkHandlers { get; }
public IReadOnlyDictionary<(string PluginName, uint CommandId), Action<uint, SeString>> RegisteredLinkHandlers { get; }
/// <summary>
/// Register a chat link handler.
/// </summary>
/// <param name="commandId">The ID of the command.</param>
/// <param name="commandAction">The action to be executed.</param>
/// <returns>Returns an SeString payload for the link.</returns>
public DalamudLinkPayload AddChatLinkHandler(Action<Guid, SeString> commandAction);
public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action<uint, SeString> commandAction);
/// <summary>
/// Remove a chat link handler.
/// </summary>
/// <param name="commandId">The ID of the command.</param>
public void RemoveChatLinkHandler(Guid commandId);
public void RemoveChatLinkHandler(uint commandId);
/// <summary>
/// Removes all chat link handlers registered by the plugin.

View file

@ -38,6 +38,15 @@ public interface ISeStringEvaluator
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
ReadOnlySeString EvaluateMacroString(string macroString, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
/// <summary>
/// Evaluates macros in a macro string.
/// </summary>
/// <param name="macroString">The macro string.</param>
/// <param name="localParameters">An optional list of local parameters.</param>
/// <param name="language">An optional language override.</param>
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
ReadOnlySeString EvaluateMacroString(ReadOnlySpan<byte> macroString, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
/// <summary>
/// Evaluates macros in text from the Addon sheet.
/// </summary>

View file

@ -36,6 +36,7 @@ internal static class BugBait
Reporter = reporter,
Name = plugin.InternalName,
Version = isTesting ? plugin.TestingAssemblyVersion?.ToString() : plugin.AssemblyVersion.ToString(),
Platform = Util.GetHostPlatform().ToString(),
DalamudHash = Util.GetScmVersion(),
};
@ -66,6 +67,9 @@ internal static class BugBait
[JsonProperty("version")]
public string? Version { get; set; }
[JsonProperty("platform")]
public string? Platform { get; set; }
[JsonProperty("reporter")]
public string? Reporter { get; set; }

View file

@ -9,6 +9,7 @@ using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Bindings.ImGui;
@ -27,6 +28,8 @@ using Windows.Win32.System.Memory;
using Windows.Win32.System.Ole;
using Windows.Win32.UI.WindowsAndMessaging;
using Dalamud.Interface.Internal;
using FLASHWINFO = Windows.Win32.UI.WindowsAndMessaging.FLASHWINFO;
using HWND = Windows.Win32.Foundation.HWND;
using MEMORY_BASIC_INFORMATION = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION;
@ -522,17 +525,36 @@ public static partial class Util
public static bool IsWindows11() => Environment.OSVersion.Version.Build >= 22000;
/// <summary>
/// Open a link in the default browser.
/// Open a link in the default browser, and attempts to focus the newly launched application.
/// </summary>
/// <param name="url">The link to open.</param>
public static void OpenLink(string url)
public static void OpenLink(string url) => new Thread(
static url =>
{
var process = new ProcessStartInfo(url)
try
{
var psi = new ProcessStartInfo((string)url!)
{
UseShellExecute = true,
ErrorDialogParentHandle = Service<InterfaceManager>.GetNullable() is { } im
? im.GameWindowHandle
: 0,
Verb = "open",
};
Process.Start(process);
if (Process.Start(psi) is not { } process)
return;
if (process.Id != 0)
TerraFX.Interop.Windows.Windows.AllowSetForegroundWindow((uint)process.Id);
process.WaitForInputIdle();
TerraFX.Interop.Windows.Windows.SetForegroundWindow(
(TerraFX.Interop.Windows.HWND)process.MainWindowHandle);
}
catch (Exception e)
{
Log.Error(e, "{fn}: failed to open {url}", nameof(OpenLink), url);
}
}).Start(url);
/// <summary>
/// Perform a "zipper merge" (A, 1, B, 2, C, 3) of multiple enumerables, allowing for lists to end early.

View file

@ -463,12 +463,14 @@ void open_folder_and_select_items(HWND hwndOpener, const std::wstring& path) {
void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const std::string& crashLog, const std::string& troubleshootingPackData) {
static const char* SourceLogFiles[] = {
"output.log",
"output.log", // XIVLauncher for Windows
"launcher.log", // XIVLauncher.Core for [mostly] Linux
"patcher.log",
"dalamud.log",
"dalamud.injector.log",
"dalamud.boot.log",
"aria.log",
"wine.log"
};
static constexpr auto MaxSizePerLog = 1 * 1024 * 1024;
static constexpr std::array<COMDLG_FILTERSPEC, 2> OutputFileTypeFilterSpec{{

View file

@ -10,7 +10,7 @@
<!-- Dependency versions -->
<PropertyGroup Label="Dependency Versions">
<LuminaVersion>6.3.0</LuminaVersion>
<LuminaVersion>6.5.1</LuminaVersion>
<NewtonsoftJsonVersion>13.0.3</NewtonsoftJsonVersion>
</PropertyGroup>

View file

@ -21,7 +21,7 @@ $sourcePaths = (
# replace "ImGuiKey.GamepadStart"
$tmp = Get-Content -Path "$PSScriptRoot\imgui\Dalamud.Bindings.ImGui\Generated\Enums\ImGuiKeyPrivate.cs" -Raw
$tmp = $tmp.Replace("unchecked((int)GamepadStart)", "unchecked((int)ImGuiKey.GamepadStart)").Trim()
$tmp | Set-Content -Path "$PSScriptRoot\imgui\Dalamud.Bindings.ImGui\Generated\Enums\ImGuiKeyPrivate.cs" -Encoding ascii
$tmp.Trim() | Set-Content -Path "$PSScriptRoot\imgui\Dalamud.Bindings.ImGui\Generated\Enums\ImGuiKeyPrivate.cs" -Encoding ascii
try
{
@ -141,7 +141,9 @@ foreach ($sourcePath in $sourcePaths)
$husks = $husks.Replace("public unsafe struct", "public unsafe partial struct")
$husks = $referNativeFunctionQualified.Replace($husks, '$1Native.$2')
$husks = "// <auto-generated/>`r`n`r`nusing $([string]::Join(";`r`nusing ", $imports) );`r`n`r`n$husks"
$husks | Set-Content -Path "$targetPath.gen.cs" -Encoding ascii
$husks = $husks -ireplace 'nuint (ActiveIdUsingKeyInputMask)', 'ImBitArrayImGuiKeyNamedKeyCOUNTLessImGuiKeyNamedKeyBEGIN $1'
$husks = $husks.Replace('ref Unsafe.AsRef<nuint>(&Handle->ActiveIdUsingKeyInputMask)', 'ref Unsafe.AsRef<ImBitArrayImGuiKeyNamedKeyCOUNTLessImGuiKeyNamedKeyBEGIN>(&Handle->ActiveIdUsingKeyInputMask)')
$husks.Trim() | Set-Content -Path "$targetPath.gen.cs" -Encoding ascii
}
$husks = "// <auto-generated/>`r`n`r`nusing $([string]::Join(";`r`nusing ", $imports) );`r`n`r`nnamespace $namespace;`r`n`r`n"
@ -286,6 +288,6 @@ foreach ($sourcePath in $sourcePaths)
$null = $sb.Append("// DISCARDED: $methodName`r`n")
}
$sb.ToString() | Set-Content -Path "$targetPath/$className.gen.cs" -Encoding ascii
$sb.ToString().Trim() | Set-Content -Path "$targetPath/$className.gen.cs" -Encoding ascii
}
}

View file

@ -8896,4 +8896,3 @@ public unsafe partial class ImGui
// DISCARDED: internal static byte VSliderFloatNative(byte* label, Vector2 size, float* v, float vMin, float vMax, byte* format, ImGuiSliderFlags flags)
// DISCARDED: internal static byte VSliderIntNative(byte* label, Vector2 size, int* v, int vMin, int vMax, byte* format, ImGuiSliderFlags flags)
// DISCARDED: internal static byte VSliderScalarNative(byte* label, Vector2 size, ImGuiDataType dataType, void* pData, void* pMin, void* pMax, byte* format, ImGuiSliderFlags flags)

View file

@ -4828,4 +4828,3 @@ public unsafe partial class ImGuiNative
}
}

File diff suppressed because one or more lines are too long

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImBitArrayImGuiKeyNamedKeyCOUNTLessImGuiKeyNamedKeyBEGIN
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImBitVector
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImBitVectorPtr
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImChunkStreamImGuiTableSettings
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImChunkStreamImGuiWindowSettings
{
}

View file

@ -47,4 +47,3 @@ public unsafe partial struct ImColor
}
}
}

View file

@ -32,4 +32,3 @@ public unsafe partial struct ImColorPtr
ImGuiNative.SetHSV(Handle, h, s, v, (float)(1.0f));
}
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawChannel
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawChannelPtr
{
}

View file

@ -27,4 +27,3 @@ public unsafe partial struct ImDrawCmd
}
}
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawCmdHeader
{
}

View file

@ -21,4 +21,3 @@ public unsafe partial struct ImDrawCmdPtr
return ret;
}
}

View file

@ -40,4 +40,3 @@ public unsafe partial struct ImDrawData
}
}
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawDataBuilder
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawDataBuilderPtr
{
}

View file

@ -28,4 +28,3 @@ public unsafe partial struct ImDrawDataPtr
ImGuiNative.ScaleClipRects(Handle, fbScale);
}
}

View file

@ -756,4 +756,3 @@ public unsafe partial struct ImDrawList
}
}
// DISCARDED: AddText

View file

@ -441,4 +441,3 @@ public unsafe partial struct ImDrawListPtr
}
}
// DISCARDED: AddText

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawListPtrPtr
{
}

View file

@ -19,4 +19,3 @@ public unsafe partial struct ImDrawListSharedData
}
}
}

View file

@ -16,4 +16,3 @@ public unsafe partial struct ImDrawListSharedDataPtr
ImGuiNative.Destroy(Handle);
}
}

View file

@ -84,4 +84,3 @@ public unsafe partial struct ImDrawListSplitter
}
}
}

View file

@ -57,4 +57,3 @@ public unsafe partial struct ImDrawListSplitterPtr
}
}
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawVert
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImDrawVertPtr
{
}

View file

@ -174,4 +174,3 @@ public unsafe partial struct ImFont
// DISCARDED: GetDebugName
// DISCARDED: GetDebugNameS
// DISCARDED: RenderText

View file

@ -1829,4 +1829,3 @@ public unsafe partial struct ImFontAtlas
// DISCARDED: AddFontFromMemoryCompressedBase85TTF
// DISCARDED: AddFontFromMemoryCompressedTTF
// DISCARDED: AddFontFromMemoryTTF

View file

@ -27,4 +27,3 @@ public unsafe partial struct ImFontAtlasCustomRect
}
}
}

View file

@ -21,4 +21,3 @@ public unsafe partial struct ImFontAtlasCustomRectPtr
return ret != 0;
}
}

View file

@ -1409,4 +1409,3 @@ public unsafe partial struct ImFontAtlasPtr
// DISCARDED: AddFontFromMemoryCompressedBase85TTF
// DISCARDED: AddFontFromMemoryCompressedTTF
// DISCARDED: AddFontFromMemoryTTF

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImFontAtlasTexture
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImFontAtlasTexturePtr
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImFontBuilderIO
{
}

View file

@ -12,4 +12,3 @@ namespace Dalamud.Bindings.ImGui;
public unsafe partial struct ImFontBuilderIOPtr
{
}

View file

@ -19,4 +19,3 @@ public unsafe partial struct ImFontConfig
}
}
}

View file

@ -16,4 +16,3 @@ public unsafe partial struct ImFontConfigPtr
ImGuiNative.Destroy(Handle);
}
}

Some files were not shown because too many files have changed in this diff Show more