mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-16 21:54:16 +01:00
Compare commits
161 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46954e6add | ||
|
|
01901c237a | ||
|
|
cdf4e27355 | ||
|
|
a843079a6b | ||
|
|
ddd85513ba | ||
|
|
89fbe6c8b0 | ||
|
|
1c1b60efee | ||
|
|
2e7c48316f | ||
|
|
b0a0fafb53 | ||
|
|
8334836b6a | ||
|
|
8a742e7e59 | ||
|
|
56325afa7f | ||
|
|
1bff6abae9 | ||
|
|
d7935d6dd4 | ||
|
|
a715725a9d | ||
|
|
bc8e986c11 | ||
|
|
ffd99d5791 | ||
|
|
20af5b40c7 | ||
|
|
a1409096fd | ||
|
|
fecba89710 | ||
|
|
b57b96b9a0 | ||
|
|
180676fe47 | ||
|
|
2d096d9b33 | ||
|
|
e100ec2abd | ||
|
|
71b0a757e9 | ||
|
|
0b55dc3e10 | ||
|
|
4d9751ea5f | ||
|
|
a39763f161 | ||
|
|
201c9cfcf2 | ||
|
|
e07bda7e58 | ||
|
|
b88a6bb616 | ||
|
|
e53ccdbcc0 | ||
|
|
97df73acea | ||
|
|
2806e59dba | ||
|
|
24caa1cb18 | ||
|
|
5d08170333 | ||
|
|
d0110f7251 | ||
|
|
2dbae05522 | ||
|
|
8ed1af30df | ||
|
|
e8485dee25 | ||
|
|
0072f49fe8 | ||
|
|
c45c6aafe1 | ||
|
|
2029a0f8a6 | ||
|
|
bcb8094c2d | ||
|
|
624191d1e0 | ||
|
|
c254c8600e | ||
|
|
61376fe84e | ||
|
|
2f5f52b572 | ||
|
|
7199bfb0a9 | ||
|
|
abcddde591 | ||
|
|
2a99108eb1 | ||
|
|
8a5f1fd96d | ||
|
|
d4fe523d73 | ||
|
|
9e5723359a | ||
|
|
07f9e03010 | ||
|
|
9cfa81c92d | ||
|
|
9fd59f736d | ||
|
|
3d29157391 | ||
|
|
b2d9480f9f | ||
|
|
61123ce573 | ||
|
|
88fc933e3f | ||
|
|
1d1db04f04 | ||
|
|
45366efd9f | ||
|
|
a36e11574b | ||
|
|
d94cacaac3 | ||
|
|
7cf20fe102 | ||
|
|
98a4c0d4fd | ||
|
|
f85ef995e3 | ||
|
|
e7d4786a1f | ||
|
|
4d949e4a07 | ||
|
|
68ca60fa8c | ||
|
|
411067219e | ||
|
|
fc983458fa | ||
|
|
ddc3113244 | ||
|
|
da7be64fdf | ||
|
|
0112e17fdb | ||
|
|
6f8e33a39c | ||
|
|
0480693f92 | ||
|
|
5bb212bfaa | ||
|
|
a917ebd856 | ||
|
|
85a7c60dae | ||
|
|
c923884626 | ||
|
|
78781c8988 | ||
|
|
b81cb9c74c | ||
|
|
8e8d0246bc | ||
|
|
d47a41b295 | ||
|
|
c9276b1771 | ||
|
|
386828005b | ||
|
|
08c1768286 | ||
|
|
eb9555ee22 | ||
|
|
be3f71dc73 | ||
|
|
e01acb4a80 | ||
|
|
f8725e5f37 | ||
|
|
c3e3e4aa85 | ||
|
|
b82b4f40ce | ||
|
|
4f59e09513 | ||
|
|
0533872a73 | ||
|
|
27a7adfdb9 | ||
|
|
54bac7f32a | ||
|
|
26f119096b | ||
|
|
c51e65e0bd | ||
|
|
874745651b | ||
|
|
ead1c705a4 | ||
|
|
a31dda7865 | ||
|
|
d7e04ad4ff | ||
|
|
b8724f7a59 | ||
|
|
170f6e0859 | ||
|
|
325d28ee32 | ||
|
|
29c154f9b5 | ||
|
|
166f249e13 | ||
|
|
c525655be6 | ||
|
|
c661faea6b | ||
|
|
d4f1636dd2 | ||
|
|
196a5ef709 | ||
|
|
5e192ef39b | ||
|
|
947518b3d6 | ||
|
|
2cef75bbbe | ||
|
|
ab0500ca6f | ||
|
|
2c1bb76643 | ||
|
|
9a1fae8246 | ||
|
|
8ab7b59ae4 | ||
|
|
7b286c427c | ||
|
|
0d8f577576 | ||
|
|
01d8fc0c7e | ||
|
|
71927a8bf6 | ||
|
|
6a69a6e197 | ||
|
|
cc91916574 | ||
|
|
b7dda599fb | ||
|
|
63b7ecf0d7 | ||
|
|
e4eca842d3 | ||
|
|
c79fa96505 | ||
|
|
ba0cf4c990 | ||
|
|
9a49a9588b | ||
|
|
19a3926051 | ||
|
|
4937a2f4bd | ||
|
|
78ed4a2b01 | ||
|
|
62b9c1f2a1 | ||
|
|
a2e923b051 | ||
|
|
de396e70f8 | ||
|
|
7a8f01f418 | ||
|
|
9d0879148c | ||
|
|
778c82fad2 | ||
|
|
7f2ed9adb6 | ||
|
|
53b94caeb7 | ||
|
|
d1dc81318a | ||
|
|
a48eead85e | ||
|
|
d1bed3ebc5 | ||
|
|
23e7c164d8 | ||
|
|
8a9b47c7a4 | ||
|
|
520e3ea028 | ||
|
|
dd70c5b8ee | ||
|
|
2b2f628096 | ||
|
|
6340afb692 | ||
|
|
928fbba489 | ||
|
|
7bc921f543 | ||
|
|
a37a13e0ba | ||
|
|
e0eff2fe74 | ||
|
|
7d76d27555 | ||
|
|
4e87b4b007 | ||
|
|
8cced4c1d7 | ||
|
|
b18b8b40e5 |
163 changed files with 4084 additions and 2881 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
uses: microsoft/setup-msbuild@v1.0.2
|
uses: microsoft/setup-msbuild@v1.0.2
|
||||||
- uses: actions/setup-dotnet@v3
|
- uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.200'
|
dotnet-version: '10.0.100'
|
||||||
- name: Define VERSION
|
- name: Define VERSION
|
||||||
run: |
|
run: |
|
||||||
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,8 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"title": "Build Schema",
|
|
||||||
"$ref": "#/definitions/build",
|
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"build": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"Configuration": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
|
||||||
"enum": [
|
|
||||||
"Debug",
|
|
||||||
"Release"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Continue": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Indicates to continue a previously failed build attempt"
|
|
||||||
},
|
|
||||||
"Help": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Shows the help text for this build assembly"
|
|
||||||
},
|
|
||||||
"Host": {
|
"Host": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Host for execution. Default is 'automatic'",
|
|
||||||
"enum": [
|
"enum": [
|
||||||
"AppVeyor",
|
"AppVeyor",
|
||||||
"AzurePipelines",
|
"AzurePipelines",
|
||||||
|
|
@ -43,9 +21,48 @@
|
||||||
"VSCode"
|
"VSCode"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"IsDocsBuild": {
|
"ExecutableTarget": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"CI",
|
||||||
|
"Clean",
|
||||||
|
"Compile",
|
||||||
|
"CompileCImGui",
|
||||||
|
"CompileCImGuizmo",
|
||||||
|
"CompileCImPlot",
|
||||||
|
"CompileDalamud",
|
||||||
|
"CompileDalamudBoot",
|
||||||
|
"CompileDalamudCrashHandler",
|
||||||
|
"CompileImGuiNatives",
|
||||||
|
"CompileInjector",
|
||||||
|
"Restore",
|
||||||
|
"SetCILogging",
|
||||||
|
"Test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Verbosity": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "",
|
||||||
|
"enum": [
|
||||||
|
"Verbose",
|
||||||
|
"Normal",
|
||||||
|
"Minimal",
|
||||||
|
"Quiet"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NukeBuild": {
|
||||||
|
"properties": {
|
||||||
|
"Continue": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether we are building for documentation - emits generated files"
|
"description": "Indicates to continue a previously failed build attempt"
|
||||||
|
},
|
||||||
|
"Help": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Shows the help text for this build assembly"
|
||||||
|
},
|
||||||
|
"Host": {
|
||||||
|
"description": "Host for execution. Default is 'automatic'",
|
||||||
|
"$ref": "#/definitions/Host"
|
||||||
},
|
},
|
||||||
"NoLogo": {
|
"NoLogo": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
@ -74,65 +91,46 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"$ref": "#/definitions/ExecutableTarget"
|
||||||
"enum": [
|
|
||||||
"CI",
|
|
||||||
"Clean",
|
|
||||||
"Compile",
|
|
||||||
"CompileCImGui",
|
|
||||||
"CompileCImGuizmo",
|
|
||||||
"CompileCImPlot",
|
|
||||||
"CompileDalamud",
|
|
||||||
"CompileDalamudBoot",
|
|
||||||
"CompileDalamudCrashHandler",
|
|
||||||
"CompileImGuiNatives",
|
|
||||||
"CompileInjector",
|
|
||||||
"CompileInjectorBoot",
|
|
||||||
"Restore",
|
|
||||||
"SetCILogging",
|
|
||||||
"Test"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Solution": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to a solution file that is automatically loaded"
|
|
||||||
},
|
|
||||||
"Target": {
|
"Target": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "List of targets to be invoked. Default is '{default_target}'",
|
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"$ref": "#/definitions/ExecutableTarget"
|
||||||
"enum": [
|
|
||||||
"CI",
|
|
||||||
"Clean",
|
|
||||||
"Compile",
|
|
||||||
"CompileCImGui",
|
|
||||||
"CompileCImGuizmo",
|
|
||||||
"CompileCImPlot",
|
|
||||||
"CompileDalamud",
|
|
||||||
"CompileDalamudBoot",
|
|
||||||
"CompileDalamudCrashHandler",
|
|
||||||
"CompileImGuiNatives",
|
|
||||||
"CompileInjector",
|
|
||||||
"CompileInjectorBoot",
|
|
||||||
"Restore",
|
|
||||||
"SetCILogging",
|
|
||||||
"Test"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Verbosity": {
|
"Verbosity": {
|
||||||
"type": "string",
|
|
||||||
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||||
|
"$ref": "#/definitions/Verbosity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"Configuration": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Minimal",
|
"Debug",
|
||||||
"Normal",
|
"Release"
|
||||||
"Quiet",
|
]
|
||||||
"Verbose"
|
},
|
||||||
|
"IsDocsBuild": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether we are building for documentation - emits generated files"
|
||||||
|
},
|
||||||
|
"Solution": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to a solution file that is automatically loaded"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/NukeBuild"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -108,6 +108,11 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
||||||
config.LogName = json.value("LogName", config.LogName);
|
config.LogName = json.value("LogName", config.LogName);
|
||||||
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
|
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
|
||||||
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
|
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
|
||||||
|
|
||||||
|
if (json.contains("TempDirectory") && !json["TempDirectory"].is_null()) {
|
||||||
|
config.TempDirectory = json.value("TempDirectory", config.TempDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
config.Language = json.value("Language", config.Language);
|
config.Language = json.value("Language", config.Language);
|
||||||
config.Platform = json.value("Platform", config.Platform);
|
config.Platform = json.value("Platform", config.Platform);
|
||||||
config.GameVersion = json.value("GameVersion", config.GameVersion);
|
config.GameVersion = json.value("GameVersion", config.GameVersion);
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ struct DalamudStartInfo {
|
||||||
std::string ConfigurationPath;
|
std::string ConfigurationPath;
|
||||||
std::string LogPath;
|
std::string LogPath;
|
||||||
std::string LogName;
|
std::string LogName;
|
||||||
|
std::string TempDirectory;
|
||||||
std::string PluginDirectory;
|
std::string PluginDirectory;
|
||||||
std::string AssetDirectory;
|
std::string AssetDirectory;
|
||||||
ClientLanguage Language = ClientLanguage::English;
|
ClientLanguage Language = ClientLanguage::English;
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstrin
|
||||||
args.emplace_back(L"--logname=\"" + unicode::convert<std::wstring>(g_startInfo.LogName) + L"\"");
|
args.emplace_back(L"--logname=\"" + unicode::convert<std::wstring>(g_startInfo.LogName) + L"\"");
|
||||||
args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert<std::wstring>(g_startInfo.PluginDirectory) + L"\"");
|
args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert<std::wstring>(g_startInfo.PluginDirectory) + L"\"");
|
||||||
args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
|
args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
|
||||||
|
args.emplace_back(L"--dalamud-temp-directory=\"" + unicode::convert<std::wstring>(g_startInfo.TempDirectory) + L"\"");
|
||||||
args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast<int>(g_startInfo.Language)));
|
args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast<int>(g_startInfo.Language)));
|
||||||
args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs));
|
args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs));
|
||||||
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler
|
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,12 @@ public record DalamudStartInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ConfigurationPath { get; set; }
|
public string? ConfigurationPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the directory for temporary files. This directory needs to exist and be writable to the user.
|
||||||
|
/// It should also be predictable and easy for launchers to find.
|
||||||
|
/// </summary>
|
||||||
|
public string? TempDirectory { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the path of the log files.
|
/// Gets or sets the path of the log files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<PropertyGroup Label="Globals">
|
|
||||||
<ProjectGuid>{8874326B-E755-4D13-90B4-59AB263A3E6B}</ProjectGuid>
|
|
||||||
<RootNamespace>Dalamud_Injector_Boot</RootNamespace>
|
|
||||||
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
|
||||||
<ProjectConfiguration Include="Debug|x64">
|
|
||||||
<Configuration>Debug</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release|x64">
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup Label="Globals">
|
|
||||||
<VCProjectVersion>16.0</VCProjectVersion>
|
|
||||||
<Keyword>Win32Proj</Keyword>
|
|
||||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
|
||||||
<TargetName>Dalamud.Injector</TargetName>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
|
||||||
<PropertyGroup Label="Configuration">
|
|
||||||
<ConfigurationType>Application</ConfigurationType>
|
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v143</PlatformToolset>
|
|
||||||
<LinkIncremental>false</LinkIncremental>
|
|
||||||
<CharacterSet>Unicode</CharacterSet>
|
|
||||||
<OutDir>..\bin\$(Configuration)\</OutDir>
|
|
||||||
<IntDir>obj\$(Configuration)\</IntDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
|
||||||
<ItemDefinitionGroup>
|
|
||||||
<ClCompile>
|
|
||||||
<WarningLevel>Level3</WarningLevel>
|
|
||||||
<SDLCheck>true</SDLCheck>
|
|
||||||
<ConformanceMode>true</ConformanceMode>
|
|
||||||
<LanguageStandard>stdcpp23</LanguageStandard>
|
|
||||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
|
||||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
|
||||||
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<SubSystem>Console</SubSystem>
|
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
|
||||||
<EnableUAC>false</EnableUAC>
|
|
||||||
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
|
||||||
<ProgramDatabaseFile>$(OutDir)$(TargetName).Boot.pdb</ProgramDatabaseFile>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
|
||||||
<ClCompile>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
|
||||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
|
||||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>false</OptimizeReferences>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
|
||||||
<ClCompile>
|
|
||||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
|
||||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
|
||||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
|
||||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
|
||||||
</ClCompile>
|
|
||||||
<Link>
|
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
|
||||||
</Link>
|
|
||||||
</ItemDefinitionGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
|
|
||||||
<Link>nethost.dll</Link>
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Image Include="dalamud.ico" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ResourceCompile Include="resources.rc" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp" />
|
|
||||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp" />
|
|
||||||
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
|
|
||||||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
|
|
||||||
<ClCompile Include="main.cpp" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="..\Dalamud.Boot\logging.h" />
|
|
||||||
<ClInclude Include="..\Dalamud.Boot\unicode.h" />
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
|
|
||||||
<ClInclude Include="pch.h" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
|
||||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
|
||||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup>
|
|
||||||
<Filter Include="Source Files">
|
|
||||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
|
||||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="Header Files">
|
|
||||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
|
||||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="Resource Files">
|
|
||||||
<UniqueIdentifier>{4faac519-3a73-4b2b-96e7-fb597f02c0be}</UniqueIdentifier>
|
|
||||||
<Extensions>ico;rc</Extensions>
|
|
||||||
</Filter>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Image Include="dalamud.ico">
|
|
||||||
<Filter>Resource Files</Filter>
|
|
||||||
</Image>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ResourceCompile Include="resources.rc">
|
|
||||||
<Filter>Resource Files</Filter>
|
|
||||||
</ResourceCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="main.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="..\Dalamud.Boot\logging.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="pch.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="..\Dalamud.Boot\unicode.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <shellapi.h>
|
|
||||||
#include "..\Dalamud.Boot\logging.h"
|
|
||||||
#include "..\lib\CoreCLR\CoreCLR.h"
|
|
||||||
#include "..\lib\CoreCLR\boot.h"
|
|
||||||
|
|
||||||
int wmain(int argc, wchar_t** argv)
|
|
||||||
{
|
|
||||||
// Take care: don't redirect stderr/out here, we need to write our pid to stdout for XL to read
|
|
||||||
//logging::start_file_logging("dalamud.injector.boot.log", false);
|
|
||||||
logging::I("Dalamud Injector, (c) 2021 XIVLauncher Contributors");
|
|
||||||
logging::I("Built at : " __DATE__ "@" __TIME__);
|
|
||||||
|
|
||||||
wchar_t _module_path[MAX_PATH];
|
|
||||||
GetModuleFileNameW(NULL, _module_path, sizeof _module_path / 2);
|
|
||||||
std::filesystem::path fs_module_path(_module_path);
|
|
||||||
|
|
||||||
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.runtimeconfig.json").c_str());
|
|
||||||
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.dll").c_str());
|
|
||||||
|
|
||||||
// =========================================================================== //
|
|
||||||
|
|
||||||
void* entrypoint_vfn;
|
|
||||||
const auto result = InitializeClrAndGetEntryPoint(
|
|
||||||
GetModuleHandleW(nullptr),
|
|
||||||
false,
|
|
||||||
runtimeconfig_path,
|
|
||||||
module_path,
|
|
||||||
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
|
|
||||||
L"Main",
|
|
||||||
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
|
|
||||||
&entrypoint_vfn);
|
|
||||||
|
|
||||||
if (FAILED(result))
|
|
||||||
return result;
|
|
||||||
|
|
||||||
typedef int (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
|
|
||||||
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
|
||||||
|
|
||||||
logging::I("Running Dalamud Injector...");
|
|
||||||
const auto ret = entrypoint_fn(argc, argv);
|
|
||||||
logging::I("Done!");
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
MAINICON ICON "dalamud.ico"
|
|
||||||
|
|
@ -13,12 +13,13 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Output">
|
<PropertyGroup Label="Output">
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<OutputPath>..\bin\$(Configuration)\</OutputPath>
|
<OutputPath>..\bin\$(Configuration)\</OutputPath>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||||
|
<ApplicationIcon>dalamud.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Documentation">
|
<PropertyGroup Label="Documentation">
|
||||||
|
|
|
||||||
|
|
@ -25,34 +25,20 @@ namespace Dalamud.Injector
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Entrypoint to the program.
|
/// Entrypoint to the program.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class EntryPoint
|
public sealed class Program
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="argc">Count of arguments.</param>
|
|
||||||
/// <param name="argvPtr">char** string arguments.</param>
|
|
||||||
/// <returns>Return value (HRESULT).</returns>
|
|
||||||
public delegate int MainDelegate(int argc, IntPtr argvPtr);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the Dalamud injector.
|
/// Start the Dalamud injector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="argc">Count of arguments.</param>
|
/// <param name="argsArray">Command line arguments.</param>
|
||||||
/// <param name="argvPtr">byte** string arguments.</param>
|
|
||||||
/// <returns>Return value (HRESULT).</returns>
|
/// <returns>Return value (HRESULT).</returns>
|
||||||
public static int Main(int argc, IntPtr argvPtr)
|
public static int Main(string[] argsArray)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<string> args = new(argc);
|
// API14 TODO: Refactor
|
||||||
|
var args = argsArray.ToList();
|
||||||
unsafe
|
args.Insert(0, Assembly.GetExecutingAssembly().Location);
|
||||||
{
|
|
||||||
var argv = (IntPtr*)argvPtr;
|
|
||||||
for (var i = 0; i < argc; i++)
|
|
||||||
args.Add(Marshal.PtrToStringUni(argv[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Init(args);
|
Init(args);
|
||||||
args.Remove("-v"); // Remove "verbose" flag
|
args.Remove("-v"); // Remove "verbose" flag
|
||||||
|
|
@ -305,6 +291,7 @@ namespace Dalamud.Injector
|
||||||
var configurationPath = startInfo.ConfigurationPath;
|
var configurationPath = startInfo.ConfigurationPath;
|
||||||
var pluginDirectory = startInfo.PluginDirectory;
|
var pluginDirectory = startInfo.PluginDirectory;
|
||||||
var assetDirectory = startInfo.AssetDirectory;
|
var assetDirectory = startInfo.AssetDirectory;
|
||||||
|
var tempDirectory = startInfo.TempDirectory;
|
||||||
var delayInitializeMs = startInfo.DelayInitializeMs;
|
var delayInitializeMs = startInfo.DelayInitializeMs;
|
||||||
var logName = startInfo.LogName;
|
var logName = startInfo.LogName;
|
||||||
var logPath = startInfo.LogPath;
|
var logPath = startInfo.LogPath;
|
||||||
|
|
@ -335,6 +322,10 @@ namespace Dalamud.Injector
|
||||||
{
|
{
|
||||||
assetDirectory = args[i][key.Length..];
|
assetDirectory = args[i][key.Length..];
|
||||||
}
|
}
|
||||||
|
else if (args[i].StartsWith(key = "--dalamud-temp-directory="))
|
||||||
|
{
|
||||||
|
tempDirectory = args[i][key.Length..];
|
||||||
|
}
|
||||||
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
|
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
|
||||||
{
|
{
|
||||||
delayInitializeMs = int.Parse(args[i][key.Length..]);
|
delayInitializeMs = int.Parse(args[i][key.Length..]);
|
||||||
|
|
@ -447,6 +438,7 @@ namespace Dalamud.Injector
|
||||||
startInfo.ConfigurationPath = configurationPath;
|
startInfo.ConfigurationPath = configurationPath;
|
||||||
startInfo.PluginDirectory = pluginDirectory;
|
startInfo.PluginDirectory = pluginDirectory;
|
||||||
startInfo.AssetDirectory = assetDirectory;
|
startInfo.AssetDirectory = assetDirectory;
|
||||||
|
startInfo.TempDirectory = tempDirectory;
|
||||||
startInfo.Language = clientLanguage;
|
startInfo.Language = clientLanguage;
|
||||||
startInfo.Platform = platform;
|
startInfo.Platform = platform;
|
||||||
startInfo.DelayInitializeMs = delayInitializeMs;
|
startInfo.DelayInitializeMs = delayInitializeMs;
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
108
Dalamud.Test/Rpc/DalamudUriTests.cs
Normal file
108
Dalamud.Test/Rpc/DalamudUriTests.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Dalamud.Networking.Rpc.Model;
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Dalamud.Test.Rpc
|
||||||
|
{
|
||||||
|
public class DalamudUriTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("https://www.google.com/", false)]
|
||||||
|
[InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", true)]
|
||||||
|
public void ValidatesScheme(string uri, bool valid)
|
||||||
|
{
|
||||||
|
Action act = () => { _ = DalamudUri.FromUri(uri); };
|
||||||
|
|
||||||
|
var ex = Record.Exception(act);
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
Assert.Null(ex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.NotNull(ex);
|
||||||
|
Assert.IsType<ArgumentOutOfRangeException>(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", "plugininstaller")]
|
||||||
|
[InlineData("dalamud://Plugin/Dalamud.FindAnything/OpenWindow", "plugin")]
|
||||||
|
[InlineData("dalamud://Test", "test")]
|
||||||
|
public void ExtractsNamespace(string uri, string expectedNamespace)
|
||||||
|
{
|
||||||
|
var dalamudUri = DalamudUri.FromUri(uri);
|
||||||
|
Assert.Equal(expectedNamespace, dalamudUri.Namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/")]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux")]
|
||||||
|
[InlineData("dalamud://foo/bar/baz", "/bar/baz")]
|
||||||
|
[InlineData("dalamud://foo/bar", "/bar")]
|
||||||
|
[InlineData("dalamud://foo/bar/", "/bar/")]
|
||||||
|
[InlineData("dalamud://foo/", "/")]
|
||||||
|
public void ExtractsPath(string uri, string expectedPath)
|
||||||
|
{
|
||||||
|
var dalamudUri = DalamudUri.FromUri(uri);
|
||||||
|
Assert.Equal(expectedPath, dalamudUri.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo#frag", "/bar/baz/qux/?cow=moo#frag")]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/?cow=moo")]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux?cow=moo")]
|
||||||
|
[InlineData("dalamud://foo/bar/baz", "/bar/baz")]
|
||||||
|
[InlineData("dalamud://foo/bar?cow=moo", "/bar?cow=moo")]
|
||||||
|
[InlineData("dalamud://foo/bar", "/bar")]
|
||||||
|
[InlineData("dalamud://foo/bar/?cow=moo", "/bar/?cow=moo")]
|
||||||
|
[InlineData("dalamud://foo/bar/", "/bar/")]
|
||||||
|
[InlineData("dalamud://foo/?cow=moo#chicken", "/?cow=moo#chicken")]
|
||||||
|
[InlineData("dalamud://foo/?cow=moo", "/?cow=moo")]
|
||||||
|
[InlineData("dalamud://foo/", "/")]
|
||||||
|
public void ExtractsData(string uri, string expectedData)
|
||||||
|
{
|
||||||
|
var dalamudUri = DalamudUri.FromUri(uri);
|
||||||
|
|
||||||
|
Assert.Equal(expectedData, dalamudUri.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("dalamud://foo/bar", 0)]
|
||||||
|
[InlineData("dalamud://foo/bar?cow=moo", 1)]
|
||||||
|
[InlineData("dalamud://foo/bar?cow=moo&wolf=awoo", 2)]
|
||||||
|
[InlineData("dalamud://foo/bar?cow=moo&wolf=awoo&cat", 3)]
|
||||||
|
public void ExtractsQueryParams(string uri, int queryCount)
|
||||||
|
{
|
||||||
|
var dalamudUri = DalamudUri.FromUri(uri);
|
||||||
|
Assert.Equal(queryCount, dalamudUri.QueryParams.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux/meh/?foo=bar", 5, true)]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux/meh/", 5, true)]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux/meh", 5)]
|
||||||
|
[InlineData("dalamud://foo/bar/baz/qux", 4)]
|
||||||
|
[InlineData("dalamud://foo/bar/baz", 3)]
|
||||||
|
[InlineData("dalamud://foo/bar/", 2)]
|
||||||
|
[InlineData("dalamud://foo/bar", 2)]
|
||||||
|
public void ExtractsSegments(string uri, int segmentCount, bool finalSegmentEndsWithSlash = false)
|
||||||
|
{
|
||||||
|
var dalamudUri = DalamudUri.FromUri(uri);
|
||||||
|
var segments = dalamudUri.Segments;
|
||||||
|
|
||||||
|
// First segment must always be `/`
|
||||||
|
Assert.Equal("/", segments[0]);
|
||||||
|
|
||||||
|
Assert.Equal(segmentCount, segments.Length);
|
||||||
|
|
||||||
|
if (finalSegmentEndsWithSlash)
|
||||||
|
{
|
||||||
|
Assert.EndsWith("/", segments.Last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Dalamud.sln
14
Dalamud.sln
|
|
@ -1,4 +1,4 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.1.32319.34
|
VisualStudioVersion = 17.1.32319.34
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
|
@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
tools\BannedSymbols.txt = tools\BannedSymbols.txt
|
tools\BannedSymbols.txt = tools\BannedSymbols.txt
|
||||||
targets\Dalamud.Plugin.Bootstrap.targets = targets\Dalamud.Plugin.Bootstrap.targets
|
|
||||||
targets\Dalamud.Plugin.targets = targets\Dalamud.Plugin.targets
|
|
||||||
tools\dalamud.ruleset = tools\dalamud.ruleset
|
tools\dalamud.ruleset = tools\dalamud.ruleset
|
||||||
Directory.Build.props = Directory.Build.props
|
Directory.Build.props = Directory.Build.props
|
||||||
Directory.Packages.props = Directory.Packages.props
|
Directory.Packages.props = Directory.Packages.props
|
||||||
|
|
@ -27,8 +25,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boo
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Injector.Boot", "Dalamud.Injector.Boot\Dalamud.Injector.Boot.vcxproj", "{8874326B-E755-4D13-90B4-59AB263A3E6B}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
|
||||||
|
|
@ -49,8 +45,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator", "lib\FFX
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator.Runtime", "lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj", "{A6AA1C3F-9470-4922-9D3F-D4549657AB22}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator.Runtime", "lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj", "{A6AA1C3F-9470-4922-9D3F-D4549657AB22}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Injector", "Injector", "{19775C83-7117-4A5F-AA00-18889F46A490}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimgui", "external\cimgui\cimgui.vcxproj", "{8430077C-F736-4246-A052-8EA1CECE844E}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimgui", "external\cimgui\cimgui.vcxproj", "{8430077C-F736-4246-A052-8EA1CECE844E}"
|
||||||
|
|
@ -103,10 +97,6 @@ Global
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
|
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
|
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
|
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
|
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
|
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
|
@ -188,8 +178,6 @@ Global
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490}
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490}
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
|
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||||
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Dalamud.Configuration;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration to store settings for a dalamud plugin.
|
/// Configuration to store settings for a dalamud plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Api13ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
[Api14ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
||||||
public sealed class PluginConfigurations
|
public sealed class PluginConfigurations
|
||||||
{
|
{
|
||||||
private readonly DirectoryInfo configDirectory;
|
private readonly DirectoryInfo configDirectory;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<PropertyGroup Label="Feature">
|
<PropertyGroup Label="Feature">
|
||||||
<Description>XIV Launcher addon framework</Description>
|
<Description>XIV Launcher addon framework</Description>
|
||||||
<DalamudVersion>13.0.0.13</DalamudVersion>
|
<DalamudVersion>14.0.0.0</DalamudVersion>
|
||||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||||
<Version>$(DalamudVersion)</Version>
|
<Version>$(DalamudVersion)</Version>
|
||||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||||
|
|
@ -73,23 +73,19 @@
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MinSharp" />
|
<PackageReference Include="MinSharp" />
|
||||||
<PackageReference Include="SharpDX.Direct3D11" />
|
|
||||||
<PackageReference Include="SharpDX.Mathematics" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" />
|
<PackageReference Include="Newtonsoft.Json" />
|
||||||
<PackageReference Include="Serilog" />
|
<PackageReference Include="Serilog" />
|
||||||
<PackageReference Include="Serilog.Sinks.Async" />
|
<PackageReference Include="Serilog.Sinks.Async" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" />
|
<PackageReference Include="Serilog.Sinks.Console" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" />
|
<PackageReference Include="Serilog.Sinks.File" />
|
||||||
<PackageReference Include="sqlite-net-pcl" />
|
<PackageReference Include="sqlite-net-pcl" />
|
||||||
|
<PackageReference Include="StreamJsonRpc" />
|
||||||
<PackageReference Include="StyleCop.Analyzers">
|
<PackageReference Include="StyleCop.Analyzers">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="System.Collections.Immutable" />
|
|
||||||
<PackageReference Include="System.Drawing.Common" />
|
|
||||||
<PackageReference Include="System.Reactive" />
|
<PackageReference Include="System.Reactive" />
|
||||||
<PackageReference Include="System.Reflection.MetadataLoadContext" />
|
<PackageReference Include="System.Reflection.MetadataLoadContext" />
|
||||||
<PackageReference Include="System.Resources.Extensions" />
|
|
||||||
<PackageReference Include="TerraFX.Interop.Windows" />
|
<PackageReference Include="TerraFX.Interop.Windows" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
@ -122,6 +118,8 @@
|
||||||
<Content Include="licenses.txt">
|
<Content Include="licenses.txt">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<None Remove="Interface\ImGuiBackend\Renderers\gaussian.hlsl" />
|
||||||
|
<None Remove="Interface\ImGuiBackend\Renderers\fullscreen-quad.hlsl.bytes" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -226,9 +224,4 @@
|
||||||
<!-- writes the attribute to the customAssemblyInfo file -->
|
<!-- writes the attribute to the customAssemblyInfo file -->
|
||||||
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
|
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!-- Copy plugin .targets folder into distrib -->
|
|
||||||
<Target Name="CopyPluginTargets" AfterTargets="Build">
|
|
||||||
<Copy SourceFiles="$(ProjectDir)\..\targets\Dalamud.Plugin.targets;$(ProjectDir)\..\targets\Dalamud.Plugin.Bootstrap.targets" DestinationFolder="$(OutDir)\targets" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ public enum DalamudAsset
|
||||||
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DalamudAsset(DalamudAssetPurpose.Font)]
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
|
[DalamudAssetPath("UIRes", "FontAwesome710FreeSolid.otf")]
|
||||||
FontAwesomeFreeSolid = 2003,
|
FontAwesomeFreeSolid = 2003,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,13 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
||||||
var tsInfo =
|
var tsInfo =
|
||||||
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
||||||
dalamud.StartInfo.TroubleshootingPackData);
|
dalamud.StartInfo.TroubleshootingPackData);
|
||||||
this.HasModifiedGameDataFiles =
|
|
||||||
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed or LauncherTroubleshootingInfo.IndexIntegrityResult.Exception;
|
// Don't fail for IndexIntegrityResult.Exception, since the check during launch has a very small timeout
|
||||||
|
// this.HasModifiedGameDataFiles =
|
||||||
|
// tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed;
|
||||||
|
|
||||||
|
// TODO: Put above back when check in XL is fixed
|
||||||
|
this.HasModifiedGameDataFiles = false;
|
||||||
|
|
||||||
if (this.HasModifiedGameDataFiles)
|
if (this.HasModifiedGameDataFiles)
|
||||||
Log.Verbose("Game data integrity check failed!\n{TsData}", dalamud.StartInfo.TroubleshootingPackData);
|
Log.Verbose("Game data integrity check failed!\n{TsData}", dalamud.StartInfo.TroubleshootingPackData);
|
||||||
|
|
|
||||||
|
|
@ -192,8 +192,8 @@ public sealed class EntryPoint
|
||||||
|
|
||||||
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
|
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
|
||||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
|
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
|
||||||
Util.GetScmVersion(),
|
Versioning.GetScmVersion(),
|
||||||
Util.GetGitHashClientStructs(),
|
Versioning.GetGitHashClientStructs(),
|
||||||
FFXIVClientStructs.ThisAssembly.Git.Commits);
|
FFXIVClientStructs.ThisAssembly.Git.Commits);
|
||||||
|
|
||||||
dalamud.WaitForUnload();
|
dalamud.WaitForUnload();
|
||||||
|
|
@ -263,7 +263,7 @@ public sealed class EntryPoint
|
||||||
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
|
||||||
var searchPath = $".;{symbolPath}";
|
var searchPath = $".;{symbolPath}";
|
||||||
|
|
||||||
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess();
|
||||||
|
|
||||||
// Remove any existing Symbol Handler and Init a new one with our search path added
|
// Remove any existing Symbol Handler and Init a new one with our search path added
|
||||||
Windows.Win32.PInvoke.SymCleanup(currentProcess);
|
Windows.Win32.PInvoke.SymCleanup(currentProcess);
|
||||||
|
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon;
|
|
||||||
|
|
||||||
/// <summary>Argument pool for Addon Lifecycle services.</summary>
|
|
||||||
[ServiceManager.EarlyLoadedService]
|
|
||||||
internal sealed class AddonLifecyclePooledArgs : IServiceType
|
|
||||||
{
|
|
||||||
private readonly AddonSetupArgs?[] addonSetupArgPool = new AddonSetupArgs?[64];
|
|
||||||
private readonly AddonFinalizeArgs?[] addonFinalizeArgPool = new AddonFinalizeArgs?[64];
|
|
||||||
private readonly AddonDrawArgs?[] addonDrawArgPool = new AddonDrawArgs?[64];
|
|
||||||
private readonly AddonUpdateArgs?[] addonUpdateArgPool = new AddonUpdateArgs?[64];
|
|
||||||
private readonly AddonRefreshArgs?[] addonRefreshArgPool = new AddonRefreshArgs?[64];
|
|
||||||
private readonly AddonRequestedUpdateArgs?[] addonRequestedUpdateArgPool = new AddonRequestedUpdateArgs?[64];
|
|
||||||
private readonly AddonReceiveEventArgs?[] addonReceiveEventArgPool = new AddonReceiveEventArgs?[64];
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private AddonLifecyclePooledArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonSetupArgs> Rent(out AddonSetupArgs arg) => new(out arg, this.addonSetupArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonFinalizeArgs> Rent(out AddonFinalizeArgs arg) => new(out arg, this.addonFinalizeArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonDrawArgs> Rent(out AddonDrawArgs arg) => new(out arg, this.addonDrawArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonUpdateArgs> Rent(out AddonUpdateArgs arg) => new(out arg, this.addonUpdateArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonRefreshArgs> Rent(out AddonRefreshArgs arg) => new(out arg, this.addonRefreshArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonRequestedUpdateArgs> Rent(out AddonRequestedUpdateArgs arg) =>
|
|
||||||
new(out arg, this.addonRequestedUpdateArgPool);
|
|
||||||
|
|
||||||
/// <summary>Rents an instance of an argument.</summary>
|
|
||||||
/// <param name="arg">The rented instance.</param>
|
|
||||||
/// <returns>The returner.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public PooledEntry<AddonReceiveEventArgs> Rent(out AddonReceiveEventArgs arg) =>
|
|
||||||
new(out arg, this.addonReceiveEventArgPool);
|
|
||||||
|
|
||||||
/// <summary>Returns the object to the pool on dispose.</summary>
|
|
||||||
/// <typeparam name="T">The type.</typeparam>
|
|
||||||
public readonly ref struct PooledEntry<T>
|
|
||||||
where T : AddonArgs, new()
|
|
||||||
{
|
|
||||||
private readonly Span<T> pool;
|
|
||||||
private readonly T obj;
|
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="PooledEntry{T}"/> struct.</summary>
|
|
||||||
/// <param name="arg">An instance of the argument.</param>
|
|
||||||
/// <param name="pool">The pool to rent from and return to.</param>
|
|
||||||
public PooledEntry(out T arg, Span<T> pool)
|
|
||||||
{
|
|
||||||
this.pool = pool;
|
|
||||||
foreach (ref var item in pool)
|
|
||||||
{
|
|
||||||
if (Interlocked.Exchange(ref item, null) is { } v)
|
|
||||||
{
|
|
||||||
this.obj = arg = v;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.obj = arg = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Returns the item to the pool.</summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
var tmp = this.obj;
|
|
||||||
foreach (ref var item in this.pool)
|
|
||||||
{
|
|
||||||
if (Interlocked.Exchange(ref item, tmp) is not { } tmp2)
|
|
||||||
return;
|
|
||||||
tmp = tmp2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,19 +5,24 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for AddonLifecycle AddonArgTypes.
|
/// Base class for AddonLifecycle AddonArgTypes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract unsafe class AddonArgs
|
public class AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constant string representing the name of an addon that is invalid.
|
/// Constant string representing the name of an addon that is invalid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string InvalidAddon = "NullAddon";
|
public const string InvalidAddon = "NullAddon";
|
||||||
|
|
||||||
private string? addonName;
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the addon this args referrers to.
|
/// Gets the name of the addon this args referrers to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AddonName => this.GetAddonName();
|
public string AddonName { get; private set; } = InvalidAddon;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the pointer to the addons AtkUnitBase.
|
/// Gets the pointer to the addons AtkUnitBase.
|
||||||
|
|
@ -25,55 +30,17 @@ public abstract unsafe class AddonArgs
|
||||||
public AtkUnitBasePtr Addon
|
public AtkUnitBasePtr Addon
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
internal set;
|
internal set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
|
||||||
|
if (!this.Addon.IsNull && !string.IsNullOrEmpty(value.Name))
|
||||||
|
this.AddonName = value.Name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of these args.
|
/// Gets the type of these args.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract AddonArgsType Type { get; }
|
public virtual AddonArgsType Type => AddonArgsType.Generic;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if addon name matches the given span of char.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name to check.</param>
|
|
||||||
/// <returns>Whether it is the case.</returns>
|
|
||||||
internal bool IsAddon(string name)
|
|
||||||
{
|
|
||||||
if (this.Addon.IsNull)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (name.Length is 0 or > 32)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.Addon.Name))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return name == this.Addon.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears this AddonArgs values.
|
|
||||||
/// </summary>
|
|
||||||
internal virtual void Clear()
|
|
||||||
{
|
|
||||||
this.addonName = null;
|
|
||||||
this.Addon = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper method for ensuring the name of the addon is valid.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The name of the addon for this object. <see cref="InvalidAddon"/> when invalid.</returns>
|
|
||||||
private string GetAddonName()
|
|
||||||
{
|
|
||||||
if (this.Addon.IsNull) return InvalidAddon;
|
|
||||||
|
|
||||||
var name = this.Addon.Name;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
return InvalidAddon;
|
|
||||||
|
|
||||||
return this.addonName ??= name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
Normal file
22
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonCloseArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Close events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonCloseArgs : AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonCloseArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonCloseArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Close;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the window should fire the callback method on close.
|
||||||
|
/// </summary>
|
||||||
|
public bool FireCallback { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Addon argument data for Draw events.
|
|
||||||
/// </summary>
|
|
||||||
public class AddonDrawArgs : AddonArgs, ICloneable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonDrawArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
|
||||||
public AddonDrawArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override AddonArgsType Type => AddonArgsType.Draw;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Addon argument data for ReceiveEvent events.
|
|
||||||
/// </summary>
|
|
||||||
public class AddonFinalizeArgs : AddonArgs, ICloneable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonFinalizeArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
|
||||||
public AddonFinalizeArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override AddonArgsType Type => AddonArgsType.Finalize;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
}
|
|
||||||
32
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
Normal file
32
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonHideArgs.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Hide events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonHideArgs : AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonHideArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonHideArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Hide;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to call the hide callback handler when this hides.
|
||||||
|
/// </summary>
|
||||||
|
public bool CallHideCallback { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the flags that the window will set when it Shows/Hides.
|
||||||
|
/// </summary>
|
||||||
|
public uint SetShowHideFlags { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether something for this event message.
|
||||||
|
/// </summary>
|
||||||
|
internal bool UnknownBool { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for ReceiveEvent events.
|
/// Addon argument data for ReceiveEvent events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
public class AddonReceiveEventArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonReceiveEventArgs()
|
||||||
public AddonReceiveEventArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,23 +31,7 @@ public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
||||||
public nint AtkEvent { get; set; }
|
public nint AtkEvent { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the pointer to a block of data for this event message.
|
/// Gets or sets the pointer to an AtkEventData for this event message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public nint Data { get; set; }
|
public nint AtkEventData { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.AtkEventType = default;
|
|
||||||
this.EventParam = default;
|
|
||||||
this.AtkEvent = default;
|
|
||||||
this.Data = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dalamud.Game.NativeWrapper;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for Refresh events.
|
/// Addon argument data for Refresh events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonRefreshArgs : AddonArgs, ICloneable
|
public class AddonRefreshArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonRefreshArgs()
|
||||||
public AddonRefreshArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,19 +36,30 @@ public class AddonRefreshArgs : AddonArgs, ICloneable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the AtkValues in the form of a span.
|
/// Gets the AtkValues in the form of a span.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
|
||||||
|
[Api15ToDo("Make this internal, remove obsolete")]
|
||||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
/// <summary>
|
||||||
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
|
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
||||||
|
/// </summary>
|
||||||
/// <inheritdoc cref="Clone"/>
|
/// <returns>
|
||||||
object ICloneable.Clone() => this.Clone();
|
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
||||||
|
/// </returns>
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
public IEnumerable<AtkValuePtr> AtkValueEnumerable
|
||||||
internal override void Clear()
|
|
||||||
{
|
{
|
||||||
base.Clear();
|
get
|
||||||
this.AtkValueCount = default;
|
{
|
||||||
this.AtkValues = default;
|
for (var i = 0; i < this.AtkValueCount; i++)
|
||||||
|
{
|
||||||
|
AtkValuePtr ptr;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for OnRequestedUpdate events.
|
/// Addon argument data for OnRequestedUpdate events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
public class AddonRequestedUpdateArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonRequestedUpdateArgs()
|
||||||
public AddonRequestedUpdateArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,18 +24,4 @@ public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
||||||
/// Gets or sets the StringArrayData** for this event.
|
/// Gets or sets the StringArrayData** for this event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public nint StringArrayData { get; set; }
|
public nint StringArrayData { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.NumberArrayData = default;
|
|
||||||
this.StringArrayData = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dalamud.Game.NativeWrapper;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Addon argument data for Setup events.
|
/// Addon argument data for Setup events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonSetupArgs : AddonArgs, ICloneable
|
public class AddonSetupArgs : AddonArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
internal AddonSetupArgs()
|
||||||
public AddonSetupArgs()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,19 +36,30 @@ public class AddonSetupArgs : AddonArgs, ICloneable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the AtkValues in the form of a span.
|
/// Gets the AtkValues in the form of a span.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
|
||||||
|
[Api15ToDo("Make this internal, remove obsolete")]
|
||||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
/// <summary>
|
||||||
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
|
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
||||||
|
/// </summary>
|
||||||
/// <inheritdoc cref="Clone"/>
|
/// <returns>
|
||||||
object ICloneable.Clone() => this.Clone();
|
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
||||||
|
/// </returns>
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
public IEnumerable<AtkValuePtr> AtkValueEnumerable
|
||||||
internal override void Clear()
|
|
||||||
{
|
{
|
||||||
base.Clear();
|
get
|
||||||
this.AtkValueCount = default;
|
{
|
||||||
this.AtkValues = default;
|
for (var i = 0; i < this.AtkValueCount; i++)
|
||||||
|
{
|
||||||
|
AtkValuePtr ptr;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
Normal file
27
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonShowArgs.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Show events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonShowArgs : AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonShowArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonShowArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Show;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the window should play open sound effects.
|
||||||
|
/// </summary>
|
||||||
|
public bool SilenceOpenSoundEffect { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the flags that the window will unset when it Shows/Hides.
|
||||||
|
/// </summary>
|
||||||
|
public uint UnsetShowHideFlags { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Addon argument data for Update events.
|
|
||||||
/// </summary>
|
|
||||||
public class AddonUpdateArgs : AddonArgs, ICloneable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonUpdateArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Not intended for public construction.", false)]
|
|
||||||
public AddonUpdateArgs()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override AddonArgsType Type => AddonArgsType.Update;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the time since the last update.
|
|
||||||
/// </summary>
|
|
||||||
public float TimeDelta
|
|
||||||
{
|
|
||||||
get => this.TimeDeltaInternal;
|
|
||||||
init => this.TimeDeltaInternal = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the time since the last update.
|
|
||||||
/// </summary>
|
|
||||||
internal float TimeDeltaInternal { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
|
||||||
public AddonUpdateArgs Clone() => (AddonUpdateArgs)this.MemberwiseClone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Clone"/>
|
|
||||||
object ICloneable.Clone() => this.Clone();
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AddonArgs.Clear"/>
|
|
||||||
internal override void Clear()
|
|
||||||
{
|
|
||||||
base.Clear();
|
|
||||||
this.TimeDeltaInternal = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,26 +5,16 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum AddonArgsType
|
public enum AddonArgsType
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic arg type that contains no meaningful data.
|
||||||
|
/// </summary>
|
||||||
|
Generic,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains argument data for Setup.
|
/// Contains argument data for Setup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Setup,
|
Setup,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains argument data for Update.
|
|
||||||
/// </summary>
|
|
||||||
Update,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains argument data for Draw.
|
|
||||||
/// </summary>
|
|
||||||
Draw,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains argument data for Finalize.
|
|
||||||
/// </summary>
|
|
||||||
Finalize,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains argument data for RequestedUpdate.
|
/// Contains argument data for RequestedUpdate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -39,4 +29,19 @@ public enum AddonArgsType
|
||||||
/// Contains argument data for ReceiveEvent.
|
/// Contains argument data for ReceiveEvent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ReceiveEvent,
|
ReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Show.
|
||||||
|
/// </summary>
|
||||||
|
Show,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Hide.
|
||||||
|
/// </summary>
|
||||||
|
Hide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Close.
|
||||||
|
/// </summary>
|
||||||
|
Close,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ public enum AddonEvent
|
||||||
/// An event that is fired before an addon begins its update cycle via <see cref="AtkUnitBase.Update"/>. This event
|
/// An event that is fired before an addon begins its update cycle via <see cref="AtkUnitBase.Update"/>. This event
|
||||||
/// is fired every frame that an addon is loaded, regardless of visibility.
|
/// is fired every frame that an addon is loaded, regardless of visibility.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="AddonUpdateArgs"/>
|
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -42,7 +41,6 @@ public enum AddonEvent
|
||||||
/// An event that is fired before an addon begins drawing to screen via <see cref="AtkUnitBase.Draw"/>. Unlike
|
/// An event that is fired before an addon begins drawing to screen via <see cref="AtkUnitBase.Draw"/>. Unlike
|
||||||
/// <see cref="PreUpdate"/>, this event is only fired if an addon is visible or otherwise drawing to screen.
|
/// <see cref="PreUpdate"/>, this event is only fired if an addon is visible or otherwise drawing to screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="AddonDrawArgs"/>
|
|
||||||
PreDraw,
|
PreDraw,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -62,7 +60,6 @@ public enum AddonEvent
|
||||||
/// <br />
|
/// <br />
|
||||||
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
|
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <seealso cref="AddonFinalizeArgs"/>
|
|
||||||
PreFinalize,
|
PreFinalize,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -118,4 +115,92 @@ public enum AddonEvent
|
||||||
/// See <see cref="PreReceiveEvent"/> for more information.
|
/// See <see cref="PreReceiveEvent"/> for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PostReceiveEvent,
|
PostReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its open method.
|
||||||
|
/// </summary>
|
||||||
|
PreOpen,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its open method.
|
||||||
|
/// </summary>
|
||||||
|
PostOpen,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An even that is fired before an addon processes its Close method.
|
||||||
|
/// </summary>
|
||||||
|
PreClose,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Close method.
|
||||||
|
/// </summary>
|
||||||
|
PostClose,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its Show method.
|
||||||
|
/// </summary>
|
||||||
|
PreShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Show method.
|
||||||
|
/// </summary>
|
||||||
|
PostShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its Hide method.
|
||||||
|
/// </summary>
|
||||||
|
PreHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Hide method.
|
||||||
|
/// </summary>
|
||||||
|
PostHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its OnMove method.
|
||||||
|
/// OnMove is triggered only when a move is completed.
|
||||||
|
/// </summary>
|
||||||
|
PreMove,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its OnMove method.
|
||||||
|
/// OnMove is triggered only when a move is completed.
|
||||||
|
/// </summary>
|
||||||
|
PostMove,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its MouseOver method.
|
||||||
|
/// </summary>
|
||||||
|
PreMouseOver,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its MouseOver method.
|
||||||
|
/// </summary>
|
||||||
|
PostMouseOver,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its MouseOut method.
|
||||||
|
/// </summary>
|
||||||
|
PreMouseOut,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its MouseOut method.
|
||||||
|
/// </summary>
|
||||||
|
PostMouseOut,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its Focus method.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||||
|
/// </remarks>
|
||||||
|
PreFocus,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after an addon has processed its Focus method.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||||
|
/// </remarks>
|
||||||
|
PostFocus,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Hooking.Internal;
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
@ -21,75 +19,36 @@ namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal unsafe class AddonLifecycle : IInternalDisposableService
|
internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all allocated addon virtual tables.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly List<AddonVirtualTable> AllocatedTables = [];
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
|
||||||
|
|
||||||
private readonly nint disallowedReceiveEventAddress;
|
|
||||||
|
|
||||||
private readonly AddonLifecycleAddressResolver address;
|
|
||||||
private readonly AddonSetupHook<AtkUnitBase.Delegates.OnSetup> onAddonSetupHook;
|
|
||||||
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
|
||||||
private readonly CallHook<AtkUnitBase.Delegates.Draw> onAddonDrawHook;
|
|
||||||
private readonly CallHook<AtkUnitBase.Delegates.Update> onAddonUpdateHook;
|
|
||||||
private readonly Hook<AtkUnitManager.Delegates.RefreshAddon> onAddonRefreshHook;
|
|
||||||
private readonly CallHook<AtkUnitBase.Delegates.OnRequestedUpdate> onAddonRequestedUpdateHook;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private AddonLifecycle(TargetSigScanner sigScanner)
|
private AddonLifecycle()
|
||||||
{
|
{
|
||||||
this.address = new AddonLifecycleAddressResolver();
|
this.onInitializeAddonHook = Hook<AtkUnitBase.Delegates.Initialize>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->Initialize, this.OnAddonInitialize);
|
||||||
this.address.Setup(sigScanner);
|
this.onInitializeAddonHook.Enable();
|
||||||
|
|
||||||
this.disallowedReceiveEventAddress = (nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveEvent;
|
|
||||||
|
|
||||||
var refreshAddonAddress = (nint)RaptureAtkUnitManager.StaticVirtualTablePointer->RefreshAddon;
|
|
||||||
|
|
||||||
this.onAddonSetupHook = new AddonSetupHook<AtkUnitBase.Delegates.OnSetup>(this.address.AddonSetup, this.OnAddonSetup);
|
|
||||||
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
|
|
||||||
this.onAddonDrawHook = new CallHook<AtkUnitBase.Delegates.Draw>(this.address.AddonDraw, this.OnAddonDraw);
|
|
||||||
this.onAddonUpdateHook = new CallHook<AtkUnitBase.Delegates.Update>(this.address.AddonUpdate, this.OnAddonUpdate);
|
|
||||||
this.onAddonRefreshHook = Hook<AtkUnitManager.Delegates.RefreshAddon>.FromAddress(refreshAddonAddress, this.OnAddonRefresh);
|
|
||||||
this.onAddonRequestedUpdateHook = new CallHook<AtkUnitBase.Delegates.OnRequestedUpdate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
|
|
||||||
|
|
||||||
this.onAddonSetupHook.Enable();
|
|
||||||
this.onAddonFinalizeHook.Enable();
|
|
||||||
this.onAddonDrawHook.Enable();
|
|
||||||
this.onAddonUpdateHook.Enable();
|
|
||||||
this.onAddonRefreshHook.Enable();
|
|
||||||
this.onAddonRequestedUpdateHook.Enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
|
|
||||||
/// </summary>
|
|
||||||
internal List<AddonLifecycleReceiveEventListener> ReceiveEventListeners { get; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of all AddonLifecycle Event Listeners.
|
/// Gets a list of all AddonLifecycle Event Listeners.
|
||||||
/// </summary>
|
/// </summary> <br/>
|
||||||
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
|
/// Mapping is: EventType -> AddonName -> ListenerList
|
||||||
|
internal Dictionary<AddonEvent, Dictionary<string, HashSet<AddonLifecycleEventListener>>> EventListeners { get; } = [];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.onAddonSetupHook.Dispose();
|
this.onInitializeAddonHook?.Dispose();
|
||||||
this.onAddonFinalizeHook.Dispose();
|
this.onInitializeAddonHook = null;
|
||||||
this.onAddonDrawHook.Dispose();
|
|
||||||
this.onAddonUpdateHook.Dispose();
|
|
||||||
this.onAddonRefreshHook.Dispose();
|
|
||||||
this.onAddonRequestedUpdateHook.Dispose();
|
|
||||||
|
|
||||||
foreach (var receiveEventListener in this.ReceiveEventListeners)
|
AllocatedTables.ForEach(entry => entry.Dispose());
|
||||||
{
|
AllocatedTables.Clear();
|
||||||
receiveEventListener.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -98,20 +57,20 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="listener">The listener to register.</param>
|
/// <param name="listener">The listener to register.</param>
|
||||||
internal void RegisterListener(AddonLifecycleEventListener listener)
|
internal void RegisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
this.framework.RunOnTick(() =>
|
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||||
{
|
{
|
||||||
this.EventListeners.Add(listener);
|
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If we want receive event messages have an already active addon, enable the receive event hook.
|
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
||||||
// If the addon isn't active yet, we'll grab the hook when it sets up.
|
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
||||||
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
|
||||||
{
|
{
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
|
||||||
{
|
return;
|
||||||
receiveEventListener.TryEnable();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -120,28 +79,14 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="listener">The listener to unregister.</param>
|
/// <param name="listener">The listener to unregister.</param>
|
||||||
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
// Set removed state to true immediately, then lazily remove it from the EventListeners list on next Framework Update.
|
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
||||||
listener.Removed = true;
|
|
||||||
|
|
||||||
this.framework.RunOnTick(() =>
|
|
||||||
{
|
{
|
||||||
this.EventListeners.Remove(listener);
|
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
||||||
|
|
||||||
// If we are disabling an ReceiveEvent listener, check if we should disable the hook.
|
|
||||||
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
|
||||||
{
|
{
|
||||||
// Get the ReceiveEvent Listener for this addon
|
addonListener.Remove(listener);
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
|
||||||
{
|
|
||||||
// If there are no other listeners listening for this event, disable the hook.
|
|
||||||
if (!this.EventListeners.Any(listeners => listeners.AddonName.Contains(listener.AddonName) && listener.EventType is AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent))
|
|
||||||
{
|
|
||||||
receiveEventListener.Disable();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoke listeners for the specified event type.
|
/// Invoke listeners for the specified event type.
|
||||||
|
|
@ -151,226 +96,63 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="blame">What to blame on errors.</param>
|
/// <param name="blame">What to blame on errors.</param>
|
||||||
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
||||||
{
|
{
|
||||||
// Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
|
// Early return if we don't have any listeners of this type
|
||||||
foreach (var listener in this.EventListeners)
|
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
||||||
|
|
||||||
|
// Handle listeners for this event type that don't care which addon is triggering it
|
||||||
|
if (addonListeners.TryGetValue(string.Empty, out var globalListeners))
|
||||||
|
{
|
||||||
|
foreach (var listener in globalListeners)
|
||||||
{
|
{
|
||||||
if (listener.EventType != eventType)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// If the listener is pending removal, and is waiting until the next Framework Update, don't invoke listener.
|
|
||||||
if (listener.Removed)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Match on string.empty for listeners that want events for all addons.
|
|
||||||
if (!string.IsNullOrWhiteSpace(listener.AddonName) && !args.IsAddon(listener.AddonName))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
listener.FunctionDelegate.Invoke(eventType, args);
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke.");
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global addon event listener.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterReceiveEventHook(AtkUnitBase* addon)
|
// Handle listeners that are listening for this addon and event type specifically
|
||||||
|
if (addonListeners.TryGetValue(args.AddonName, out var addonListener))
|
||||||
{
|
{
|
||||||
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
|
foreach (var listener in addonListener)
|
||||||
// Disallows hooking the core internal event handler.
|
|
||||||
var addonName = addon->NameString;
|
|
||||||
var receiveEventAddress = (nint)addon->VirtualTable->ReceiveEvent;
|
|
||||||
if (receiveEventAddress != this.disallowedReceiveEventAddress)
|
|
||||||
{
|
|
||||||
// If we have a ReceiveEvent listener already made for this hook address, add this addon's name to that handler.
|
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.FunctionAddress == receiveEventAddress) is { } existingListener)
|
|
||||||
{
|
|
||||||
if (!existingListener.AddonNames.Contains(addonName))
|
|
||||||
{
|
|
||||||
existingListener.AddonNames.Add(addonName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else, we have an addon that we don't have the ReceiveEvent for yet, make it.
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.ReceiveEventListeners.Add(new AddonLifecycleReceiveEventListener(this, addonName, receiveEventAddress));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have an active listener for this addon already, we need to activate this hook.
|
|
||||||
if (this.EventListeners.Any(listener => (listener.EventType is AddonEvent.PostReceiveEvent or AddonEvent.PreReceiveEvent) && listener.AddonName == addonName))
|
|
||||||
{
|
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } receiveEventListener)
|
|
||||||
{
|
|
||||||
receiveEventListener.TryEnable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnregisterReceiveEventHook(string addonName)
|
|
||||||
{
|
|
||||||
// Remove this addons ReceiveEvent Registration
|
|
||||||
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } eventListener)
|
|
||||||
{
|
|
||||||
eventListener.AddonNames.Remove(addonName);
|
|
||||||
|
|
||||||
// If there are no more listeners let's remove and dispose.
|
|
||||||
if (eventListener.AddonNames.Count is 0)
|
|
||||||
{
|
|
||||||
this.ReceiveEventListeners.Remove(eventListener);
|
|
||||||
eventListener.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.RegisterReceiveEventHook(addon);
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific addon {args.AddonName}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonSetupArgs arg);
|
private void OnAddonInitialize(AtkUnitBase* addon)
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.AtkValueCount = valueCount;
|
|
||||||
arg.AtkValues = (nint)values;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreSetup, arg);
|
|
||||||
valueCount = arg.AtkValueCount;
|
|
||||||
values = (AtkValue*)arg.AtkValues;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->OnSetup(valueCount, values);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonSetup. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostSetup, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var addonName = atkUnitBase[0]->NameString;
|
this.LogInitialize(addon->NameString);
|
||||||
this.UnregisterReceiveEventHook(addonName);
|
|
||||||
|
// AddonVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
|
||||||
|
AllocatedTables.Add(new AddonVirtualTable(addon, this));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
|
Log.Error(e, "Exception in AddonLifecycle during OnAddonInitialize.");
|
||||||
}
|
}
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg);
|
this.onInitializeAddonHook!.Original(addon);
|
||||||
arg.Clear();
|
}
|
||||||
arg.Addon = (nint)atkUnitBase[0];
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreFinalize, arg);
|
|
||||||
|
|
||||||
try
|
[Conditional("DEBUG")]
|
||||||
|
private void LogInitialize(string addonName)
|
||||||
{
|
{
|
||||||
this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
|
Log.Debug($"Initializing {addonName}");
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonFinalize. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAddonDraw(AtkUnitBase* addon)
|
|
||||||
{
|
|
||||||
using var returner = this.argsPool.Rent(out AddonDrawArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreDraw, arg);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->Draw();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonDraw. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostDraw, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
|
||||||
{
|
|
||||||
using var returner = this.argsPool.Rent(out AddonUpdateArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.TimeDeltaInternal = delta;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreUpdate, arg);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->Update(delta);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonUpdate. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostUpdate, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool OnAddonRefresh(AtkUnitManager* thisPtr, AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
|
||||||
{
|
|
||||||
var result = false;
|
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonRefreshArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.AtkValueCount = valueCount;
|
|
||||||
arg.AtkValues = (nint)values;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreRefresh, arg);
|
|
||||||
valueCount = arg.AtkValueCount;
|
|
||||||
values = (AtkValue*)arg.AtkValues;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = this.onAddonRefreshHook.Original(thisPtr, addon, valueCount, values);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonRefresh. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostRefresh, arg);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
|
||||||
{
|
|
||||||
using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.NumberArrayData = (nint)numberArrayData;
|
|
||||||
arg.StringArrayData = (nint)stringArrayData;
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg);
|
|
||||||
numberArrayData = (NumberArrayData**)arg.NumberArrayData;
|
|
||||||
stringArrayData = (StringArrayData**)arg.StringArrayData;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
addon->OnRequestedUpdate(numberArrayData, stringArrayData);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, arg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,7 +169,7 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
||||||
|
|
||||||
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
private readonly List<AddonLifecycleEventListener> eventListeners = [];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AddonLifecycleService memory address resolver.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon setup hook invoked by the AtkUnitManager.
|
|
||||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
|
||||||
/// This is called for a majority of all addon OnSetup's.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonSetup { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the other addon setup hook invoked by the AtkUnitManager.
|
|
||||||
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
|
|
||||||
/// This seems to be called rarely for specific addons.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonSetup2 { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonFinalize { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon draw hook invoked by virtual function call.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonDraw { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon update hook invoked by virtual function call.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonUpdate { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
|
|
||||||
/// </summary>
|
|
||||||
public nint AddonOnRequestedUpdate { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scan for and setup any configured address pointers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
|
||||||
protected override void Setup64Bit(ISigScanner sig)
|
|
||||||
{
|
|
||||||
this.AddonSetup = sig.ScanText("4C 8B 88 ?? ?? ?? ?? 66 44 39 BB");
|
|
||||||
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
|
||||||
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01");
|
|
||||||
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2");
|
|
||||||
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 A0 01 00 00 48 8B 5C 24 30");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -26,11 +26,6 @@ internal class AddonLifecycleEventListener
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AddonName { get; init; }
|
public string AddonName { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this event has been unregistered.
|
|
||||||
/// </summary>
|
|
||||||
public bool Removed { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the event type this listener is looking for.
|
/// Gets the event type this listener is looking for.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.Logging.Internal;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class is a helper for tracking and invoking listener delegates for Addon_OnReceiveEvent.
|
|
||||||
/// Multiple addons may use the same ReceiveEvent function, this helper makes sure that those addon events are handled properly.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
|
|
||||||
{
|
|
||||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="service">AddonLifecycle service instance.</param>
|
|
||||||
/// <param name="addonName">Initial Addon Requesting this listener.</param>
|
|
||||||
/// <param name="receiveEventAddress">Address of Addon's ReceiveEvent function.</param>
|
|
||||||
internal AddonLifecycleReceiveEventListener(AddonLifecycle service, string addonName, nint receiveEventAddress)
|
|
||||||
{
|
|
||||||
this.AddonLifecycle = service;
|
|
||||||
this.AddonNames = [addonName];
|
|
||||||
this.FunctionAddress = receiveEventAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the list of addons that use this receive event hook.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> AddonNames { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the ReceiveEvent function as provided by the vtable on setup.
|
|
||||||
/// </summary>
|
|
||||||
public nint FunctionAddress { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the contained hook for these addons.
|
|
||||||
/// </summary>
|
|
||||||
public Hook<AtkUnitBase.Delegates.ReceiveEvent>? Hook { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Reference to AddonLifecycle service instance.
|
|
||||||
/// </summary>
|
|
||||||
private AddonLifecycle AddonLifecycle { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to hook and enable this receive event handler.
|
|
||||||
/// </summary>
|
|
||||||
public void TryEnable()
|
|
||||||
{
|
|
||||||
this.Hook ??= Hook<AtkUnitBase.Delegates.ReceiveEvent>.FromAddress(this.FunctionAddress, this.OnReceiveEvent);
|
|
||||||
this.Hook?.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disable the hook for this receive event handler.
|
|
||||||
/// </summary>
|
|
||||||
public void Disable()
|
|
||||||
{
|
|
||||||
this.Hook?.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.Hook?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
|
||||||
{
|
|
||||||
// Check that we didn't get here through a call to another addons handler.
|
|
||||||
var addonName = addon->NameString;
|
|
||||||
if (!this.AddonNames.Contains(addonName))
|
|
||||||
{
|
|
||||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg);
|
|
||||||
arg.Clear();
|
|
||||||
arg.Addon = (nint)addon;
|
|
||||||
arg.AtkEventType = (byte)eventType;
|
|
||||||
arg.EventParam = eventParam;
|
|
||||||
arg.AtkEvent = (IntPtr)atkEvent;
|
|
||||||
arg.Data = (nint)atkEventData;
|
|
||||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg);
|
|
||||||
eventType = (AtkEventType)arg.AtkEventType;
|
|
||||||
eventParam = arg.EventParam;
|
|
||||||
atkEvent = (AtkEvent*)arg.AtkEvent;
|
|
||||||
atkEventData = (AtkEventData*)arg.Data;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Caught exception when calling original AddonReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using Reloaded.Hooks.Definitions;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Addon.Lifecycle;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a callsite hook used to replace the address of the OnSetup function in r9.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
|
||||||
internal class AddonSetupHook<T> : IDisposable where T : Delegate
|
|
||||||
{
|
|
||||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
|
||||||
|
|
||||||
private T? detour;
|
|
||||||
private bool activated;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AddonSetupHook{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the instruction to replace.</param>
|
|
||||||
/// <param name="detour">Delegate to invoke.</param>
|
|
||||||
internal AddonSetupHook(nint address, T detour)
|
|
||||||
{
|
|
||||||
this.detour = detour;
|
|
||||||
|
|
||||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
|
||||||
var code = new[]
|
|
||||||
{
|
|
||||||
"use64",
|
|
||||||
$"mov r9, 0x{detourPtr:X8}",
|
|
||||||
};
|
|
||||||
|
|
||||||
var opt = new AsmHookOptions
|
|
||||||
{
|
|
||||||
PreferRelativeJump = true,
|
|
||||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
|
||||||
MaxOpcodeSize = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the hook is enabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Enable()
|
|
||||||
{
|
|
||||||
if (!this.activated)
|
|
||||||
{
|
|
||||||
this.activated = true;
|
|
||||||
this.asmHook.Activate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.asmHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Disable()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a hook from the current process.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
this.detour = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
638
Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
Normal file
638
Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
Normal file
|
|
@ -0,0 +1,638 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class that holds references to an addons original and modified virtual table entries.
|
||||||
|
/// </summary>
|
||||||
|
internal unsafe class AddonVirtualTable : IDisposable
|
||||||
|
{
|
||||||
|
// This need to be at minimum the largest virtual table size of all addons
|
||||||
|
// Copying extra entries is not problematic, and is considered safe.
|
||||||
|
private const int VirtualTableEntryCount = 200;
|
||||||
|
|
||||||
|
private const bool EnableLogging = false;
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("LifecycleVT");
|
||||||
|
|
||||||
|
private readonly AddonLifecycle lifecycleService;
|
||||||
|
|
||||||
|
// Each addon gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||||
|
private readonly AddonSetupArgs setupArgs = new();
|
||||||
|
private readonly AddonArgs finalizeArgs = new();
|
||||||
|
private readonly AddonArgs drawArgs = new();
|
||||||
|
private readonly AddonArgs updateArgs = new();
|
||||||
|
private readonly AddonRefreshArgs refreshArgs = new();
|
||||||
|
private readonly AddonRequestedUpdateArgs requestedUpdateArgs = new();
|
||||||
|
private readonly AddonReceiveEventArgs receiveEventArgs = new();
|
||||||
|
private readonly AddonArgs openArgs = new();
|
||||||
|
private readonly AddonCloseArgs closeArgs = new();
|
||||||
|
private readonly AddonShowArgs showArgs = new();
|
||||||
|
private readonly AddonHideArgs hideArgs = new();
|
||||||
|
private readonly AddonArgs onMoveArgs = new();
|
||||||
|
private readonly AddonArgs onMouseOverArgs = new();
|
||||||
|
private readonly AddonArgs onMouseOutArgs = new();
|
||||||
|
private readonly AddonArgs focusArgs = new();
|
||||||
|
|
||||||
|
private readonly AtkUnitBase* atkUnitBase;
|
||||||
|
|
||||||
|
private readonly AtkUnitBase.AtkUnitBaseVirtualTable* originalVirtualTable;
|
||||||
|
private readonly AtkUnitBase.AtkUnitBaseVirtualTable* modifiedVirtualTable;
|
||||||
|
|
||||||
|
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
|
||||||
|
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
|
||||||
|
private readonly AtkUnitBase.Delegates.Dtor destructorFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnSetup onSetupFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Finalizer finalizerFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Draw drawFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Update updateFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnRefresh onRefreshFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnRequestedUpdate onRequestedUpdateFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.ReceiveEvent onReceiveEventFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Open openFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Close closeFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Show showFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Hide hideFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnMove onMoveFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.Focus focusFunction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonVirtualTable"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addon">AtkUnitBase* for the addon to replace the table of.</param>
|
||||||
|
/// <param name="lifecycleService">Reference to AddonLifecycle service to callback and invoke listeners.</param>
|
||||||
|
internal AddonVirtualTable(AtkUnitBase* addon, AddonLifecycle lifecycleService)
|
||||||
|
{
|
||||||
|
this.atkUnitBase = addon;
|
||||||
|
this.lifecycleService = lifecycleService;
|
||||||
|
|
||||||
|
// Save original virtual table
|
||||||
|
this.originalVirtualTable = addon->VirtualTable;
|
||||||
|
|
||||||
|
// Create copy of original table
|
||||||
|
// Note this will copy any derived/overriden functions that this specific addon has.
|
||||||
|
// Note: currently there are 73 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||||
|
this.modifiedVirtualTable = (AtkUnitBase.AtkUnitBaseVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||||
|
NativeMemory.Copy(addon->VirtualTable, this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
|
||||||
|
// Overwrite the addons existing virtual table with our own
|
||||||
|
addon->VirtualTable = this.modifiedVirtualTable;
|
||||||
|
|
||||||
|
// Pin each of our listener functions
|
||||||
|
this.destructorFunction = this.OnAddonDestructor;
|
||||||
|
this.onSetupFunction = this.OnAddonSetup;
|
||||||
|
this.finalizerFunction = this.OnAddonFinalize;
|
||||||
|
this.drawFunction = this.OnAddonDraw;
|
||||||
|
this.updateFunction = this.OnAddonUpdate;
|
||||||
|
this.onRefreshFunction = this.OnAddonRefresh;
|
||||||
|
this.onRequestedUpdateFunction = this.OnRequestedUpdate;
|
||||||
|
this.onReceiveEventFunction = this.OnAddonReceiveEvent;
|
||||||
|
this.openFunction = this.OnAddonOpen;
|
||||||
|
this.closeFunction = this.OnAddonClose;
|
||||||
|
this.showFunction = this.OnAddonShow;
|
||||||
|
this.hideFunction = this.OnAddonHide;
|
||||||
|
this.onMoveFunction = this.OnAddonMove;
|
||||||
|
this.onMouseOverFunction = this.OnAddonMouseOver;
|
||||||
|
this.onMouseOutFunction = this.OnAddonMouseOut;
|
||||||
|
this.focusFunction = this.OnAddonFocus;
|
||||||
|
|
||||||
|
// Overwrite specific virtual table entries
|
||||||
|
this.modifiedVirtualTable->Dtor = (delegate* unmanaged<AtkUnitBase*, byte, AtkEventListener*>)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
|
||||||
|
this.modifiedVirtualTable->OnSetup = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, void>)Marshal.GetFunctionPointerForDelegate(this.onSetupFunction);
|
||||||
|
this.modifiedVirtualTable->Finalizer = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.finalizerFunction);
|
||||||
|
this.modifiedVirtualTable->Draw = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.drawFunction);
|
||||||
|
this.modifiedVirtualTable->Update = (delegate* unmanaged<AtkUnitBase*, float, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||||
|
this.modifiedVirtualTable->OnRefresh = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, bool>)Marshal.GetFunctionPointerForDelegate(this.onRefreshFunction);
|
||||||
|
this.modifiedVirtualTable->OnRequestedUpdate = (delegate* unmanaged<AtkUnitBase*, NumberArrayData**, StringArrayData**, void>)Marshal.GetFunctionPointerForDelegate(this.onRequestedUpdateFunction);
|
||||||
|
this.modifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AtkUnitBase*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.onReceiveEventFunction);
|
||||||
|
this.modifiedVirtualTable->Open = (delegate* unmanaged<AtkUnitBase*, uint, bool>)Marshal.GetFunctionPointerForDelegate(this.openFunction);
|
||||||
|
this.modifiedVirtualTable->Close = (delegate* unmanaged<AtkUnitBase*, bool, bool>)Marshal.GetFunctionPointerForDelegate(this.closeFunction);
|
||||||
|
this.modifiedVirtualTable->Show = (delegate* unmanaged<AtkUnitBase*, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||||
|
this.modifiedVirtualTable->Hide = (delegate* unmanaged<AtkUnitBase*, bool, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||||
|
this.modifiedVirtualTable->OnMove = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMoveFunction);
|
||||||
|
this.modifiedVirtualTable->OnMouseOver = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
|
||||||
|
this.modifiedVirtualTable->OnMouseOut = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
|
||||||
|
this.modifiedVirtualTable->Focus = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Ensure restoration is done atomically.
|
||||||
|
Interlocked.Exchange(ref *(nint*)&this.atkUnitBase->VirtualTable, (nint)this.originalVirtualTable);
|
||||||
|
IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkEventListener* OnAddonDestructor(AtkUnitBase* thisPtr, byte freeFlags)
|
||||||
|
{
|
||||||
|
AtkEventListener* result = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->Dtor(thisPtr, freeFlags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Dtor. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((freeFlags & 1) == 1)
|
||||||
|
{
|
||||||
|
IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
AddonLifecycle.AllocatedTables.Remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDestructor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.setupArgs.Addon = addon;
|
||||||
|
this.setupArgs.AtkValueCount = valueCount;
|
||||||
|
this.setupArgs.AtkValues = (nint)values;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreSetup, this.setupArgs);
|
||||||
|
|
||||||
|
valueCount = this.setupArgs.AtkValueCount;
|
||||||
|
values = (AtkValue*)this.setupArgs.AtkValues;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnSetup(addon, valueCount, values);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnSetup. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostSetup, this.setupArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonSetup.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonFinalize(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.finalizeArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFinalize, this.finalizeArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Finalizer(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Finalizer. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFinalize.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonDraw(AtkUnitBase* addon)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.drawArgs.Addon = addon;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreDraw, this.drawArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Draw(addon);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Draw. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostDraw, this.drawArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDraw.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.updateArgs.Addon = addon;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreUpdate, this.updateArgs);
|
||||||
|
|
||||||
|
// Note: Do not pass or allow manipulation of delta.
|
||||||
|
// It's realistically not something that should be needed.
|
||||||
|
// And even if someone does, they are encouraged to hook Update themselves.
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Update(addon, delta);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostUpdate, this.updateArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonUpdate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnAddonRefresh(AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.refreshArgs.Addon = addon;
|
||||||
|
this.refreshArgs.AtkValueCount = valueCount;
|
||||||
|
this.refreshArgs.AtkValues = (nint)values;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRefresh, this.refreshArgs);
|
||||||
|
|
||||||
|
valueCount = this.refreshArgs.AtkValueCount;
|
||||||
|
values = (AtkValue*)this.refreshArgs.AtkValues;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->OnRefresh(addon, valueCount, values);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnRefresh. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRefresh, this.refreshArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonRefresh.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.requestedUpdateArgs.Addon = addon;
|
||||||
|
this.requestedUpdateArgs.NumberArrayData = (nint)numberArrayData;
|
||||||
|
this.requestedUpdateArgs.StringArrayData = (nint)stringArrayData;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, this.requestedUpdateArgs);
|
||||||
|
|
||||||
|
numberArrayData = (NumberArrayData**)this.requestedUpdateArgs.NumberArrayData;
|
||||||
|
stringArrayData = (StringArrayData**)this.requestedUpdateArgs.StringArrayData;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.requestedUpdateArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnRequestedUpdate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.receiveEventArgs.Addon = (nint)addon;
|
||||||
|
this.receiveEventArgs.AtkEventType = (byte)eventType;
|
||||||
|
this.receiveEventArgs.EventParam = eventParam;
|
||||||
|
this.receiveEventArgs.AtkEvent = (IntPtr)atkEvent;
|
||||||
|
this.receiveEventArgs.AtkEventData = (nint)atkEventData;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||||
|
|
||||||
|
eventType = (AtkEventType)this.receiveEventArgs.AtkEventType;
|
||||||
|
eventParam = this.receiveEventArgs.EventParam;
|
||||||
|
atkEvent = (AtkEvent*)this.receiveEventArgs.AtkEvent;
|
||||||
|
atkEventData = (AtkEventData*)this.receiveEventArgs.AtkEventData;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->ReceiveEvent(addon, eventType, eventParam, atkEvent, atkEventData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonReceiveEvent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnAddonOpen(AtkUnitBase* thisPtr, uint depthLayer)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.openArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreOpen, this.openArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->Open(thisPtr, depthLayer);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Open. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostOpen, this.openArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonOpen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool OnAddonClose(AtkUnitBase* thisPtr, bool fireCallback)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.closeArgs.Addon = thisPtr;
|
||||||
|
this.closeArgs.FireCallback = fireCallback;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreClose, this.closeArgs);
|
||||||
|
|
||||||
|
fireCallback = this.closeArgs.FireCallback;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.originalVirtualTable->Close(thisPtr, fireCallback);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Close. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostClose, this.closeArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonClose.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonShow(AtkUnitBase* thisPtr, bool silenceOpenSoundEffect, uint unsetShowHideFlags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.showArgs.Addon = thisPtr;
|
||||||
|
this.showArgs.SilenceOpenSoundEffect = silenceOpenSoundEffect;
|
||||||
|
this.showArgs.UnsetShowHideFlags = unsetShowHideFlags;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreShow, this.showArgs);
|
||||||
|
|
||||||
|
silenceOpenSoundEffect = this.showArgs.SilenceOpenSoundEffect;
|
||||||
|
unsetShowHideFlags = this.showArgs.UnsetShowHideFlags;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Show(thisPtr, silenceOpenSoundEffect, unsetShowHideFlags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostShow, this.showArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonShow.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonHide(AtkUnitBase* thisPtr, bool unkBool, bool callHideCallback, uint setShowHideFlags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.hideArgs.Addon = thisPtr;
|
||||||
|
this.hideArgs.UnknownBool = unkBool;
|
||||||
|
this.hideArgs.CallHideCallback = callHideCallback;
|
||||||
|
this.hideArgs.SetShowHideFlags = setShowHideFlags;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreHide, this.hideArgs);
|
||||||
|
|
||||||
|
unkBool = this.hideArgs.UnknownBool;
|
||||||
|
callHideCallback = this.hideArgs.CallHideCallback;
|
||||||
|
setShowHideFlags = this.hideArgs.SetShowHideFlags;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Hide(thisPtr, unkBool, callHideCallback, setShowHideFlags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostHide, this.hideArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonHide.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonMove(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.onMoveArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMove, this.onMoveArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnMove(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnMove. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMove, this.onMoveArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMove.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonMouseOver(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.onMouseOverArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOver, this.onMouseOverArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnMouseOver(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnMouseOver. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOver, this.onMouseOverArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOver.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonMouseOut(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.onMouseOutArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOut, this.onMouseOutArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->OnMouseOut(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnMouseOut. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOut, this.onMouseOutArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOut.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonFocus(AtkUnitBase* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.focusArgs.Addon = thisPtr;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocus, this.focusArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.originalVirtualTable->Focus(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Focus. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocus, this.focusArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocus.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
if (loggingEnabled)
|
||||||
|
{
|
||||||
|
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
|
||||||
|
if (caller is "OnAddonUpdate" or "OnAddonDraw" or "OnAddonReceiveEvent" or "OnRequestedUpdate")
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Debug($"[{caller}]: {this.atkUnitBase->NameString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ internal partial class ChatHandlers : IServiceType
|
||||||
|
|
||||||
if (this.configuration.PrintDalamudWelcomeMsg)
|
if (this.configuration.PrintDalamudWelcomeMsg)
|
||||||
{
|
{
|
||||||
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud {0} loaded."), Util.GetScmVersion())
|
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud {0} loaded."), Versioning.GetScmVersion())
|
||||||
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
|
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ internal partial class ChatHandlers : IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Util.AssemblyVersion.StartsWith(this.configuration.LastVersion))
|
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Versioning.GetAssemblyVersion().StartsWith(this.configuration.LastVersion))
|
||||||
{
|
{
|
||||||
var linkPayload = chatGui.AddChatLinkHandler(
|
var linkPayload = chatGui.AddChatLinkHandler(
|
||||||
(_, _) => dalamudInterface.OpenPluginInstallerTo(PluginInstallerOpenKind.Changelogs));
|
(_, _) => dalamudInterface.OpenPluginInstallerTo(PluginInstallerOpenKind.Changelogs));
|
||||||
|
|
@ -137,7 +137,7 @@ internal partial class ChatHandlers : IServiceType
|
||||||
Type = XivChatType.Notice,
|
Type = XivChatType.Notice,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.configuration.LastVersion = Util.AssemblyVersion;
|
this.configuration.LastVersion = Versioning.GetAssemblyVersion();
|
||||||
this.configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,47 +63,37 @@ public interface IAetheryteEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class representing an aetheryte entry available to the game.
|
/// This struct represents an aetheryte entry available to the game.
|
||||||
/// </summary>
|
|
||||||
internal sealed class AetheryteEntry : IAetheryteEntry
|
|
||||||
{
|
|
||||||
private readonly TeleportInfo data;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">Data read from the Aetheryte List.</param>
|
/// <param name="data">Data read from the Aetheryte List.</param>
|
||||||
internal AetheryteEntry(TeleportInfo data)
|
internal readonly struct AetheryteEntry(TeleportInfo data) : IAetheryteEntry
|
||||||
{
|
{
|
||||||
this.data = data;
|
/// <inheritdoc />
|
||||||
}
|
public uint AetheryteId => data.AetheryteId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint AetheryteId => this.data.AetheryteId;
|
public uint TerritoryId => data.TerritoryId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint TerritoryId => this.data.TerritoryId;
|
public byte SubIndex => data.SubIndex;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte SubIndex => this.data.SubIndex;
|
public byte Ward => data.Ward;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte Ward => this.data.Ward;
|
public byte Plot => data.Plot;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte Plot => this.data.Plot;
|
public uint GilCost => data.GilCost;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint GilCost => this.data.GilCost;
|
public bool IsFavourite => data.IsFavourite;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsFavourite => this.data.IsFavourite;
|
public bool IsSharedHouse => data.IsSharedHouse;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsSharedHouse => this.data.IsSharedHouse;
|
public bool IsApartment => data.IsApartment;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsApartment => this.data.IsApartment;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId);
|
public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId);
|
||||||
|
|
|
||||||
|
|
@ -88,10 +88,7 @@ internal sealed partial class AetheryteList
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IAetheryteEntry> GetEnumerator()
|
public IEnumerator<IAetheryteEntry> GetEnumerator()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
return new Enumerator(this);
|
||||||
{
|
|
||||||
yield return this[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -99,4 +96,34 @@ internal sealed partial class AetheryteList
|
||||||
{
|
{
|
||||||
return this.GetEnumerator();
|
return this.GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct Enumerator(AetheryteList aetheryteList) : IEnumerator<IAetheryteEntry>
|
||||||
|
{
|
||||||
|
private int index = -1;
|
||||||
|
|
||||||
|
public IAetheryteEntry Current { get; private set; }
|
||||||
|
|
||||||
|
object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (++this.index < aetheryteList.Length)
|
||||||
|
{
|
||||||
|
this.Current = aetheryteList[this.index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Current = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using CSBuddy = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy;
|
using CSBuddy = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy;
|
||||||
|
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
|
||||||
using CSUIState = FFXIVClientStructs.FFXIV.Client.Game.UI.UIState;
|
using CSUIState = FFXIVClientStructs.FFXIV.Client.Game.UI.UIState;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Buddy;
|
namespace Dalamud.Game.ClientState.Buddy;
|
||||||
|
|
@ -23,7 +24,7 @@ namespace Dalamud.Game.ClientState.Buddy;
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
internal sealed partial class BuddyList : IServiceType, IBuddyList
|
internal sealed partial class BuddyList : IServiceType, IBuddyList
|
||||||
{
|
{
|
||||||
private const uint InvalidObjectID = 0xE0000000;
|
private const uint InvalidEntityId = 0xE0000000;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||||
|
|
@ -84,37 +85,37 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IntPtr GetCompanionBuddyMemberAddress()
|
public unsafe nint GetCompanionBuddyMemberAddress()
|
||||||
{
|
{
|
||||||
return (IntPtr)this.BuddyListStruct->CompanionInfo.Companion;
|
return (nint)this.BuddyListStruct->CompanionInfo.Companion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IntPtr GetPetBuddyMemberAddress()
|
public unsafe nint GetPetBuddyMemberAddress()
|
||||||
{
|
{
|
||||||
return (IntPtr)this.BuddyListStruct->PetInfo.Pet;
|
return (nint)this.BuddyListStruct->PetInfo.Pet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
|
public unsafe nint GetBattleBuddyMemberAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= 3)
|
if (index < 0 || index >= 3)
|
||||||
return IntPtr.Zero;
|
return 0;
|
||||||
|
|
||||||
return (IntPtr)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
return (nint)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IBuddyMember? CreateBuddyMemberReference(IntPtr address)
|
public unsafe IBuddyMember? CreateBuddyMemberReference(nint address)
|
||||||
{
|
{
|
||||||
if (address == IntPtr.Zero)
|
if (address == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (!this.playerState.IsLoaded)
|
if (this.playerState.ContentId == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var buddy = new BuddyMember(address);
|
var buddy = new BuddyMember((CSBuddyMember*)address);
|
||||||
if (buddy.ObjectId == InvalidObjectID)
|
if (buddy.EntityId == InvalidEntityId)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return buddy;
|
return buddy;
|
||||||
|
|
@ -132,12 +133,39 @@ internal sealed partial class BuddyList
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IBuddyMember> GetEnumerator()
|
public IEnumerator<IBuddyMember> GetEnumerator()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
return new Enumerator(this);
|
||||||
{
|
|
||||||
yield return this[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
private struct Enumerator(BuddyList buddyList) : IEnumerator<IBuddyMember>
|
||||||
|
{
|
||||||
|
private int index = -1;
|
||||||
|
|
||||||
|
public IBuddyMember Current { get; private set; }
|
||||||
|
|
||||||
|
object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (++this.index < buddyList.Length)
|
||||||
|
{
|
||||||
|
this.Current = buddyList[this.index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Current = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
||||||
|
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Buddy;
|
namespace Dalamud.Game.ClientState.Buddy;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
/// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBuddyMember
|
public interface IBuddyMember : IEquatable<IBuddyMember>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the buddy in memory.
|
/// Gets the address of the buddy in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IntPtr Address { get; }
|
nint Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the object ID of this buddy.
|
/// Gets the object ID of this buddy.
|
||||||
|
|
@ -67,42 +71,34 @@ public interface IBuddyMember
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
/// This struct represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal unsafe class BuddyMember : IBuddyMember
|
/// <param name="ptr">A pointer to the BuddyMember.</param>
|
||||||
|
internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : IBuddyMember
|
||||||
{
|
{
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
public nint Address => (nint)ptr;
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Buddy address.</param>
|
|
||||||
internal BuddyMember(IntPtr address)
|
|
||||||
{
|
|
||||||
this.Address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IntPtr Address { get; }
|
public uint ObjectId => this.EntityId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint ObjectId => this.Struct->EntityId;
|
public uint EntityId => ptr->EntityId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint EntityId => this.Struct->EntityId;
|
public IGameObject? GameObject => this.objectTable.SearchById(this.EntityId);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
|
public uint CurrentHP => ptr->CurrentHealth;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint CurrentHP => this.Struct->CurrentHealth;
|
public uint MaxHP => ptr->MaxHealth;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint MaxHP => this.Struct->MaxHealth;
|
public uint DataID => ptr->DataId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public uint DataID => this.Struct->DataId;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID);
|
public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID);
|
||||||
|
|
@ -113,5 +109,25 @@ internal unsafe class BuddyMember : IBuddyMember
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(this.DataID);
|
public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(this.DataID);
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
|
public static bool operator ==(BuddyMember x, BuddyMember y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(BuddyMember x, BuddyMember y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(IBuddyMember? other)
|
||||||
|
{
|
||||||
|
return this.EntityId == other.EntityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is BuddyMember fate && this.Equals(fate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.EntityId.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState;
|
namespace Dalamud.Game.ClientState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
|
@ -7,10 +8,12 @@ using Dalamud.Memory;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
||||||
|
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Fates;
|
namespace Dalamud.Game.ClientState.Fates;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface representing an fate entry that can be seen in the current area.
|
/// Interface representing a fate entry that can be seen in the current area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IFate : IEquatable<IFate>
|
public interface IFate : IEquatable<IFate>
|
||||||
{
|
{
|
||||||
|
|
@ -112,129 +115,96 @@ public interface IFate : IEquatable<IFate>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of this Fate in memory.
|
/// Gets the address of this Fate in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IntPtr Address { get; }
|
nint Address { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents an FFXIV Fate.
|
/// This struct represents a Fate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal unsafe partial class Fate
|
/// <param name="ptr">A pointer to the FateContext.</param>
|
||||||
{
|
internal readonly unsafe struct Fate(CSFateContext* ptr) : IFate
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Fate"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of this fate in memory.</param>
|
|
||||||
internal Fate(IntPtr address)
|
|
||||||
{
|
|
||||||
this.Address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IntPtr Address { get; }
|
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
|
|
||||||
|
|
||||||
public static bool operator ==(Fate fate1, Fate fate2)
|
|
||||||
{
|
|
||||||
if (fate1 is null || fate2 is null)
|
|
||||||
return Equals(fate1, fate2);
|
|
||||||
|
|
||||||
return fate1.Equals(fate2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this Fate is still valid in memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fate">The fate to check.</param>
|
|
||||||
/// <returns>True or false.</returns>
|
|
||||||
public static bool IsValid(Fate fate)
|
|
||||||
{
|
|
||||||
if (fate == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var playerState = Service<PlayerState>.Get();
|
|
||||||
return playerState.IsLoaded == true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this actor is still valid in memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True or false.</returns>
|
|
||||||
public bool IsValid() => IsValid(this);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
bool IEquatable<IFate>.Equals(IFate other) => this.FateId == other?.FateId;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Equals(object obj) => ((IEquatable<IFate>)this).Equals(obj as IFate);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int GetHashCode() => this.FateId.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents an FFXIV Fate.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe partial class Fate : IFate
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ushort FateId => this.Struct->FateId;
|
public nint Address => (nint)ptr;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ushort FateId => ptr->FateId;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId);
|
public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
public int StartTimeEpoch => ptr->StartTimeEpoch;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public short Duration => this.Struct->Duration;
|
public short Duration => ptr->Duration;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
public SeString Name => MemoryHelper.ReadSeString(&ptr->Name);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description);
|
public SeString Description => MemoryHelper.ReadSeString(&ptr->Description);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective);
|
public SeString Objective => MemoryHelper.ReadSeString(&ptr->Objective);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public FateState State => (FateState)this.Struct->State;
|
public FateState State => (FateState)ptr->State;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte HandInCount => this.Struct->HandInCount;
|
public byte HandInCount => ptr->HandInCount;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Progress => this.Struct->Progress;
|
public byte Progress => ptr->Progress;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool HasBonus => this.Struct->IsBonus;
|
public bool HasBonus => ptr->IsBonus;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint IconId => this.Struct->IconId;
|
public uint IconId => ptr->IconId;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Level => this.Struct->Level;
|
public byte Level => ptr->Level;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte MaxLevel => this.Struct->MaxLevel;
|
public byte MaxLevel => ptr->MaxLevel;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Vector3 Position => this.Struct->Location;
|
public Vector3 Position => ptr->Location;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public float Radius => this.Struct->Radius;
|
public float Radius => ptr->Radius;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint MapIconId => this.Struct->MapIconId;
|
public uint MapIconId => ptr->MapIconId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->MapMarkers[0].MapMarkerData.TerritoryTypeId);
|
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->MapMarkers[0].MapMarkerData.TerritoryTypeId);
|
||||||
|
|
||||||
|
public static bool operator ==(Fate x, Fate y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(Fate x, Fate y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(IFate? other)
|
||||||
|
{
|
||||||
|
return this.FateId == other.FateId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is Fate fate && this.Equals(fate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.FateId.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
|
||||||
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
|
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Fates;
|
namespace Dalamud.Game.ClientState.Fates;
|
||||||
|
|
@ -26,7 +27,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IntPtr Address => (nint)CSFateManager.Instance();
|
public unsafe nint Address => (nint)CSFateManager.Instance();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe int Length
|
public unsafe int Length
|
||||||
|
|
@ -69,29 +70,29 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IntPtr GetFateAddress(int index)
|
public unsafe nint GetFateAddress(int index)
|
||||||
{
|
{
|
||||||
if (index >= this.Length)
|
if (index >= this.Length)
|
||||||
return IntPtr.Zero;
|
return 0;
|
||||||
|
|
||||||
var fateManager = CSFateManager.Instance();
|
var fateManager = CSFateManager.Instance();
|
||||||
if (fateManager == null)
|
if (fateManager == null)
|
||||||
return IntPtr.Zero;
|
return 0;
|
||||||
|
|
||||||
return (IntPtr)fateManager->Fates[index].Value;
|
return (nint)fateManager->Fates[index].Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IFate? CreateFateReference(IntPtr offset)
|
public unsafe IFate? CreateFateReference(IntPtr address)
|
||||||
{
|
{
|
||||||
if (offset == IntPtr.Zero)
|
if (address == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var playerState = Service<PlayerState>.Get();
|
var clientState = Service<ClientState>.Get();
|
||||||
if (!playerState.IsLoaded)
|
if (clientState.LocalContentId == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new Fate(offset);
|
return new Fate((CSFateContext*)address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,12 +107,39 @@ internal sealed partial class FateTable
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IFate> GetEnumerator()
|
public IEnumerator<IFate> GetEnumerator()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
return new Enumerator(this);
|
||||||
{
|
|
||||||
yield return this[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
private struct Enumerator(FateTable fateTable) : IEnumerator<IFate>
|
||||||
|
{
|
||||||
|
private int index = -1;
|
||||||
|
|
||||||
|
public IFate Current { get; private set; }
|
||||||
|
|
||||||
|
object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (++this.index < fateTable.Length)
|
||||||
|
{
|
||||||
|
this.Current = fateTable[this.index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Current = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.Interop;
|
using FFXIVClientStructs.Interop;
|
||||||
|
|
||||||
using Microsoft.Extensions.ObjectPool;
|
|
||||||
|
|
||||||
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||||
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager;
|
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager;
|
||||||
|
|
||||||
|
|
@ -37,8 +35,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||||
|
|
||||||
private readonly CachedEntry[] cachedObjectTable;
|
private readonly CachedEntry[] cachedObjectTable;
|
||||||
|
|
||||||
private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4];
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private unsafe ObjectTable()
|
private unsafe ObjectTable()
|
||||||
{
|
{
|
||||||
|
|
@ -48,9 +44,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||||
this.cachedObjectTable = new CachedEntry[objectTableLength];
|
this.cachedObjectTable = new CachedEntry[objectTableLength];
|
||||||
for (var i = 0; i < this.cachedObjectTable.Length; i++)
|
for (var i = 0; i < this.cachedObjectTable.Length; i++)
|
||||||
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
|
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
|
||||||
|
|
||||||
for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++)
|
|
||||||
this.frameworkThreadEnumerators[i] = new(this, i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -243,43 +236,25 @@ internal sealed partial class ObjectTable
|
||||||
public IEnumerator<IGameObject> GetEnumerator()
|
public IEnumerator<IGameObject> GetEnumerator()
|
||||||
{
|
{
|
||||||
ThreadSafety.AssertMainThread();
|
ThreadSafety.AssertMainThread();
|
||||||
|
return new Enumerator(this);
|
||||||
// If we're on the framework thread, see if there's an already allocated enumerator available for use.
|
|
||||||
foreach (ref var x in this.frameworkThreadEnumerators.AsSpan())
|
|
||||||
{
|
|
||||||
if (x is not null)
|
|
||||||
{
|
|
||||||
var t = x;
|
|
||||||
x = null;
|
|
||||||
t.Reset();
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No reusable enumerator is available; allocate a new temporary one.
|
|
||||||
return new Enumerator(this, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator<IGameObject>, IResettable
|
private struct Enumerator(ObjectTable owner) : IEnumerator<IGameObject>
|
||||||
{
|
{
|
||||||
private ObjectTable? owner = owner;
|
|
||||||
|
|
||||||
private int index = -1;
|
private int index = -1;
|
||||||
|
|
||||||
public IGameObject Current { get; private set; } = null!;
|
public IGameObject Current { get; private set; }
|
||||||
|
|
||||||
object IEnumerator.Current => this.Current;
|
object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
if (this.index == objectTableLength)
|
var cache = owner.cachedObjectTable.AsSpan();
|
||||||
return false;
|
|
||||||
|
|
||||||
var cache = this.owner!.cachedObjectTable.AsSpan();
|
while (++this.index < objectTableLength)
|
||||||
for (this.index++; this.index < objectTableLength; this.index++)
|
|
||||||
{
|
{
|
||||||
if (cache[this.index].Update() is { } ao)
|
if (cache[this.index].Update() is { } ao)
|
||||||
{
|
{
|
||||||
|
|
@ -288,24 +263,17 @@ internal sealed partial class ObjectTable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.Current = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset() => this.index = -1;
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (this.owner is not { } o)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (slotId != -1)
|
|
||||||
o.frameworkThreadEnumerators[slotId] = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryReset()
|
|
||||||
{
|
|
||||||
this.Reset();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
|
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
|
||||||
|
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Party;
|
namespace Dalamud.Game.ClientState.Party;
|
||||||
|
|
||||||
|
|
@ -43,20 +44,20 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||||
public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
|
public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance();
|
public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
public nint GroupListAddress => (nint)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr AllianceListAddress => (IntPtr)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public long PartyId => this.GroupManagerStruct->MainGroup.PartyId;
|
public long PartyId => this.GroupManagerStruct->MainGroup.PartyId;
|
||||||
|
|
||||||
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
|
private static int PartyMemberSize { get; } = Marshal.SizeOf<CSPartyMember>();
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
|
private CSGroupManager* GroupManagerStruct => (CSGroupManager*)this.GroupManagerAddress;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IPartyMember? this[int index]
|
public IPartyMember? this[int index]
|
||||||
|
|
@ -81,39 +82,45 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr GetPartyMemberAddress(int index)
|
public nint GetPartyMemberAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= GroupLength)
|
if (index < 0 || index >= GroupLength)
|
||||||
return IntPtr.Zero;
|
return 0;
|
||||||
|
|
||||||
return this.GroupListAddress + (index * PartyMemberSize);
|
return this.GroupListAddress + (index * PartyMemberSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IPartyMember? CreatePartyMemberReference(IntPtr address)
|
public IPartyMember? CreatePartyMemberReference(nint address)
|
||||||
{
|
{
|
||||||
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
|
if (this.playerState.ContentId == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new PartyMember(address);
|
if (address == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PartyMember((CSPartyMember*)address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr GetAllianceMemberAddress(int index)
|
public nint GetAllianceMemberAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= AllianceLength)
|
if (index < 0 || index >= AllianceLength)
|
||||||
return IntPtr.Zero;
|
return 0;
|
||||||
|
|
||||||
return this.AllianceListAddress + (index * PartyMemberSize);
|
return this.AllianceListAddress + (index * PartyMemberSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IPartyMember? CreateAllianceMemberReference(IntPtr address)
|
public IPartyMember? CreateAllianceMemberReference(nint address)
|
||||||
{
|
{
|
||||||
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
|
if (this.playerState.ContentId == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new PartyMember(address);
|
if (address == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PartyMember((CSPartyMember*)address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,18 +135,43 @@ internal sealed partial class PartyList
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IPartyMember> GetEnumerator()
|
public IEnumerator<IPartyMember> GetEnumerator()
|
||||||
{
|
{
|
||||||
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
return new Enumerator(this);
|
||||||
for (var i = 0; i < this.Length; i++)
|
|
||||||
{
|
|
||||||
var member = this[i];
|
|
||||||
|
|
||||||
if (member == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
yield return member;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
private struct Enumerator(PartyList partyList) : IEnumerator<IPartyMember>
|
||||||
|
{
|
||||||
|
private int index = -1;
|
||||||
|
|
||||||
|
public IPartyMember Current { get; private set; }
|
||||||
|
|
||||||
|
object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
while (++this.index < partyList.Length)
|
||||||
|
{
|
||||||
|
var partyMember = partyList[this.index];
|
||||||
|
if (partyMember != null)
|
||||||
|
{
|
||||||
|
this.Current = partyMember;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Current = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Statuses;
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
||||||
|
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Party;
|
namespace Dalamud.Game.ClientState.Party;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface representing a party member.
|
/// Interface representing a party member.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPartyMember
|
public interface IPartyMember : IEquatable<IPartyMember>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of this party member in memory.
|
/// Gets the address of this party member in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IntPtr Address { get; }
|
nint Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of buffs or debuffs applied to this party member.
|
/// Gets a list of buffs or debuffs applied to this party member.
|
||||||
|
|
@ -108,69 +109,81 @@ public interface IPartyMember
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a party member in the group manager.
|
/// This struct represents a party member in the group manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal unsafe class PartyMember : IPartyMember
|
/// <param name="ptr">A pointer to the PartyMember.</param>
|
||||||
|
internal unsafe readonly struct PartyMember(CSPartyMember* ptr) : IPartyMember
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
public nint Address => (nint)ptr;
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the party member.</param>
|
|
||||||
internal PartyMember(IntPtr address)
|
|
||||||
{
|
|
||||||
this.Address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr Address { get; }
|
public StatusList Statuses => new(&ptr->StatusManager);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public StatusList Statuses => new(&this.Struct->StatusManager);
|
public Vector3 Position => ptr->Position;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Vector3 Position => this.Struct->Position;
|
public long ContentId => (long)ptr->ContentId;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public long ContentId => (long)this.Struct->ContentId;
|
public uint ObjectId => ptr->EntityId;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint ObjectId => this.Struct->EntityId;
|
public uint EntityId => ptr->EntityId;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public uint EntityId => this.Struct->EntityId;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.EntityId);
|
public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.EntityId);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint CurrentHP => this.Struct->CurrentHP;
|
public uint CurrentHP => ptr->CurrentHP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint MaxHP => this.Struct->MaxHP;
|
public uint MaxHP => ptr->MaxHP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ushort CurrentMP => this.Struct->CurrentMP;
|
public ushort CurrentMP => ptr->CurrentMP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ushort MaxMP => this.Struct->MaxMP;
|
public ushort MaxMP => ptr->MaxMP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryType);
|
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->TerritoryType);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(this.Struct->HomeWorld);
|
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(ptr->HomeWorld);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString Name => SeString.Parse(this.Struct->Name);
|
public SeString Name => SeString.Parse(ptr->Name);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Sex => this.Struct->Sex;
|
public byte Sex => ptr->Sex;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(this.Struct->ClassJob);
|
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(ptr->ClassJob);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Level => this.Struct->Level;
|
public byte Level => ptr->Level;
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
|
public static bool operator ==(PartyMember x, PartyMember y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(PartyMember x, PartyMember y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(IPartyMember? other)
|
||||||
|
{
|
||||||
|
return this.EntityId == other.EntityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is PartyMember fate && this.Equals(fate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.EntityId.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,49 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
||||||
|
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Statuses;
|
namespace Dalamud.Game.ClientState.Statuses;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents a status effect an actor is afflicted by.
|
/// Interface representing a status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class Status
|
public interface IStatus : IEquatable<IStatus>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Status"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Status address.</param>
|
|
||||||
internal Status(IntPtr address)
|
|
||||||
{
|
|
||||||
this.Address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the status in memory.
|
/// Gets the address of the status in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr Address { get; }
|
nint Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the status ID of this status.
|
/// Gets the status ID of this status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint StatusId => this.Struct->StatusId;
|
uint StatusId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the GameData associated with this status.
|
/// Gets the GameData associated with this status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(this.Struct->StatusId);
|
RowRef<Lumina.Excel.Sheets.Status> GameData { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parameter value of the status.
|
/// Gets the parameter value of the status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Param => this.Struct->Param;
|
ushort Param { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the stack count of this status.
|
|
||||||
/// Only valid if this is a non-food status.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete($"Replaced with {nameof(Param)}", true)]
|
|
||||||
public byte StackCount => (byte)this.Struct->Param;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the time remaining of this status.
|
/// Gets the time remaining of this status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float RemainingTime => this.Struct->RemainingTime;
|
float RemainingTime { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the source ID of this status.
|
/// Gets the source ID of this status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint SourceId => this.Struct->SourceObject.ObjectId;
|
uint SourceId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the source actor associated with this status.
|
/// Gets the source actor associated with this status.
|
||||||
|
|
@ -63,7 +51,55 @@ public unsafe class Status
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This iterates the actor table, it should be used with care.
|
/// This iterates the actor table, it should be used with care.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
IGameObject? SourceObject { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This struct represents a status effect an actor is afflicted by.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">A pointer to the Status.</param>
|
||||||
|
internal unsafe readonly struct Status(CSStatus* ptr) : IStatus
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public nint Address => (nint)ptr;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint StatusId => ptr->StatusId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(ptr->StatusId);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ushort Param => ptr->Param;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public float RemainingTime => ptr->RemainingTime;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint SourceId => ptr->SourceObject.ObjectId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
|
public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
|
public static bool operator ==(Status x, Status y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(Status x, Status y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(IStatus? other)
|
||||||
|
{
|
||||||
|
return this.StatusId == other.StatusId && this.SourceId == other.SourceId && this.Param == other.Param && this.RemainingTime == other.RemainingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is Status fate && this.Equals(fate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(this.StatusId, this.SourceId, this.Param, this.RemainingTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.Player;
|
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Statuses;
|
namespace Dalamud.Game.ClientState.Statuses;
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ public sealed unsafe partial class StatusList
|
||||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the status list.</param>
|
/// <param name="address">Address of the status list.</param>
|
||||||
internal StatusList(IntPtr address)
|
internal StatusList(nint address)
|
||||||
{
|
{
|
||||||
this.Address = address;
|
this.Address = address;
|
||||||
}
|
}
|
||||||
|
|
@ -26,14 +26,14 @@ public sealed unsafe partial class StatusList
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pointer">Pointer to the status list.</param>
|
/// <param name="pointer">Pointer to the status list.</param>
|
||||||
internal unsafe StatusList(void* pointer)
|
internal unsafe StatusList(void* pointer)
|
||||||
: this((IntPtr)pointer)
|
: this((nint)pointer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the status list in memory.
|
/// Gets the address of the status list in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr Address { get; }
|
public nint Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of status effect slots the actor has.
|
/// Gets the amount of status effect slots the actor has.
|
||||||
|
|
@ -49,7 +49,7 @@ public sealed unsafe partial class StatusList
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Status Index.</param>
|
/// <param name="index">Status Index.</param>
|
||||||
/// <returns>The status at the specified index.</returns>
|
/// <returns>The status at the specified index.</returns>
|
||||||
public Status? this[int index]
|
public IStatus? this[int index]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -66,7 +66,7 @@ public sealed unsafe partial class StatusList
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of the status list in memory.</param>
|
/// <param name="address">The address of the status list in memory.</param>
|
||||||
/// <returns>The status object containing the requested data.</returns>
|
/// <returns>The status object containing the requested data.</returns>
|
||||||
public static StatusList? CreateStatusListReference(IntPtr address)
|
public static StatusList? CreateStatusListReference(nint address)
|
||||||
{
|
{
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -74,8 +74,12 @@ public sealed unsafe partial class StatusList
|
||||||
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
|
||||||
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
// fake status lists can be generated. Since they aren't exposed as services, it's either
|
||||||
// here or somewhere else.
|
// here or somewhere else.
|
||||||
var playerState = Service<PlayerState>.Get();
|
var clientState = Service<ClientState>.Get();
|
||||||
if (!playerState.IsLoaded)
|
|
||||||
|
if (clientState.LocalContentId == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (address == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new StatusList(address);
|
return new StatusList(address);
|
||||||
|
|
@ -86,16 +90,15 @@ public sealed unsafe partial class StatusList
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of the status effect in memory.</param>
|
/// <param name="address">The address of the status effect in memory.</param>
|
||||||
/// <returns>The status object containing the requested data.</returns>
|
/// <returns>The status object containing the requested data.</returns>
|
||||||
public static Status? CreateStatusReference(IntPtr address)
|
public static IStatus? CreateStatusReference(nint address)
|
||||||
{
|
{
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var playerState = Service<PlayerState>.Get();
|
if (address == 0)
|
||||||
if (!playerState.IsLoaded)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new Status(address);
|
return new Status((CSStatus*)address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -103,22 +106,22 @@ public sealed unsafe partial class StatusList
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">The index of the status.</param>
|
/// <param name="index">The index of the status.</param>
|
||||||
/// <returns>The memory address of the status.</returns>
|
/// <returns>The memory address of the status.</returns>
|
||||||
public IntPtr GetStatusAddress(int index)
|
public nint GetStatusAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= this.Length)
|
if (index < 0 || index >= this.Length)
|
||||||
return IntPtr.Zero;
|
return 0;
|
||||||
|
|
||||||
return (IntPtr)Unsafe.AsPointer(ref this.Struct->Status[index]);
|
return (nint)Unsafe.AsPointer(ref this.Struct->Status[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the status effects an actor is afflicted by.
|
/// This collection represents the status effects an actor is afflicted by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollection
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int IReadOnlyCollection<Status>.Count => this.Length;
|
int IReadOnlyCollection<IStatus>.Count => this.Length;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int ICollection.Count => this.Length;
|
int ICollection.Count => this.Length;
|
||||||
|
|
@ -130,17 +133,9 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
|
||||||
object ICollection.SyncRoot => this;
|
object ICollection.SyncRoot => this;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<Status> GetEnumerator()
|
public IEnumerator<IStatus> GetEnumerator()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
return new Enumerator(this);
|
||||||
{
|
|
||||||
var status = this[i];
|
|
||||||
|
|
||||||
if (status == null || status.StatusId == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
yield return status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -155,4 +150,38 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct Enumerator(StatusList statusList) : IEnumerator<IStatus>
|
||||||
|
{
|
||||||
|
private int index = -1;
|
||||||
|
|
||||||
|
public IStatus Current { get; private set; }
|
||||||
|
|
||||||
|
object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
while (++this.index < statusList.Length)
|
||||||
|
{
|
||||||
|
var status = statusList[this.index];
|
||||||
|
if (status != null && status.StatusId != 0)
|
||||||
|
{
|
||||||
|
this.Current = status;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Current = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Native memory representation of a FFXIV status effect.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct StatusEffect
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The effect ID.
|
|
||||||
/// </summary>
|
|
||||||
public short EffectId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How many stacks are present.
|
|
||||||
/// </summary>
|
|
||||||
public byte StackCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Additional parameters.
|
|
||||||
/// </summary>
|
|
||||||
public byte Param;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The duration remaining.
|
|
||||||
/// </summary>
|
|
||||||
public float Duration;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ID of the actor that caused this effect.
|
|
||||||
/// </summary>
|
|
||||||
public int OwnerId;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
namespace Dalamud.Game.Config;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Config;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Game config system address resolver.
|
/// Game config system address resolver.
|
||||||
|
|
|
||||||
|
|
@ -4069,6 +4069,13 @@ public enum UiConfigOption
|
||||||
[GameConfigOption("GposePortraitRotateType", ConfigType.UInt)]
|
[GameConfigOption("GposePortraitRotateType", ConfigType.UInt)]
|
||||||
GposePortraitRotateType,
|
GposePortraitRotateType,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UiConfig option with the internal name GroupPosePortraitUnlockAspectLimit.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("GroupPosePortraitUnlockAspectLimit", ConfigType.UInt)]
|
||||||
|
GroupPosePortraitUnlockAspectLimit,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UiConfig option with the internal name LsListSortPriority.
|
/// UiConfig option with the internal name LsListSortPriority.
|
||||||
/// This option is a UInt.
|
/// This option is a UInt.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace Dalamud.Game.DutyState;
|
namespace Dalamud.Game.DutyState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[Api13ToDo("Maybe make this config scoped to internal name?")]
|
[Api14ToDo("Maybe make this config scoped to internal name?")]
|
||||||
public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false;
|
public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -92,34 +92,16 @@ public enum FlyTextKind : int
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IslandExp = 15,
|
IslandExp = 15,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use Dataset instead", true)]
|
|
||||||
Unknown16 = 16,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
|
/// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Dataset = 16,
|
Dataset = 16,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use Knowledge instead", true)]
|
|
||||||
Unknown17 = 17,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Knowledge = 17,
|
Knowledge = 17,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use PhantomExp instead", true)]
|
|
||||||
Unknown18 = 18,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui;
|
namespace Dalamud.Game.Gui;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.NamePlate;
|
namespace Dalamud.Game.Gui.NamePlate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -305,7 +305,8 @@ internal class GameInventory : IInternalDisposableService
|
||||||
private GameInventoryItem[] CreateItemsArray(int length)
|
private GameInventoryItem[] CreateItemsArray(int length)
|
||||||
{
|
{
|
||||||
var items = new GameInventoryItem[length];
|
var items = new GameInventoryItem[length];
|
||||||
items.Initialize();
|
foreach (ref var item in items.AsSpan())
|
||||||
|
item = new();
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network;
|
namespace Dalamud.Game.Network;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
namespace Dalamud.Game.Network.Internal;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Network.Internal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal address resolver for the network handlers.
|
/// Internal address resolver for the network handlers.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using Iced.Intel;
|
using Iced.Intel;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Globalization;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||||
using LSeString = Lumina.Text.SeString;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.Evaluator;
|
namespace Dalamud.Game.Text.Evaluator;
|
||||||
|
|
||||||
|
|
@ -71,9 +70,6 @@ public readonly struct SeStringParameter
|
||||||
|
|
||||||
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
|
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
|
||||||
|
|
||||||
[Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
|
|
||||||
public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData));
|
|
||||||
|
|
||||||
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
|
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(string value) => new(value);
|
||||||
|
|
|
||||||
|
|
@ -113,14 +113,6 @@ public class SeString
|
||||||
/// <returns>Equivalent SeString.</returns>
|
/// <returns>Equivalent SeString.</returns>
|
||||||
public static implicit operator SeString(string str) => new(new TextPayload(str));
|
public static implicit operator SeString(string str) => new(new TextPayload(str));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implicitly convert a string into a SeString containing a <see cref="TextPayload"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="str">string to convert.</param>
|
|
||||||
/// <returns>Equivalent SeString.</returns>
|
|
||||||
[Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
|
|
||||||
public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parse a binary game message into an SeString.
|
/// Parse a binary game message into an SeString.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1116:SplitParametersMustStartOnLineAfterDeclaration", Justification = "Reviewed.")]
|
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1116:SplitParametersMustStartOnLineAfterDeclaration", Justification = "Reviewed.")]
|
||||||
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "This would be nice, but a big refactor")]
|
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "This would be nice, but a big refactor")]
|
||||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:FileNameMustMatchTypeName", Justification = "I don't like this one so much")]
|
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:FileNameMustMatchTypeName", Justification = "I don't like this one so much")]
|
||||||
|
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1108:BlockStatementsMustNotContainEmbeddedComments", Justification = "I like having comments in blocks")]
|
||||||
|
|
||||||
// ImRAII stuff
|
// ImRAII stuff
|
||||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "Reviewed.", Scope = "namespaceanddescendants", Target = "Dalamud.Interface.Utility.Raii")]
|
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "Reviewed.", Scope = "namespaceanddescendants", Target = "Dalamud.Interface.Utility.Raii")]
|
||||||
|
|
|
||||||
|
|
@ -201,19 +201,19 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||||
useMinHook = true;
|
useMinHook = true;
|
||||||
|
|
||||||
using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
||||||
if (moduleHandle.IsInvalid)
|
if (moduleHandle.IsNull)
|
||||||
throw new Exception($"Could not get a handle to module {moduleName}");
|
throw new Exception($"Could not get a handle to module {moduleName}");
|
||||||
|
|
||||||
var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
var procAddress = Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
||||||
if (procAddress == IntPtr.Zero)
|
if (procAddress.IsNull)
|
||||||
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
||||||
|
|
||||||
procAddress = HookManager.FollowJmp(procAddress);
|
var address = HookManager.FollowJmp(procAddress.Value);
|
||||||
if (useMinHook)
|
if (useMinHook)
|
||||||
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
return new MinHookHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||||
else
|
else
|
||||||
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
return new ReloadedHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using Reloaded.Hooks.Definitions;
|
|
||||||
|
|
||||||
namespace Dalamud.Hooking.Internal;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a callsite hook. Only the specific address's instructions are replaced with this hook.
|
|
||||||
/// This is a destructive operation, no other callsite hooks can coexist at the same address.
|
|
||||||
///
|
|
||||||
/// There's no .Original for this hook type.
|
|
||||||
/// This is only intended for be for functions where the parameters provided allow you to invoke the original call.
|
|
||||||
///
|
|
||||||
/// This class was specifically added for hooking virtual function callsites.
|
|
||||||
/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
|
||||||
internal class CallHook<T> : IDalamudHook where T : Delegate
|
|
||||||
{
|
|
||||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
|
||||||
|
|
||||||
private T? detour;
|
|
||||||
private bool activated;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CallHook{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address of the instruction to replace.</param>
|
|
||||||
/// <param name="detour">Delegate to invoke.</param>
|
|
||||||
internal CallHook(nint address, T detour)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(detour);
|
|
||||||
|
|
||||||
this.detour = detour;
|
|
||||||
this.Address = address;
|
|
||||||
|
|
||||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
|
||||||
var code = new[]
|
|
||||||
{
|
|
||||||
"use64",
|
|
||||||
$"mov rax, 0x{detourPtr:X8}",
|
|
||||||
"call rax",
|
|
||||||
};
|
|
||||||
|
|
||||||
var opt = new AsmHookOptions
|
|
||||||
{
|
|
||||||
PreferRelativeJump = true,
|
|
||||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
|
||||||
MaxOpcodeSize = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the hook is enabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IntPtr Address { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string BackendName => "Reloaded AsmHook";
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool IsDisposed => this.detour == null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Enable()
|
|
||||||
{
|
|
||||||
if (!this.activated)
|
|
||||||
{
|
|
||||||
this.activated = true;
|
|
||||||
this.asmHook.Activate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.asmHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Disable()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a hook from the current process.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.asmHook.Disable();
|
|
||||||
this.detour = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -48,7 +48,7 @@ public abstract class Easing
|
||||||
/// Gets the current value of the animation, following unclamped logic.
|
/// Gets the current value of the animation, following unclamped logic.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)]
|
[Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)]
|
||||||
[Api13ToDo("Map this field to ValueClamped, probably.")]
|
[Api14ToDo("Map this field to ValueClamped, probably.")]
|
||||||
public double Value => this.ValueUnclamped;
|
public double Value => this.ValueUnclamped;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -64,9 +64,9 @@ public interface IObjectWithLocalizableName
|
||||||
var result = new Dictionary<string, string>((int)count);
|
var result = new Dictionary<string, string>((int)count);
|
||||||
for (var i = 0u; i < count; i++)
|
for (var i = 0u; i < count; i++)
|
||||||
{
|
{
|
||||||
fn->GetLocaleName(i, (ushort*)buf, maxStrLen).ThrowOnError();
|
fn->GetLocaleName(i, buf, maxStrLen).ThrowOnError();
|
||||||
var key = new string(buf);
|
var key = new string(buf);
|
||||||
fn->GetString(i, (ushort*)buf, maxStrLen).ThrowOnError();
|
fn->GetString(i, buf, maxStrLen).ThrowOnError();
|
||||||
var value = new string(buf);
|
var value = new string(buf);
|
||||||
result[key.ToLowerInvariant()] = value;
|
result[key.ToLowerInvariant()] = value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,8 @@ public sealed class SystemFontFamilyId : IFontFamilyId
|
||||||
|
|
||||||
var familyIndex = 0u;
|
var familyIndex = 0u;
|
||||||
BOOL exists = false;
|
BOOL exists = false;
|
||||||
fixed (void* pName = this.EnglishName)
|
fixed (char* pName = this.EnglishName)
|
||||||
sfc.Get()->FindFamilyName((ushort*)pName, &familyIndex, &exists).ThrowOnError();
|
sfc.Get()->FindFamilyName(pName, &familyIndex, &exists).ThrowOnError();
|
||||||
if (!exists)
|
if (!exists)
|
||||||
throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found.");
|
throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found.");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,8 @@ public sealed class SystemFontId : IFontId
|
||||||
|
|
||||||
var familyIndex = 0u;
|
var familyIndex = 0u;
|
||||||
BOOL exists = false;
|
BOOL exists = false;
|
||||||
fixed (void* name = this.Family.EnglishName)
|
fixed (char* name = this.Family.EnglishName)
|
||||||
sfc.Get()->FindFamilyName((ushort*)name, &familyIndex, &exists).ThrowOnError();
|
sfc.Get()->FindFamilyName(name, &familyIndex, &exists).ThrowOnError();
|
||||||
if (!exists)
|
if (!exists)
|
||||||
throw new FileNotFoundException($"Font \"{this.Family.EnglishName}\" not found.");
|
throw new FileNotFoundException($"Font \"{this.Family.EnglishName}\" not found.");
|
||||||
|
|
||||||
|
|
@ -151,7 +151,7 @@ public sealed class SystemFontId : IFontId
|
||||||
flocal.Get()->GetFilePathLengthFromKey(refKey, refKeySize, &pathSize).ThrowOnError();
|
flocal.Get()->GetFilePathLengthFromKey(refKey, refKeySize, &pathSize).ThrowOnError();
|
||||||
|
|
||||||
var path = stackalloc char[(int)pathSize + 1];
|
var path = stackalloc char[(int)pathSize + 1];
|
||||||
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, (ushort*)path, pathSize + 1).ThrowOnError();
|
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, path, pathSize + 1).ThrowOnError();
|
||||||
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
|
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,19 +104,19 @@ internal static unsafe class ReShadePeeler
|
||||||
fixed (byte* pfn5 = "glBegin"u8)
|
fixed (byte* pfn5 = "glBegin"u8)
|
||||||
fixed (byte* pfn6 = "vkCreateDevice"u8)
|
fixed (byte* pfn6 = "vkCreateDevice"u8)
|
||||||
{
|
{
|
||||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0)
|
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == null)
|
||||||
continue;
|
continue;
|
||||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0)
|
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == null)
|
||||||
continue;
|
continue;
|
||||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0)
|
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == null)
|
||||||
continue;
|
continue;
|
||||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0)
|
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == null)
|
||||||
continue;
|
continue;
|
||||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0)
|
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == null)
|
||||||
continue;
|
continue;
|
||||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0)
|
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == null)
|
||||||
continue;
|
continue;
|
||||||
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0)
|
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == null)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Console;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
|
@ -37,6 +38,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
private readonly WndProcDelegate wndProcDelegate;
|
private readonly WndProcDelegate wndProcDelegate;
|
||||||
private readonly nint platformNamePtr;
|
private readonly nint platformNamePtr;
|
||||||
|
|
||||||
|
private readonly IConsoleVariable<bool> cvLogMouseEvents;
|
||||||
|
|
||||||
private ViewportHandler viewportHandler;
|
private ViewportHandler viewportHandler;
|
||||||
|
|
||||||
private int mouseButtonsDown;
|
private int mouseButtonsDown;
|
||||||
|
|
@ -87,6 +90,11 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
this.cursors[(int)ImGuiMouseCursor.ResizeNwse] = LoadCursorW(default, IDC.IDC_SIZENWSE);
|
this.cursors[(int)ImGuiMouseCursor.ResizeNwse] = LoadCursorW(default, IDC.IDC_SIZENWSE);
|
||||||
this.cursors[(int)ImGuiMouseCursor.Hand] = LoadCursorW(default, IDC.IDC_HAND);
|
this.cursors[(int)ImGuiMouseCursor.Hand] = LoadCursorW(default, IDC.IDC_HAND);
|
||||||
this.cursors[(int)ImGuiMouseCursor.NotAllowed] = LoadCursorW(default, IDC.IDC_NO);
|
this.cursors[(int)ImGuiMouseCursor.NotAllowed] = LoadCursorW(default, IDC.IDC_NO);
|
||||||
|
|
||||||
|
this.cvLogMouseEvents = Service<ConsoleManager>.Get().AddVariable(
|
||||||
|
"imgui.log_mouse_events",
|
||||||
|
"Log mouse events to console for debugging",
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -267,11 +275,23 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_XBUTTONDOWN:
|
case WM.WM_XBUTTONDOWN:
|
||||||
case WM.WM_XBUTTONDBLCLK:
|
case WM.WM_XBUTTONDBLCLK:
|
||||||
{
|
{
|
||||||
|
if (this.cvLogMouseEvents.Value)
|
||||||
|
{
|
||||||
|
Log.Verbose(
|
||||||
|
"Handle MouseDown {Btn} WantCaptureMouse: {Want} mouseButtonsDown: {Down}",
|
||||||
|
GetButton(msg, wParam),
|
||||||
|
io.WantCaptureMouse,
|
||||||
|
this.mouseButtonsDown);
|
||||||
|
}
|
||||||
|
|
||||||
var button = GetButton(msg, wParam);
|
var button = GetButton(msg, wParam);
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
if (this.mouseButtonsDown == 0 && GetCapture() == nint.Zero)
|
if (this.mouseButtonsDown == 0 && GetCapture() == nint.Zero)
|
||||||
|
{
|
||||||
SetCapture(hWndCurrent);
|
SetCapture(hWndCurrent);
|
||||||
|
}
|
||||||
|
|
||||||
this.mouseButtonsDown |= 1 << button;
|
this.mouseButtonsDown |= 1 << button;
|
||||||
io.AddMouseButtonEvent(button, true);
|
io.AddMouseButtonEvent(button, true);
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
|
|
@ -288,12 +308,28 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_MBUTTONUP:
|
case WM.WM_MBUTTONUP:
|
||||||
case WM.WM_XBUTTONUP:
|
case WM.WM_XBUTTONUP:
|
||||||
{
|
{
|
||||||
|
if (this.cvLogMouseEvents.Value)
|
||||||
|
{
|
||||||
|
Log.Verbose(
|
||||||
|
"Handle MouseUp {Btn} WantCaptureMouse: {Want} mouseButtonsDown: {Down}",
|
||||||
|
GetButton(msg, wParam),
|
||||||
|
io.WantCaptureMouse,
|
||||||
|
this.mouseButtonsDown);
|
||||||
|
}
|
||||||
|
|
||||||
var button = GetButton(msg, wParam);
|
var button = GetButton(msg, wParam);
|
||||||
if (io.WantCaptureMouse)
|
|
||||||
|
// Need to check if we captured the button event away from the game here, otherwise the game might get
|
||||||
|
// a down event but no up event, causing the cursor to get stuck.
|
||||||
|
// Can happen if WantCaptureMouse becomes true in between down and up
|
||||||
|
if (io.WantCaptureMouse && (this.mouseButtonsDown & (1 << button)) != 0)
|
||||||
{
|
{
|
||||||
this.mouseButtonsDown &= ~(1 << button);
|
this.mouseButtonsDown &= ~(1 << button);
|
||||||
if (this.mouseButtonsDown == 0 && GetCapture() == hWndCurrent)
|
if (this.mouseButtonsDown == 0 && GetCapture() == hWndCurrent)
|
||||||
|
{
|
||||||
ReleaseCapture();
|
ReleaseCapture();
|
||||||
|
}
|
||||||
|
|
||||||
io.AddMouseButtonEvent(button, false);
|
io.AddMouseButtonEvent(button, false);
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
@ -458,7 +494,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
// (This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same.)
|
// (This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same.)
|
||||||
var mousePos = mouseScreenPos;
|
var mousePos = mouseScreenPos;
|
||||||
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0)
|
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0)
|
||||||
ClientToScreen(focusedWindow, &mousePos);
|
{
|
||||||
|
// Use game window, otherwise, positions are calculated based on the focused window which might not be the game.
|
||||||
|
// Leads to offsets.
|
||||||
|
ClientToScreen(this.hWnd, &mousePos);
|
||||||
|
}
|
||||||
|
|
||||||
io.AddMousePosEvent(mousePos.x, mousePos.y);
|
io.AddMousePosEvent(mousePos.x, mousePos.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -672,7 +713,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND),
|
hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND),
|
||||||
lpfnWndProc = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal
|
lpfnWndProc = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal
|
||||||
.GetFunctionPointerForDelegate(this.input.wndProcDelegate),
|
.GetFunctionPointerForDelegate(this.input.wndProcDelegate),
|
||||||
lpszClassName = (ushort*)windowClassNamePtr,
|
lpszClassName = windowClassNamePtr,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (RegisterClassExW(&wcex) == 0)
|
if (RegisterClassExW(&wcex) == 0)
|
||||||
|
|
@ -701,7 +742,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
fixed (char* windowClassNamePtr = WindowClassName)
|
fixed (char* windowClassNamePtr = WindowClassName)
|
||||||
{
|
{
|
||||||
UnregisterClassW(
|
UnregisterClassW(
|
||||||
(ushort*)windowClassNamePtr,
|
windowClassNamePtr,
|
||||||
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module));
|
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -815,8 +856,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
{
|
{
|
||||||
data->Hwnd = CreateWindowExW(
|
data->Hwnd = CreateWindowExW(
|
||||||
(uint)data->DwExStyle,
|
(uint)data->DwExStyle,
|
||||||
(ushort*)windowClassNamePtr,
|
windowClassNamePtr,
|
||||||
(ushort*)windowClassNamePtr,
|
windowClassNamePtr,
|
||||||
(uint)data->DwStyle,
|
(uint)data->DwStyle,
|
||||||
rect.left,
|
rect.left,
|
||||||
rect.top,
|
rect.top,
|
||||||
|
|
@ -1030,7 +1071,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
{
|
{
|
||||||
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
|
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
|
||||||
fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title))
|
fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title))
|
||||||
SetWindowTextW(data->Hwnd, (ushort*)pwszTitle);
|
SetWindowTextW(data->Hwnd, pwszTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||||
|
|
|
||||||
|
|
@ -162,15 +162,14 @@ internal class SeStringRenderer : IServiceType
|
||||||
if (drawParams.Font.HasValue)
|
if (drawParams.Font.HasValue)
|
||||||
font = drawParams.Font.Value;
|
font = drawParams.Font.Value;
|
||||||
|
|
||||||
// API14: Remove commented out code
|
if (ThreadSafety.IsMainThread && drawParams.TargetDrawList is null && font is null)
|
||||||
if (ThreadSafety.IsMainThread /* && drawParams.TargetDrawList is null */ && font is null)
|
|
||||||
font = ImGui.GetFont();
|
font = ImGui.GetFont();
|
||||||
if (font is null)
|
if (font is null)
|
||||||
throw new ArgumentException("Specified font is empty.");
|
throw new ArgumentException("Specified font is empty.");
|
||||||
|
|
||||||
// This also does argument validation for drawParams. Do it here.
|
// This also does argument validation for drawParams. Do it here.
|
||||||
// `using var` makes a struct read-only, but we do want to modify it.
|
// `using var` makes a struct read-only, but we do want to modify it.
|
||||||
var stateStorage = new SeStringDrawState(
|
using var stateStorage = new SeStringDrawState(
|
||||||
sss,
|
sss,
|
||||||
drawParams,
|
drawParams,
|
||||||
ThreadSafety.IsMainThread ? this.colorStackSetMainThread : new(this.colorStackSetMainThread.ColorTypes),
|
ThreadSafety.IsMainThread ? this.colorStackSetMainThread : new(this.colorStackSetMainThread.ColorTypes),
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,9 @@ public record struct SeStringDrawParams
|
||||||
public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; set; }
|
public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets the screen offset of the left top corner.</summary>
|
/// <summary>Gets or sets the screen offset of the left top corner.</summary>
|
||||||
/// <value>Screen offset to draw at, or <c>null</c> to use <see cref="ImGui.GetCursorScreenPos()"/>.</value>
|
/// <value>Screen offset to draw at, or <c>null</c> to use <see cref="ImGui.GetCursorScreenPos()"/>, if no <see cref="TargetDrawList"/>
|
||||||
|
/// is specified. Otherwise, you must specify it (for example, by passing <see cref="ImGui.GetCursorScreenPos()"/> when passing the window
|
||||||
|
/// draw list.</value>
|
||||||
public Vector2? ScreenOffset { get; set; }
|
public Vector2? ScreenOffset { get; set; }
|
||||||
|
|
||||||
/// <summary>Gets or sets the font to use.</summary>
|
/// <summary>Gets or sets the font to use.</summary>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
|
@ -17,7 +18,7 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
|
|
||||||
/// <summary>Calculated values from <see cref="SeStringDrawParams"/> using ImGui styles.</summary>
|
/// <summary>Calculated values from <see cref="SeStringDrawParams"/> using ImGui styles.</summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public unsafe ref struct SeStringDrawState
|
public unsafe ref struct SeStringDrawState : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly int ChannelCount = Enum.GetValues<SeStringDrawChannel>().Length;
|
private static readonly int ChannelCount = Enum.GetValues<SeStringDrawChannel>().Length;
|
||||||
|
|
||||||
|
|
@ -63,18 +64,12 @@ public unsafe ref struct SeStringDrawState
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.drawList = ssdp.TargetDrawList.Value;
|
this.drawList = ssdp.TargetDrawList.Value;
|
||||||
this.ScreenOffset = Vector2.Zero;
|
this.ScreenOffset = ssdp.ScreenOffset ?? Vector2.Zero;
|
||||||
|
|
||||||
// API14: Remove, always throw
|
this.ScreenOffset = ssdp.ScreenOffset ?? throw new ArgumentException(
|
||||||
if (ThreadSafety.IsMainThread)
|
$"{nameof(ssdp.ScreenOffset)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state. (GetCursorScreenPos?)");
|
||||||
{
|
this.FontSize = ssdp.FontSize ?? throw new ArgumentException(
|
||||||
this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException(
|
|
||||||
$"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state.");
|
$"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state.");
|
||||||
}
|
|
||||||
|
|
||||||
// this.FontSize = ssdp.FontSize ?? throw new ArgumentException(
|
// this.FontSize = ssdp.FontSize ?? throw new ArgumentException(
|
||||||
// $"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state.");
|
// $"{nameof(ssdp.FontSize)} must be set when specifying a target draw list, as it cannot be fetched from the ImGui state.");
|
||||||
|
|
@ -88,7 +83,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
this.splitter = default;
|
this.splitter = default;
|
||||||
this.GetEntity = ssdp.GetEntity;
|
this.GetEntity = ssdp.GetEntity;
|
||||||
this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y));
|
this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y));
|
||||||
this.FontSizeScale = this.FontSize / this.Font->FontSize;
|
this.FontSizeScale = this.FontSize / this.Font.FontSize;
|
||||||
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
|
||||||
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
|
||||||
this.Opacity = ssdp.EffectiveOpacity;
|
this.Opacity = ssdp.EffectiveOpacity;
|
||||||
|
|
@ -118,7 +113,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
public Vector2 ScreenOffset { get; }
|
public Vector2 ScreenOffset { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
/// <inheritdoc cref="SeStringDrawParams.Font"/>
|
||||||
public ImFont* Font { get; }
|
public ImFontPtr Font { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
|
||||||
public float FontSize { get; }
|
public float FontSize { get; }
|
||||||
|
|
@ -193,6 +188,9 @@ public unsafe ref struct SeStringDrawState
|
||||||
/// <summary>Gets the text fragments.</summary>
|
/// <summary>Gets the text fragments.</summary>
|
||||||
internal List<TextFragment> Fragments { get; }
|
internal List<TextFragment> Fragments { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose() => this.splitter.ClearFreeMemory();
|
||||||
|
|
||||||
/// <summary>Sets the current channel in the ImGui draw list splitter.</summary>
|
/// <summary>Sets the current channel in the ImGui draw list splitter.</summary>
|
||||||
/// <param name="channelIndex">Channel to switch to.</param>
|
/// <param name="channelIndex">Channel to switch to.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -268,7 +266,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
|
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
|
||||||
internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset)
|
internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset)
|
||||||
{
|
{
|
||||||
var texId = this.Font->ContainerAtlas->Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
|
var texId = this.Font.ContainerAtlas.Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
|
||||||
var xy0 = new Vector2(
|
var xy0 = new Vector2(
|
||||||
MathF.Round(g.X0 * this.FontSizeScale),
|
MathF.Round(g.X0 * this.FontSizeScale),
|
||||||
MathF.Round(g.Y0 * this.FontSizeScale));
|
MathF.Round(g.Y0 * this.FontSizeScale));
|
||||||
|
|
@ -325,7 +323,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
|
|
||||||
offset += this.ScreenOffset;
|
offset += this.ScreenOffset;
|
||||||
offset.Y += (this.LinkUnderlineThickness - 1) / 2f;
|
offset.Y += (this.LinkUnderlineThickness - 1) / 2f;
|
||||||
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale));
|
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font.Ascent * this.FontSizeScale));
|
||||||
|
|
||||||
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
|
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
|
||||||
this.DrawList.AddLine(
|
this.DrawList.AddLine(
|
||||||
|
|
@ -352,9 +350,9 @@ public unsafe ref struct SeStringDrawState
|
||||||
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune)
|
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune)
|
||||||
{
|
{
|
||||||
var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue
|
var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue
|
||||||
? this.Font->FindGlyph((ushort)rune.Value)
|
? (ImFontGlyphPtr)this.Font.FindGlyph((ushort)rune.Value)
|
||||||
: this.Font->FallbackGlyph;
|
: this.Font.FallbackGlyph;
|
||||||
return ref *(ImGuiHelpers.ImFontGlyphReal*)p;
|
return ref *(ImGuiHelpers.ImFontGlyphReal*)p.Handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
|
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
|
||||||
|
|
@ -387,7 +385,7 @@ public unsafe ref struct SeStringDrawState
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return MathF.Round(
|
return MathF.Round(
|
||||||
this.Font->GetDistanceAdjustmentForPair(
|
this.Font.GetDistanceAdjustmentForPair(
|
||||||
(ushort)left.Value,
|
(ushort)left.Value,
|
||||||
(ushort)right.Value) * this.FontSizeScale);
|
(ushort)right.Value) * this.FontSizeScale);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -305,12 +305,12 @@ internal class DalamudCommands : IServiceType
|
||||||
|
|
||||||
chatGui.Print(new SeStringBuilder()
|
chatGui.Print(new SeStringBuilder()
|
||||||
.AddItalics("Dalamud:")
|
.AddItalics("Dalamud:")
|
||||||
.AddText($" {Util.GetScmVersion()}")
|
.AddText($" {Versioning.GetScmVersion()}")
|
||||||
.Build());
|
.Build());
|
||||||
|
|
||||||
chatGui.Print(new SeStringBuilder()
|
chatGui.Print(new SeStringBuilder()
|
||||||
.AddItalics("FFXIVCS:")
|
.AddItalics("FFXIVCS:")
|
||||||
.AddText($" {Util.GetGitHashClientStructs()}")
|
.AddText($" {Versioning.GetGitHashClientStructs()}")
|
||||||
.Build());
|
.Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ internal class DalamudInterface : IInternalDisposableService
|
||||||
() => Service<DalamudInterface>.GetNullable()?.ToggleDevMenu(),
|
() => Service<DalamudInterface>.GetNullable()?.ToggleDevMenu(),
|
||||||
VirtualKey.SHIFT);
|
VirtualKey.SHIFT);
|
||||||
|
|
||||||
if (Util.GetActiveTrack() != "release")
|
if (Versioning.GetActiveTrack() != "release")
|
||||||
{
|
{
|
||||||
titleScreenMenu.AddEntryCore(
|
titleScreenMenu.AddEntryCore(
|
||||||
Loc.Localize("TSMDalamudDevMenu", "Developer Menu"),
|
Loc.Localize("TSMDalamudDevMenu", "Developer Menu"),
|
||||||
|
|
@ -669,6 +669,8 @@ internal class DalamudInterface : IInternalDisposableService
|
||||||
{
|
{
|
||||||
using var barColor = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0.060f, 0.060f, 0.060f, 0.773f));
|
using var barColor = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0.060f, 0.060f, 0.060f, 0.773f));
|
||||||
barColor.Push(ImGuiCol.MenuBarBg, Vector4.Zero);
|
barColor.Push(ImGuiCol.MenuBarBg, Vector4.Zero);
|
||||||
|
barColor.Push(ImGuiCol.Border, Vector4.Zero);
|
||||||
|
barColor.Push(ImGuiCol.BorderShadow, Vector4.Zero);
|
||||||
if (ImGui.BeginMainMenuBar())
|
if (ImGui.BeginMainMenuBar())
|
||||||
{
|
{
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var pluginManager = Service<PluginManager>.Get();
|
||||||
|
|
@ -863,7 +865,7 @@ internal class DalamudInterface : IInternalDisposableService
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false, 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($"D: {Versioning.GetScmVersion()} CS: {Versioning.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false, false);
|
||||||
ImGui.MenuItem($"CLR: {Environment.Version}", false, false);
|
ImGui.MenuItem($"CLR: {Environment.Version}", false, false);
|
||||||
|
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
|
|
@ -1074,8 +1076,8 @@ internal class DalamudInterface : IInternalDisposableService
|
||||||
{
|
{
|
||||||
ImGui.PushFont(InterfaceManager.MonoFont);
|
ImGui.PushFont(InterfaceManager.MonoFont);
|
||||||
|
|
||||||
ImGui.BeginMenu($"{Util.GetActiveTrack() ?? "???"} on {Util.GetGitBranch() ?? "???"}", false);
|
ImGui.BeginMenu($"{Versioning.GetActiveTrack() ?? "???"} on {Versioning.GetGitBranch() ?? "???"}", false);
|
||||||
ImGui.BeginMenu($"{Util.GetScmVersion()}", false);
|
ImGui.BeginMenu($"{Versioning.GetScmVersion()}", false);
|
||||||
ImGui.BeginMenu(this.FrameCount.ToString("000000"), false);
|
ImGui.BeginMenu(this.FrameCount.ToString("000000"), false);
|
||||||
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
|
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
|
||||||
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
|
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ internal partial class InterfaceManager : IInternalDisposableService
|
||||||
var gwh = default(HWND);
|
var gwh = default(HWND);
|
||||||
fixed (char* pClass = "FFXIVGAME")
|
fixed (char* pClass = "FFXIVGAME")
|
||||||
{
|
{
|
||||||
while ((gwh = FindWindowExW(default, gwh, (ushort*)pClass, default)) != default)
|
while ((gwh = FindWindowExW(default, gwh, pClass, default)) != default)
|
||||||
{
|
{
|
||||||
uint pid;
|
uint pid;
|
||||||
_ = GetWindowThreadProcessId(gwh, &pid);
|
_ = GetWindowThreadProcessId(gwh, &pid);
|
||||||
|
|
|
||||||
|
|
@ -63,11 +63,11 @@ internal sealed unsafe partial class ReShadeAddonInterface
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res)
|
static bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res)
|
||||||
{
|
{
|
||||||
Span<byte> name8 = stackalloc byte[Encoding.UTF8.GetByteCount(name) + 1];
|
Span<byte> name8 = stackalloc byte[Encoding.UTF8.GetByteCount(name) + 1];
|
||||||
name8[Encoding.UTF8.GetBytes(name, name8)] = 0;
|
name8[Encoding.UTF8.GetBytes(name, name8)] = 0;
|
||||||
*(nint*)res = GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0]));
|
*(nint*)res = (nint)GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0]));
|
||||||
return *(nint*)res != 0;
|
return *(nint*)res != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +174,7 @@ internal sealed unsafe partial class ReShadeAddonInterface
|
||||||
CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE,
|
CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE,
|
||||||
CERT.CERT_NAME_ISSUER_FLAG,
|
CERT.CERT_NAME_ISSUER_FLAG,
|
||||||
null,
|
null,
|
||||||
(ushort*)Unsafe.AsPointer(ref issuerName[0]),
|
(char*)Unsafe.AsPointer(ref issuerName[0]),
|
||||||
pcb);
|
pcb);
|
||||||
if (pcb == 0)
|
if (pcb == 0)
|
||||||
throw new Win32Exception("CertGetNameStringW(2)");
|
throw new Win32Exception("CertGetNameStringW(2)");
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ internal static unsafe class ReShadeUnwrapper
|
||||||
static bool HasProcExported(ProcessModule m, ReadOnlySpan<byte> name)
|
static bool HasProcExported(ProcessModule m, ReadOnlySpan<byte> name)
|
||||||
{
|
{
|
||||||
fixed (byte* p = name)
|
fixed (byte* p = name)
|
||||||
return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != 0;
|
return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ internal partial class StaThreadService : IInternalDisposableService
|
||||||
lpfnWndProc = &MessageReceiverWndProcStatic,
|
lpfnWndProc = &MessageReceiverWndProcStatic,
|
||||||
hInstance = hInstance,
|
hInstance = hInstance,
|
||||||
hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1),
|
hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1),
|
||||||
lpszClassName = (ushort*)name,
|
lpszClassName = name,
|
||||||
};
|
};
|
||||||
|
|
||||||
wndClassAtom = RegisterClassExW(&wndClass);
|
wndClassAtom = RegisterClassExW(&wndClass);
|
||||||
|
|
@ -226,8 +226,8 @@ internal partial class StaThreadService : IInternalDisposableService
|
||||||
this.messageReceiverHwndTask.SetResult(
|
this.messageReceiverHwndTask.SetResult(
|
||||||
CreateWindowExW(
|
CreateWindowExW(
|
||||||
0,
|
0,
|
||||||
(ushort*)wndClassAtom,
|
(char*)wndClassAtom,
|
||||||
(ushort*)name,
|
name,
|
||||||
0,
|
0,
|
||||||
CW_USEDEFAULT,
|
CW_USEDEFAULT,
|
||||||
CW_USEDEFAULT,
|
CW_USEDEFAULT,
|
||||||
|
|
@ -275,7 +275,7 @@ internal partial class StaThreadService : IInternalDisposableService
|
||||||
_ = OleFlushClipboard();
|
_ = OleFlushClipboard();
|
||||||
OleUninitialize();
|
OleUninitialize();
|
||||||
if (wndClassAtom != 0)
|
if (wndClassAtom != 0)
|
||||||
UnregisterClassW((ushort*)wndClassAtom, hInstance);
|
UnregisterClassW((char*)wndClassAtom, hInstance);
|
||||||
this.messageReceiverHwndTask.TrySetException(e);
|
this.messageReceiverHwndTask.TrySetException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public class BranchSwitcherWindow : Window
|
||||||
this.branches = await client.GetFromJsonAsync<Dictionary<string, VersionEntry>>(BranchInfoUrl);
|
this.branches = await client.GetFromJsonAsync<Dictionary<string, VersionEntry>>(BranchInfoUrl);
|
||||||
Debug.Assert(this.branches != null, "this.branches != null");
|
Debug.Assert(this.branches != null, "this.branches != null");
|
||||||
|
|
||||||
var trackName = Util.GetActiveTrack();
|
var trackName = Versioning.GetActiveTrack();
|
||||||
this.selectedBranchIndex = this.branches.IndexOf(x => x.Value.Track == trackName);
|
this.selectedBranchIndex = this.branches.IndexOf(x => x.Value.Track == trackName);
|
||||||
if (this.selectedBranchIndex == -1)
|
if (this.selectedBranchIndex == -1)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
var pmWantsChangelog = pm?.InstalledPlugins.Any() ?? true;
|
var pmWantsChangelog = pm?.InstalledPlugins.Any() ?? true;
|
||||||
return (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) ||
|
return (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) ||
|
||||||
(!WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) &&
|
(!WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) &&
|
||||||
Util.AssemblyVersion.StartsWith(WarrantsChangelogForMajorMinor))) && pmWantsChangelog;
|
Versioning.GetAssemblyVersion().StartsWith(WarrantsChangelogForMajorMinor))) && pmWantsChangelog;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -357,7 +357,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
case State.WindowFadeIn:
|
case State.WindowFadeIn:
|
||||||
case State.ExplainerIntro:
|
case State.ExplainerIntro:
|
||||||
ImGui.TextWrapped($"Welcome to Dalamud v{Util.GetScmVersion()}!");
|
ImGui.TextWrapped($"Welcome to Dalamud v{Versioning.GetScmVersion()}!");
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
ImGui.TextWrapped(ChangeLog);
|
ImGui.TextWrapped(ChangeLog);
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
|
|
||||||
|
|
@ -48,97 +46,38 @@ public class AddonLifecycleWidget : IDataWindowWidget
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Listeners"u8))
|
foreach (var (eventType, addonListeners) in this.AddonLifecycle.EventListeners)
|
||||||
{
|
{
|
||||||
ImGui.Indent();
|
using var eventId = ImRaii.PushId(eventType.ToString());
|
||||||
this.DrawEventListeners();
|
|
||||||
ImGui.Unindent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("ReceiveEvent Hooks"u8))
|
|
||||||
{
|
|
||||||
ImGui.Indent();
|
|
||||||
this.DrawReceiveEventHooks();
|
|
||||||
ImGui.Unindent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawEventListeners()
|
|
||||||
{
|
|
||||||
if (!this.Ready) return;
|
|
||||||
|
|
||||||
foreach (var eventType in Enum.GetValues<AddonEvent>())
|
|
||||||
{
|
|
||||||
if (ImGui.CollapsingHeader(eventType.ToString()))
|
if (ImGui.CollapsingHeader(eventType.ToString()))
|
||||||
{
|
{
|
||||||
ImGui.Indent();
|
using var eventIndent = ImRaii.PushIndent();
|
||||||
var listeners = this.AddonLifecycle.EventListeners.Where(listener => listener.EventType == eventType).ToList();
|
|
||||||
|
if (addonListeners.Count == 0)
|
||||||
|
{
|
||||||
|
ImGui.Text("No Addons Registered for Event"u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (addonName, listeners) in addonListeners)
|
||||||
|
{
|
||||||
|
using var addonId = ImRaii.PushId(addonName);
|
||||||
|
|
||||||
|
if (ImGui.CollapsingHeader(addonName.IsNullOrEmpty() ? "GLOBAL" : addonName))
|
||||||
|
{
|
||||||
|
using var addonIndent = ImRaii.PushIndent();
|
||||||
|
|
||||||
if (listeners.Count == 0)
|
if (listeners.Count == 0)
|
||||||
{
|
{
|
||||||
ImGui.Text("No Listeners Registered for Event"u8);
|
ImGui.Text("No Listeners Registered for Event"u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTable("AddonLifecycleListenersTable"u8, 2))
|
|
||||||
{
|
|
||||||
ImGui.TableSetupColumn("##AddonName"u8, ImGuiTableColumnFlags.WidthFixed, 100.0f * ImGuiHelpers.GlobalScale);
|
|
||||||
ImGui.TableSetupColumn("##MethodInvoke"u8, ImGuiTableColumnFlags.WidthStretch);
|
|
||||||
|
|
||||||
foreach (var listener in listeners)
|
foreach (var listener in listeners)
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.Text(listener.AddonName is "" ? "GLOBAL" : listener.AddonName);
|
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}");
|
ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ImGui.EndTable();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Unindent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawReceiveEventHooks()
|
|
||||||
{
|
|
||||||
if (!this.Ready) return;
|
|
||||||
|
|
||||||
var listeners = this.AddonLifecycle.ReceiveEventListeners;
|
|
||||||
|
|
||||||
if (listeners.Count == 0)
|
|
||||||
{
|
|
||||||
ImGui.Text("No ReceiveEvent Hooks are Registered"u8);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var receiveEventListener in this.AddonLifecycle.ReceiveEventListeners)
|
|
||||||
{
|
|
||||||
if (ImGui.CollapsingHeader(string.Join(", ", receiveEventListener.AddonNames)))
|
|
||||||
{
|
|
||||||
ImGui.Columns(2);
|
|
||||||
|
|
||||||
var functionAddress = receiveEventListener.FunctionAddress;
|
|
||||||
|
|
||||||
ImGui.Text("Hook Address"u8);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
ImGui.Text($"0x{functionAddress:X} (ffxiv_dx11.exe+{functionAddress - Process.GetCurrentProcess().MainModule!.BaseAddress:X})");
|
|
||||||
|
|
||||||
ImGui.NextColumn();
|
|
||||||
ImGui.Text("Hook Status"u8);
|
|
||||||
ImGui.NextColumn();
|
|
||||||
if (receiveEventListener.Hook is null)
|
|
||||||
{
|
|
||||||
ImGui.Text("Hook is null"u8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var color = receiveEventListener.Hook.IsEnabled ? ImGuiColors.HealerGreen : ImGuiColors.DalamudRed;
|
|
||||||
var text = receiveEventListener.Hook.IsEnabled ? "Enabled"u8 : "Disabled"u8;
|
|
||||||
ImGui.TextColored(color, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Columns(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Addon.Lifecycle;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -34,7 +33,7 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
private MessageBoxWDelegate? messageBoxWOriginal;
|
private MessageBoxWDelegate? messageBoxWOriginal;
|
||||||
private AddonFinalizeDelegate? addonFinalizeOriginal;
|
private AddonFinalizeDelegate? addonFinalizeOriginal;
|
||||||
|
|
||||||
private AddonLifecycleAddressResolver? address;
|
private nint address;
|
||||||
|
|
||||||
private delegate int MessageBoxWDelegate(
|
private delegate int MessageBoxWDelegate(
|
||||||
IntPtr hWnd,
|
IntPtr hWnd,
|
||||||
|
|
@ -55,7 +54,7 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
public string DisplayName { get; init; } = "Hook";
|
public string DisplayName { get; init; } = "Hook";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string[]? CommandShortcuts { get; init; } = { "hook" };
|
public string[]? CommandShortcuts { get; init; } = ["hook"];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Ready { get; set; }
|
public bool Ready { get; set; }
|
||||||
|
|
@ -65,8 +64,8 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
this.Ready = true;
|
this.Ready = true;
|
||||||
|
|
||||||
this.address = new AddonLifecycleAddressResolver();
|
var sigScanner = Service<TargetSigScanner>.Get();
|
||||||
this.address.Setup(Service<TargetSigScanner>.Get());
|
this.address = sigScanner.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -224,7 +223,7 @@ internal unsafe class HookWidget : IDataWindowWidget
|
||||||
|
|
||||||
private IDalamudHook HookAddonFinalize()
|
private IDalamudHook HookAddonFinalize()
|
||||||
{
|
{
|
||||||
var hook = Hook<AddonFinalizeDelegate>.FromAddress(this.address!.AddonFinalize, this.OnAddonFinalize);
|
var hook = Hook<AddonFinalizeDelegate>.FromAddress(this.address, this.OnAddonFinalize);
|
||||||
|
|
||||||
this.addonFinalizeOriginal = hook.Original;
|
this.addonFinalizeOriginal = hook.Original;
|
||||||
hook.Enable();
|
hook.Enable();
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,20 @@ internal class PluginIpcWidget : IDataWindowWidget
|
||||||
|
|
||||||
this.ipcPub.RegisterAction(msg =>
|
this.ipcPub.RegisterAction(msg =>
|
||||||
{
|
{
|
||||||
Log.Information("Data action was called: {Msg}", msg);
|
Log.Information(
|
||||||
|
"Data action was called: {Msg}\n" +
|
||||||
|
" Context: {Context}",
|
||||||
|
msg,
|
||||||
|
this.ipcPub.GetContext());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ipcPub.RegisterFunc(msg =>
|
this.ipcPub.RegisterFunc(msg =>
|
||||||
{
|
{
|
||||||
Log.Information("Data func was called: {Msg}", msg);
|
Log.Information(
|
||||||
|
"Data func was called: {Msg}\n" +
|
||||||
|
" Context: {Context}",
|
||||||
|
msg,
|
||||||
|
this.ipcPub.GetContext());
|
||||||
return Guid.NewGuid().ToString();
|
return Guid.NewGuid().ToString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -61,14 +69,8 @@ internal class PluginIpcWidget : IDataWindowWidget
|
||||||
if (this.ipcSub == null)
|
if (this.ipcSub == null)
|
||||||
{
|
{
|
||||||
this.ipcSub = new CallGatePubSub<string, string>("dataDemo1");
|
this.ipcSub = new CallGatePubSub<string, string>("dataDemo1");
|
||||||
this.ipcSub.Subscribe(_ =>
|
this.ipcSub.Subscribe(_ => { Log.Information("PONG1"); });
|
||||||
{
|
this.ipcSub.Subscribe(_ => { Log.Information("PONG2"); });
|
||||||
Log.Information("PONG1");
|
|
||||||
});
|
|
||||||
this.ipcSub.Subscribe(_ =>
|
|
||||||
{
|
|
||||||
Log.Information("PONG2");
|
|
||||||
});
|
|
||||||
this.ipcSub.Subscribe(_ => throw new Exception("PONG3"));
|
this.ipcSub.Subscribe(_ => throw new Exception("PONG3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,12 +80,21 @@ internal class PluginIpcWidget : IDataWindowWidget
|
||||||
|
|
||||||
this.ipcPubGo.RegisterAction(go =>
|
this.ipcPubGo.RegisterAction(go =>
|
||||||
{
|
{
|
||||||
Log.Information("Data action was called: {Name}", go?.Name);
|
Log.Information(
|
||||||
|
"Data action was called: {Name}" +
|
||||||
|
"\n Context: {Context}",
|
||||||
|
go?.Name,
|
||||||
|
this.ipcPubGo.GetContext());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ipcPubGo.RegisterFunc(go =>
|
this.ipcPubGo.RegisterFunc(go =>
|
||||||
{
|
{
|
||||||
Log.Information("Data func was called: {Name}", go?.Name);
|
Log.Information(
|
||||||
|
"Data func was called: {Name}\n" +
|
||||||
|
" Context: {Context}",
|
||||||
|
go?.Name,
|
||||||
|
this.ipcPubGo.GetContext());
|
||||||
|
|
||||||
return "test";
|
return "test";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ internal class SeStringCreatorWidget : IDataWindowWidget
|
||||||
new TextEntry(TextEntryType.Macro, " <string(lstr1)>"),
|
new TextEntry(TextEntryType.Macro, " <string(lstr1)>"),
|
||||||
];
|
];
|
||||||
|
|
||||||
private SeStringParameter[]? localParameters = [Util.GetScmVersion()];
|
private SeStringParameter[]? localParameters = [Versioning.GetScmVersion()];
|
||||||
private ReadOnlySeString input;
|
private ReadOnlySeString input;
|
||||||
private ClientLanguage? language;
|
private ClientLanguage? language;
|
||||||
private Task? validImportSheetNamesTask;
|
private Task? validImportSheetNamesTask;
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,24 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
ImGuiHelpers.SeStringWrapped(this.logkind.Value.Data.Span, this.style);
|
ImGuiHelpers.SeStringWrapped(this.logkind.Value.Data.Span, this.style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.CollapsingHeader("Draw into drawlist"))
|
||||||
|
{
|
||||||
|
ImGuiHelpers.ScaledDummy(100);
|
||||||
|
ImGui.SetCursorScreenPos(ImGui.GetItemRectMin() + ImGui.GetStyle().FramePadding);
|
||||||
|
var clipMin = ImGui.GetItemRectMin() + ImGui.GetStyle().FramePadding;
|
||||||
|
var clipMax = ImGui.GetItemRectMax() - ImGui.GetStyle().FramePadding;
|
||||||
|
clipMin.Y = MathF.Max(clipMin.Y, ImGui.GetWindowPos().Y);
|
||||||
|
clipMax.Y = MathF.Min(clipMax.Y, ImGui.GetWindowPos().Y + ImGui.GetWindowHeight());
|
||||||
|
|
||||||
|
var dl = ImGui.GetWindowDrawList();
|
||||||
|
dl.PushClipRect(clipMin, clipMax);
|
||||||
|
ImGuiHelpers.CompileSeStringWrapped(
|
||||||
|
"<icon(1)>Test test<icon(1)>",
|
||||||
|
new SeStringDrawParams
|
||||||
|
{ Color = 0xFFFFFFFF, WrapWidth = float.MaxValue, TargetDrawList = dl });
|
||||||
|
dl.PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Addon Table"u8))
|
if (ImGui.CollapsingHeader("Addon Table"u8))
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTable("Addon Sheet"u8, 3))
|
if (ImGui.BeginTable("Addon Sheet"u8, 3))
|
||||||
|
|
|
||||||
|
|
@ -302,7 +302,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
|
|
||||||
this.profileManagerWidget.Reset();
|
this.profileManagerWidget.Reset();
|
||||||
|
|
||||||
if (this.staleDalamudNewVersion == null && !Util.GetActiveTrack().IsNullOrEmpty())
|
if (this.staleDalamudNewVersion == null && !Versioning.GetActiveTrack().IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
Service<DalamudReleases>.Get().GetVersionForCurrentTrack().ContinueWith(t =>
|
Service<DalamudReleases>.Get().GetVersionForCurrentTrack().ContinueWith(t =>
|
||||||
{
|
{
|
||||||
|
|
@ -310,7 +310,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var versionInfo = t.Result;
|
var versionInfo = t.Result;
|
||||||
if (versionInfo.AssemblyVersion != Util.GetScmVersion())
|
if (versionInfo.AssemblyVersion != Versioning.GetScmVersion())
|
||||||
{
|
{
|
||||||
this.staleDalamudNewVersion = versionInfo.AssemblyVersion;
|
this.staleDalamudNewVersion = versionInfo.AssemblyVersion;
|
||||||
}
|
}
|
||||||
|
|
@ -1670,7 +1670,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
DrawWarningIcon();
|
DrawWarningIcon();
|
||||||
DrawLinesCentered("A new version of Dalamud is available.\n" +
|
DrawLinesCentered("A new version of Dalamud is available.\n" +
|
||||||
"Please restart the game to ensure compatibility with updated plugins.\n" +
|
"Please restart the game to ensure compatibility with updated plugins.\n" +
|
||||||
$"old: {Util.GetScmVersion()} new: {this.staleDalamudNewVersion}");
|
$"old: {Versioning.GetScmVersion()} new: {this.staleDalamudNewVersion}");
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
}
|
}
|
||||||
|
|
@ -2461,7 +2461,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
var isOutdated = effectiveApiLevel < PluginManager.DalamudApiLevel;
|
var isOutdated = effectiveApiLevel < PluginManager.DalamudApiLevel;
|
||||||
|
|
||||||
var isIncompatible = manifest.MinimumDalamudVersion != null &&
|
var isIncompatible = manifest.MinimumDalamudVersion != null &&
|
||||||
manifest.MinimumDalamudVersion > Util.AssemblyVersionParsed;
|
manifest.MinimumDalamudVersion > Versioning.GetAssemblyVersionParsed();
|
||||||
|
|
||||||
var enableInstallButton = this.updateStatus != OperationStatus.InProgress &&
|
var enableInstallButton = this.updateStatus != OperationStatus.InProgress &&
|
||||||
this.installStatus != OperationStatus.InProgress &&
|
this.installStatus != OperationStatus.InProgress &&
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ Contribute at: https://github.com/goatcorp/Dalamud
|
||||||
.Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n")
|
.Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n")
|
||||||
.Aggregate(string.Empty, (current, next) => $"{current}{next}");
|
.Aggregate(string.Empty, (current, next) => $"{current}{next}");
|
||||||
|
|
||||||
this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits, Util.GetGitHashClientStructs());
|
this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits, Versioning.GetGitHashClientStructs());
|
||||||
|
|
||||||
var gameGui = Service<GameGui>.Get();
|
var gameGui = Service<GameGui>.Get();
|
||||||
var playerState = PlayerState.Instance();
|
var playerState = PlayerState.Instance();
|
||||||
|
|
|
||||||
|
|
@ -472,9 +472,9 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
|
|
||||||
private unsafe void OnVersionStringDraw(AddonEvent ev, AddonArgs args)
|
private unsafe void OnVersionStringDraw(AddonEvent ev, AddonArgs args)
|
||||||
{
|
{
|
||||||
if (args is not AddonDrawArgs drawArgs) return;
|
if (ev is not (AddonEvent.PostDraw or AddonEvent.PreDraw)) return;
|
||||||
|
|
||||||
var addon = drawArgs.Addon.Struct;
|
var addon = args.Addon.Struct;
|
||||||
var textNode = addon->GetTextNodeById(3);
|
var textNode = addon->GetTextNodeById(3);
|
||||||
|
|
||||||
// look and feel init. should be harmless to set.
|
// look and feel init. should be harmless to set.
|
||||||
|
|
@ -503,7 +503,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
lssb.PushEdgeColorType(701).PushColorType(539)
|
lssb.PushEdgeColorType(701).PushColorType(539)
|
||||||
.Append(SeIconChar.BoxedLetterD.ToIconChar())
|
.Append(SeIconChar.BoxedLetterD.ToIconChar())
|
||||||
.PopColorType().PopEdgeColorType();
|
.PopColorType().PopEdgeColorType();
|
||||||
lssb.Append($" Dalamud: {Util.GetScmVersion()}");
|
lssb.Append($" Dalamud: {Versioning.GetScmVersion()}");
|
||||||
|
|
||||||
lssb.Append($" - {count} {(count != 1 ? "plugins" : "plugin")} loaded");
|
lssb.Append($" - {count} {(count != 1 ? "plugins" : "plugin")} loaded");
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue