mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-25 01:49:18 +01:00
Compare commits
No commits in common. "master" and "13.0.0.13" have entirely different histories.
189 changed files with 3517 additions and 4174 deletions
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
|
|
@ -1,8 +1,9 @@
|
||||||
name: Build Dalamud
|
name: Build Dalamud
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
# Globally blocking because of git pushes in deploy step
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build_dalamud_${{ github.ref_name }}
|
group: build_dalamud_${{ github.repository_owner }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -23,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: '10.0.100'
|
dotnet-version: '9.0.200'
|
||||||
- name: Define VERSION
|
- name: Define VERSION
|
||||||
run: |
|
run: |
|
||||||
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
|
||||||
|
|
|
||||||
6
.github/workflows/rollup.yml
vendored
6
.github/workflows/rollup.yml
vendored
|
|
@ -1,8 +1,8 @@
|
||||||
name: Rollup changes to next version
|
name: Rollup changes to next version
|
||||||
on:
|
on:
|
||||||
# push:
|
push:
|
||||||
# branches:
|
branches:
|
||||||
# - master
|
- master
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,19 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"title": "Build Schema",
|
||||||
|
"$ref": "#/definitions/build",
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Host": {
|
"build": {
|
||||||
"type": "string",
|
"type": "object",
|
||||||
"enum": [
|
|
||||||
"AppVeyor",
|
|
||||||
"AzurePipelines",
|
|
||||||
"Bamboo",
|
|
||||||
"Bitbucket",
|
|
||||||
"Bitrise",
|
|
||||||
"GitHubActions",
|
|
||||||
"GitLab",
|
|
||||||
"Jenkins",
|
|
||||||
"Rider",
|
|
||||||
"SpaceAutomation",
|
|
||||||
"TeamCity",
|
|
||||||
"Terminal",
|
|
||||||
"TravisCI",
|
|
||||||
"VisualStudio",
|
|
||||||
"VSCode"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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": {
|
"properties": {
|
||||||
|
"Configuration": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||||
|
"enum": [
|
||||||
|
"Debug",
|
||||||
|
"Release"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Continue": {
|
"Continue": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Indicates to continue a previously failed build attempt"
|
"description": "Indicates to continue a previously failed build attempt"
|
||||||
|
|
@ -61,8 +23,29 @@
|
||||||
"description": "Shows the help text for this build assembly"
|
"description": "Shows the help text for this build assembly"
|
||||||
},
|
},
|
||||||
"Host": {
|
"Host": {
|
||||||
|
"type": "string",
|
||||||
"description": "Host for execution. Default is 'automatic'",
|
"description": "Host for execution. Default is 'automatic'",
|
||||||
"$ref": "#/definitions/Host"
|
"enum": [
|
||||||
|
"AppVeyor",
|
||||||
|
"AzurePipelines",
|
||||||
|
"Bamboo",
|
||||||
|
"Bitbucket",
|
||||||
|
"Bitrise",
|
||||||
|
"GitHubActions",
|
||||||
|
"GitLab",
|
||||||
|
"Jenkins",
|
||||||
|
"Rider",
|
||||||
|
"SpaceAutomation",
|
||||||
|
"TeamCity",
|
||||||
|
"Terminal",
|
||||||
|
"TravisCI",
|
||||||
|
"VisualStudio",
|
||||||
|
"VSCode"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"IsDocsBuild": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether we are building for documentation - emits generated files"
|
||||||
},
|
},
|
||||||
"NoLogo": {
|
"NoLogo": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
@ -91,46 +74,65 @@
|
||||||
"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": {
|
||||||
"$ref": "#/definitions/ExecutableTarget"
|
"type": "string",
|
||||||
|
"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": {
|
||||||
"$ref": "#/definitions/ExecutableTarget"
|
"type": "string",
|
||||||
|
"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": [
|
||||||
"Debug",
|
"Minimal",
|
||||||
"Release"
|
"Normal",
|
||||||
|
"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,11 +108,6 @@ 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,7 +44,6 @@ 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,7 +124,6 @@ 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,12 +34,6 @@ 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>
|
||||||
|
|
|
||||||
111
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
Normal file
111
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
<?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>
|
||||||
67
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj.filters
Normal file
67
Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj.filters
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?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>
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
48
Dalamud.Injector.Boot/main.cpp
Normal file
48
Dalamud.Injector.Boot/main.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#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
Dalamud.Injector.Boot/pch.h
Normal file
1
Dalamud.Injector.Boot/pch.h
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#pragma once
|
||||||
1
Dalamud.Injector.Boot/resources.rc
Normal file
1
Dalamud.Injector.Boot/resources.rc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
MAINICON ICON "dalamud.ico"
|
||||||
|
|
@ -13,13 +13,12 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Output">
|
<PropertyGroup Label="Output">
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Library</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,20 +25,34 @@ namespace Dalamud.Injector
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Entrypoint to the program.
|
/// Entrypoint to the program.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Program
|
public sealed class EntryPoint
|
||||||
{
|
{
|
||||||
|
/// <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="argsArray">Command line arguments.</param>
|
/// <param name="argc">Count of arguments.</param>
|
||||||
|
/// <param name="argvPtr">byte** string arguments.</param>
|
||||||
/// <returns>Return value (HRESULT).</returns>
|
/// <returns>Return value (HRESULT).</returns>
|
||||||
public static int Main(string[] argsArray)
|
public static int Main(int argc, IntPtr argvPtr)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// API14 TODO: Refactor
|
List<string> args = new(argc);
|
||||||
var args = argsArray.ToList();
|
|
||||||
args.Insert(0, Assembly.GetExecutingAssembly().Location);
|
unsafe
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|
@ -291,7 +305,6 @@ 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;
|
||||||
|
|
@ -322,10 +335,6 @@ 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..]);
|
||||||
|
|
@ -438,7 +447,6 @@ 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;
|
||||||
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,6 +7,8 @@ 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
|
||||||
|
|
@ -25,6 +27,8 @@ 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}"
|
||||||
|
|
@ -45,6 +49,8 @@ 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}"
|
||||||
|
|
@ -97,6 +103,10 @@ 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
|
||||||
|
|
@ -178,6 +188,8 @@ 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}
|
||||||
|
|
|
||||||
|
|
@ -495,16 +495,6 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
#pragma warning restore SA1516
|
#pragma warning restore SA1516
|
||||||
#pragma warning restore SA1600
|
#pragma warning restore SA1600
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of badge passwords used to unlock badges.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> UsedBadgePasswords { get; set; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether badges should be shown on the title screen.
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowBadgesOnTitleScreen { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load a configuration from the provided path.
|
/// Load a configuration from the provided path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
[Api15ToDo("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.")]
|
[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.")]
|
||||||
public sealed class PluginConfigurations
|
public sealed class PluginConfigurations
|
||||||
{
|
{
|
||||||
private readonly DirectoryInfo configDirectory;
|
private readonly DirectoryInfo configDirectory;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using System.Threading.Tasks;
|
||||||
using Dalamud.Common;
|
using Dalamud.Common;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Hooking.Internal.Verification;
|
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Storage;
|
using Dalamud.Storage;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
@ -74,11 +73,6 @@ internal sealed unsafe class Dalamud : IServiceType
|
||||||
scanner,
|
scanner,
|
||||||
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
|
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
|
||||||
|
|
||||||
using (Timings.Start("HookVerifier Init"))
|
|
||||||
{
|
|
||||||
HookVerifier.Initialize(scanner);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up FFXIVClientStructs
|
// Set up FFXIVClientStructs
|
||||||
this.SetupClientStructsResolver(cacheDir);
|
this.SetupClientStructsResolver(cacheDir);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<PropertyGroup Label="Feature">
|
<PropertyGroup Label="Feature">
|
||||||
<Description>XIV Launcher addon framework</Description>
|
<Description>XIV Launcher addon framework</Description>
|
||||||
<DalamudVersion>14.0.0.2</DalamudVersion>
|
<DalamudVersion>13.0.0.13</DalamudVersion>
|
||||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||||
<Version>$(DalamudVersion)</Version>
|
<Version>$(DalamudVersion)</Version>
|
||||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||||
|
|
@ -73,6 +73,8 @@
|
||||||
<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" />
|
||||||
|
|
@ -83,8 +85,11 @@
|
||||||
<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>
|
||||||
|
|
||||||
|
|
@ -117,8 +122,6 @@
|
||||||
<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>
|
||||||
|
|
@ -223,4 +226,9 @@
|
||||||
<!-- 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>
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ public enum DalamudAsset
|
||||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
[DalamudAssetPath("UIRes", "troubleIcon.png")]
|
[DalamudAssetPath("UIRes", "troubleIcon.png")]
|
||||||
TroubleIcon = 1006,
|
TroubleIcon = 1006,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -124,13 +124,6 @@ public enum DalamudAsset
|
||||||
[DalamudAssetPath("UIRes", "tsmShade.png")]
|
[DalamudAssetPath("UIRes", "tsmShade.png")]
|
||||||
TitleScreenMenuShade = 1013,
|
TitleScreenMenuShade = 1013,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: Atlas containing badges.
|
|
||||||
/// </summary>
|
|
||||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
|
||||||
[DalamudAssetPath("UIRes", "badgeAtlas.png")]
|
|
||||||
BadgeAtlas = 1015,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
|
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -158,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", "FontAwesome710FreeSolid.otf")]
|
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
|
||||||
FontAwesomeFreeSolid = 2003,
|
FontAwesomeFreeSolid = 2003,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -82,13 +82,8 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
||||||
var tsInfo =
|
var tsInfo =
|
||||||
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
||||||
dalamud.StartInfo.TroubleshootingPackData);
|
dalamud.StartInfo.TroubleshootingPackData);
|
||||||
|
this.HasModifiedGameDataFiles =
|
||||||
// Don't fail for IndexIntegrityResult.Exception, since the check during launch has a very small timeout
|
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed or LauncherTroubleshootingInfo.IndexIntegrityResult.Exception;
|
||||||
// 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}]",
|
||||||
Versioning.GetScmVersion(),
|
Util.GetScmVersion(),
|
||||||
Versioning.GetGitHashClientStructs(),
|
Util.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();
|
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
|
||||||
107
Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs
Normal file
107
Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
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,24 +5,19 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for AddonLifecycle AddonArgTypes.
|
/// Base class for AddonLifecycle AddonArgTypes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AddonArgs
|
public abstract unsafe 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";
|
||||||
|
|
||||||
/// <summary>
|
private string? addonName;
|
||||||
/// 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 { get; private set; } = InvalidAddon;
|
public string AddonName => this.GetAddonName();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the pointer to the addons AtkUnitBase.
|
/// Gets the pointer to the addons AtkUnitBase.
|
||||||
|
|
@ -30,17 +25,55 @@ public 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 virtual AddonArgsType Type => AddonArgsType.Generic;
|
public abstract AddonArgsType Type { get; }
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
24
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs
Normal file
24
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
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,12 +3,13 @@ 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
|
public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal AddonReceiveEventArgs()
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonReceiveEventArgs()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +32,23 @@ public class AddonReceiveEventArgs : AddonArgs
|
||||||
public nint AtkEvent { get; set; }
|
public nint AtkEvent { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the pointer to an AtkEventData for this event message.
|
/// Gets or sets the pointer to a block of data for this event message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public nint AtkEventData { get; set; }
|
public nint Data { 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,22 +1,17 @@
|
||||||
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
|
public class AddonRefreshArgs : AddonArgs, ICloneable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal AddonRefreshArgs()
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonRefreshArgs()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,32 +31,19 @@ public class AddonRefreshArgs : AddonArgs
|
||||||
/// <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);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||||
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
|
||||||
/// </returns>
|
|
||||||
public IEnumerable<AtkValuePtr> AtkValueEnumerable
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
for (var i = 0; i < this.AtkValueCount; i++)
|
|
||||||
{
|
|
||||||
AtkValuePtr ptr;
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return ptr;
|
/// <inheritdoc cref="Clone"/>
|
||||||
}
|
object ICloneable.Clone() => this.Clone();
|
||||||
}
|
|
||||||
|
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||||
|
internal override void Clear()
|
||||||
|
{
|
||||||
|
base.Clear();
|
||||||
|
this.AtkValueCount = default;
|
||||||
|
this.AtkValues = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ 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
|
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal AddonRequestedUpdateArgs()
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonRequestedUpdateArgs()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,4 +25,18 @@ public class AddonRequestedUpdateArgs : AddonArgs
|
||||||
/// 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,22 +1,17 @@
|
||||||
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
|
public class AddonSetupArgs : AddonArgs, ICloneable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal AddonSetupArgs()
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonSetupArgs()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,32 +31,19 @@ public class AddonSetupArgs : AddonArgs
|
||||||
/// <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);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||||
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
|
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
|
|
||||||
/// </returns>
|
|
||||||
public IEnumerable<AtkValuePtr> AtkValueEnumerable
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
for (var i = 0; i < this.AtkValueCount; i++)
|
|
||||||
{
|
|
||||||
AtkValuePtr ptr;
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return ptr;
|
/// <inheritdoc cref="Clone"/>
|
||||||
}
|
object ICloneable.Clone() => this.Clone();
|
||||||
}
|
|
||||||
|
/// <inheritdoc cref="AddonArgs.Clear"/>
|
||||||
|
internal override void Clear()
|
||||||
|
{
|
||||||
|
base.Clear();
|
||||||
|
this.AtkValueCount = default;
|
||||||
|
this.AtkValues = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
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,43 +5,38 @@
|
||||||
/// </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>
|
||||||
RequestedUpdate,
|
RequestedUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains argument data for Refresh.
|
/// Contains argument data for Refresh.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Refresh,
|
Refresh,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public enum AddonEvent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="AddonSetupArgs"/>
|
/// <seealso cref="AddonSetupArgs"/>
|
||||||
PreSetup,
|
PreSetup,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon has finished its initial setup. This event is particularly useful for
|
/// An event that is fired after an addon has finished its initial setup. This event is particularly useful for
|
||||||
/// developers seeking to add custom elements to now-initialized and populated node lists, as well as reading data
|
/// developers seeking to add custom elements to now-initialized and populated node lists, as well as reading data
|
||||||
|
|
@ -29,6 +29,7 @@ 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>
|
||||||
|
|
@ -41,6 +42,7 @@ 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>
|
||||||
|
|
@ -60,8 +62,9 @@ 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>
|
||||||
/// An event that is fired before a call to <see cref="AtkUnitBase.OnRequestedUpdate"/> is made in response to a
|
/// An event that is fired before a call to <see cref="AtkUnitBase.OnRequestedUpdate"/> is made in response to a
|
||||||
/// change in the subscribed <see cref="AddonRequestedUpdateArgs.NumberArrayData"/> or
|
/// change in the subscribed <see cref="AddonRequestedUpdateArgs.NumberArrayData"/> or
|
||||||
|
|
@ -78,13 +81,13 @@ public enum AddonEvent
|
||||||
/// to the Free Company's overview.
|
/// to the Free Company's overview.
|
||||||
/// </example>
|
/// </example>
|
||||||
PreRequestedUpdate,
|
PreRequestedUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon has finished processing an <c>ArrayData</c> update.
|
/// An event that is fired after an addon has finished processing an <c>ArrayData</c> update.
|
||||||
/// See <see cref="PreRequestedUpdate"/> for more information.
|
/// See <see cref="PreRequestedUpdate"/> for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PostRequestedUpdate,
|
PostRequestedUpdate,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired before an addon calls its <see cref="AtkUnitManager.RefreshAddon"/> method. Refreshes are
|
/// An event that is fired before an addon calls its <see cref="AtkUnitManager.RefreshAddon"/> method. Refreshes are
|
||||||
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
|
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
|
||||||
|
|
@ -93,13 +96,13 @@ public enum AddonEvent
|
||||||
/// <seealso cref="AddonRefreshArgs"/>
|
/// <seealso cref="AddonRefreshArgs"/>
|
||||||
/// <seealso cref="PostRefresh"/>
|
/// <seealso cref="PostRefresh"/>
|
||||||
PreRefresh,
|
PreRefresh,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon has finished its refresh.
|
/// An event that is fired after an addon has finished its refresh.
|
||||||
/// See <see cref="PreRefresh"/> for more information.
|
/// See <see cref="PreRefresh"/> for more information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PostRefresh,
|
PostRefresh,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired before an addon begins processing a user-driven event via
|
/// An event that is fired before an addon begins processing a user-driven event via
|
||||||
/// <see cref="AtkEventListener.ReceiveEvent"/>, such as mousing over an element or clicking a button. This event
|
/// <see cref="AtkEventListener.ReceiveEvent"/>, such as mousing over an element or clicking a button. This event
|
||||||
|
|
@ -109,98 +112,10 @@ public enum AddonEvent
|
||||||
/// <seealso cref="AddonReceiveEventArgs"/>
|
/// <seealso cref="AddonReceiveEventArgs"/>
|
||||||
/// <seealso cref="PostReceiveEvent"/>
|
/// <seealso cref="PostReceiveEvent"/>
|
||||||
PreReceiveEvent,
|
PreReceiveEvent,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that is fired after an addon finishes calling its <see cref="AtkEventListener.ReceiveEvent"/> method.
|
/// An event that is fired after an addon finishes calling its <see cref="AtkEventListener.ReceiveEvent"/> method.
|
||||||
/// 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,15 +1,16 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
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;
|
||||||
|
|
@ -20,36 +21,75 @@ 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");
|
||||||
|
|
||||||
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
[ServiceManager.ServiceDependency]
|
||||||
|
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()
|
private AddonLifecycle(TargetSigScanner sigScanner)
|
||||||
{
|
{
|
||||||
this.onInitializeAddonHook = Hook<AtkUnitBase.Delegates.Initialize>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->Initialize, this.OnAddonInitialize);
|
this.address = new AddonLifecycleAddressResolver();
|
||||||
this.onInitializeAddonHook.Enable();
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
|
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> <br/>
|
/// </summary>
|
||||||
/// Mapping is: EventType -> AddonName -> ListenerList
|
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
|
||||||
internal Dictionary<AddonEvent, Dictionary<string, HashSet<AddonLifecycleEventListener>>> EventListeners { get; } = [];
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.onInitializeAddonHook?.Dispose();
|
this.onAddonSetupHook.Dispose();
|
||||||
this.onInitializeAddonHook = null;
|
this.onAddonFinalizeHook.Dispose();
|
||||||
|
this.onAddonDrawHook.Dispose();
|
||||||
|
this.onAddonUpdateHook.Dispose();
|
||||||
|
this.onAddonRefreshHook.Dispose();
|
||||||
|
this.onAddonRequestedUpdateHook.Dispose();
|
||||||
|
|
||||||
AllocatedTables.ForEach(entry => entry.Dispose());
|
foreach (var receiveEventListener in this.ReceiveEventListeners)
|
||||||
AllocatedTables.Clear();
|
{
|
||||||
|
receiveEventListener.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -58,20 +98,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)
|
||||||
{
|
{
|
||||||
if (!this.EventListeners.ContainsKey(listener.EventType))
|
this.framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
this.EventListeners.Add(listener);
|
||||||
return;
|
|
||||||
}
|
// If we want receive event messages have an already active addon, enable the receive event hook.
|
||||||
|
// If the addon isn't active yet, we'll grab the hook when it sets up.
|
||||||
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
||||||
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
{
|
||||||
{
|
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>
|
||||||
|
|
@ -80,13 +120,27 @@ 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)
|
||||||
{
|
{
|
||||||
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
// Set removed state to true immediately, then lazily remove it from the EventListeners list on next Framework Update.
|
||||||
|
listener.Removed = true;
|
||||||
|
|
||||||
|
this.framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
this.EventListeners.Remove(listener);
|
||||||
|
|
||||||
|
// If we are disabling an ReceiveEvent listener, check if we should disable the hook.
|
||||||
|
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
||||||
{
|
{
|
||||||
addonListener.Remove(listener);
|
// Get the ReceiveEvent Listener for this addon
|
||||||
|
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>
|
||||||
|
|
@ -97,76 +151,226 @@ 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 = "")
|
||||||
{
|
{
|
||||||
// Early return if we don't have any listeners of this type
|
// Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
|
||||||
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
foreach (var listener in this.EventListeners)
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global addon event listener.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception e)
|
||||||
|
|
||||||
// Handle listeners that are listening for this addon and event type specifically
|
|
||||||
if (addonListeners.TryGetValue(args.AddonName, out var addonListener))
|
|
||||||
{
|
|
||||||
foreach (var listener in addonListener)
|
|
||||||
{
|
{
|
||||||
try
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke.");
|
||||||
{
|
|
||||||
listener.FunctionDelegate.Invoke(eventType, args);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific addon {args.AddonName}.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void RegisterReceiveEventHook(AtkUnitBase* addon)
|
||||||
/// Resolves a virtual table address to the original virtual table address.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tableAddress">The modified address to resolve.</param>
|
|
||||||
/// <returns>The original address.</returns>
|
|
||||||
internal AtkUnitBase.AtkUnitBaseVirtualTable* GetOriginalVirtualTable(AtkUnitBase.AtkUnitBaseVirtualTable* tableAddress)
|
|
||||||
{
|
{
|
||||||
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
|
||||||
if (matchedTable == null) return null;
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return matchedTable.OriginalVirtualTable;
|
// 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 OnAddonInitialize(AtkUnitBase* addon)
|
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.LogInitialize(addon->NameString);
|
this.RegisterReceiveEventHook(addon);
|
||||||
|
|
||||||
// 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 AddonLifecycle during OnAddonInitialize.");
|
Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onInitializeAddonHook!.Original(addon);
|
using var returner = this.argsPool.Rent(out AddonSetupArgs arg);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||||
private void LogInitialize(string addonName)
|
|
||||||
{
|
{
|
||||||
Log.Debug($"Initializing {addonName}");
|
try
|
||||||
|
{
|
||||||
|
var addonName = atkUnitBase[0]->NameString;
|
||||||
|
this.UnregisterReceiveEventHook(addonName);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg);
|
||||||
|
arg.Clear();
|
||||||
|
arg.Addon = (nint)atkUnitBase[0];
|
||||||
|
this.InvokeListenersSafely(AddonEvent.PreFinalize, arg);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +387,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 = [];
|
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
|
|
@ -254,14 +458,10 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
||||||
this.eventListeners.RemoveAll(entry =>
|
this.eventListeners.RemoveAll(entry =>
|
||||||
{
|
{
|
||||||
if (entry.FunctionDelegate != handler) return false;
|
if (entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
this.addonLifecycleService.UnregisterListener(entry);
|
this.addonLifecycleService.UnregisterListener(entry);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
|
||||||
=> (nint)this.addonLifecycleService.GetOriginalVirtualTable((AtkUnitBase.AtkUnitBaseVirtualTable*)virtualTableAddress);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,12 +25,17 @@ internal class AddonLifecycleEventListener
|
||||||
/// string.Empty if it wants to be called for any addon.
|
/// string.Empty if it wants to be called for any addon.
|
||||||
/// </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>
|
||||||
public AddonEvent EventType { get; init; }
|
public AddonEvent EventType { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the delegate this listener invokes.
|
/// Gets the delegate this listener invokes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs
Normal file
80
Dalamud/Game/Addon/Lifecycle/AddonSetupHook.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,645 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the original virtual table address for this addon.
|
|
||||||
/// </summary>
|
|
||||||
internal AtkUnitBase.AtkUnitBaseVirtualTable* OriginalVirtualTable { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the modified virtual address for this addon.
|
|
||||||
/// </summary>
|
|
||||||
internal AtkUnitBase.AtkUnitBaseVirtualTable* ModifiedVirtualTable { get; private set; }
|
|
||||||
|
|
||||||
/// <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,8 +2,6 @@ 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."), Versioning.GetScmVersion())
|
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud {0} loaded."), Util.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) || !Versioning.GetAssemblyVersion().StartsWith(this.configuration.LastVersion))
|
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Util.AssemblyVersion.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 = Versioning.GetAssemblyVersion();
|
this.configuration.LastVersion = Util.AssemblyVersion;
|
||||||
this.configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,37 +63,47 @@ public interface IAetheryteEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This struct represents an aetheryte entry available to the game.
|
/// Class representing an aetheryte entry available to the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">Data read from the Aetheryte List.</param>
|
internal sealed class AetheryteEntry : IAetheryteEntry
|
||||||
internal readonly struct AetheryteEntry(TeleportInfo data) : IAetheryteEntry
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
private readonly TeleportInfo data;
|
||||||
public uint AetheryteId => data.AetheryteId;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data read from the Aetheryte List.</param>
|
||||||
|
internal AetheryteEntry(TeleportInfo data)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint TerritoryId => data.TerritoryId;
|
public uint AetheryteId => this.data.AetheryteId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte SubIndex => data.SubIndex;
|
public uint TerritoryId => this.data.TerritoryId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte Ward => data.Ward;
|
public byte SubIndex => this.data.SubIndex;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public byte Plot => data.Plot;
|
public byte Ward => this.data.Ward;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint GilCost => data.GilCost;
|
public byte Plot => this.data.Plot;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsFavourite => data.IsFavourite;
|
public uint GilCost => this.data.GilCost;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsSharedHouse => data.IsSharedHouse;
|
public bool IsFavourite => this.data.IsFavourite;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsApartment => data.IsApartment;
|
public bool IsSharedHouse => this.data.IsSharedHouse;
|
||||||
|
|
||||||
|
/// <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,7 +88,10 @@ internal sealed partial class AetheryteList
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IAetheryteEntry> GetEnumerator()
|
public IEnumerator<IAetheryteEntry> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new Enumerator(this);
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
yield return this[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -96,34 +99,4 @@ 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,7 +8,6 @@ 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;
|
||||||
|
|
@ -24,7 +23,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 InvalidEntityId = 0xE0000000;
|
private const uint InvalidObjectID = 0xE0000000;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
private readonly PlayerState playerState = Service<PlayerState>.Get();
|
||||||
|
|
@ -85,37 +84,37 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe nint GetCompanionBuddyMemberAddress()
|
public unsafe IntPtr GetCompanionBuddyMemberAddress()
|
||||||
{
|
{
|
||||||
return (nint)this.BuddyListStruct->CompanionInfo.Companion;
|
return (IntPtr)this.BuddyListStruct->CompanionInfo.Companion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe nint GetPetBuddyMemberAddress()
|
public unsafe IntPtr GetPetBuddyMemberAddress()
|
||||||
{
|
{
|
||||||
return (nint)this.BuddyListStruct->PetInfo.Pet;
|
return (IntPtr)this.BuddyListStruct->PetInfo.Pet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe nint GetBattleBuddyMemberAddress(int index)
|
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= 3)
|
if (index < 0 || index >= 3)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return (nint)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
return (IntPtr)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IBuddyMember? CreateBuddyMemberReference(nint address)
|
public IBuddyMember? CreateBuddyMemberReference(IntPtr address)
|
||||||
{
|
{
|
||||||
if (address == 0)
|
if (address == IntPtr.Zero)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (this.playerState.ContentId == 0)
|
if (!this.playerState.IsLoaded)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var buddy = new BuddyMember((CSBuddyMember*)address);
|
var buddy = new BuddyMember(address);
|
||||||
if (buddy.EntityId == InvalidEntityId)
|
if (buddy.ObjectId == InvalidObjectID)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return buddy;
|
return buddy;
|
||||||
|
|
@ -133,39 +132,12 @@ internal sealed partial class BuddyList
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IBuddyMember> GetEnumerator()
|
public IEnumerator<IBuddyMember> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new Enumerator(this);
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
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,24 +1,20 @@
|
||||||
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 : IEquatable<IBuddyMember>
|
public interface IBuddyMember
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the buddy in memory.
|
/// Gets the address of the buddy in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
nint Address { get; }
|
IntPtr Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the object ID of this buddy.
|
/// Gets the object ID of this buddy.
|
||||||
|
|
@ -71,34 +67,42 @@ public interface IBuddyMember : IEquatable<IBuddyMember>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This struct represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ptr">A pointer to the BuddyMember.</param>
|
internal unsafe class BuddyMember : IBuddyMember
|
||||||
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();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public nint Address => (nint)ptr;
|
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Buddy address.</param>
|
||||||
|
internal BuddyMember(IntPtr address)
|
||||||
|
{
|
||||||
|
this.Address = address;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint ObjectId => this.EntityId;
|
public IntPtr Address { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint EntityId => ptr->EntityId;
|
public uint ObjectId => this.Struct->EntityId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IGameObject? GameObject => this.objectTable.SearchById(this.EntityId);
|
public uint EntityId => this.Struct->EntityId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint CurrentHP => ptr->CurrentHealth;
|
public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint MaxHP => ptr->MaxHealth;
|
public uint CurrentHP => this.Struct->CurrentHealth;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public uint DataID => ptr->DataId;
|
public uint MaxHP => this.Struct->MaxHealth;
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
|
@ -109,25 +113,5 @@ internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : 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);
|
||||||
|
|
||||||
public static bool operator ==(BuddyMember x, BuddyMember y) => x.Equals(y);
|
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 == 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
|
|
||||||
private readonly GameLifecycle lifecycle;
|
private readonly GameLifecycle lifecycle;
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
private readonly Hook<HandleZoneInitPacketDelegate> handleZoneInitPacketHook;
|
||||||
private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook;
|
private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook;
|
||||||
private readonly Hook<SetCurrentInstanceDelegate> setCurrentInstanceHook;
|
private readonly Hook<SetCurrentInstanceDelegate> setCurrentInstanceHook;
|
||||||
|
|
||||||
|
|
@ -71,11 +72,13 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
|
|
||||||
this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language;
|
this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language;
|
||||||
|
|
||||||
|
this.handleZoneInitPacketHook = Hook<HandleZoneInitPacketDelegate>.FromAddress(this.AddressResolver.HandleZoneInitPacket, this.HandleZoneInitPacketDetour);
|
||||||
this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour);
|
this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour);
|
||||||
this.setCurrentInstanceHook = Hook<SetCurrentInstanceDelegate>.FromAddress(this.AddressResolver.SetCurrentInstance, this.SetCurrentInstanceDetour);
|
this.setCurrentInstanceHook = Hook<SetCurrentInstanceDelegate>.FromAddress(this.AddressResolver.SetCurrentInstance, this.SetCurrentInstanceDetour);
|
||||||
|
|
||||||
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||||
|
|
||||||
|
this.handleZoneInitPacketHook.Enable();
|
||||||
this.uiModuleHandlePacketHook.Enable();
|
this.uiModuleHandlePacketHook.Enable();
|
||||||
this.setCurrentInstanceHook.Enable();
|
this.setCurrentInstanceHook.Enable();
|
||||||
|
|
||||||
|
|
@ -268,6 +271,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void IInternalDisposableService.DisposeService()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
|
this.handleZoneInitPacketHook.Dispose();
|
||||||
this.uiModuleHandlePacketHook.Dispose();
|
this.uiModuleHandlePacketHook.Dispose();
|
||||||
this.onLogoutHook.Dispose();
|
this.onLogoutHook.Dispose();
|
||||||
this.setCurrentInstanceHook.Dispose();
|
this.setCurrentInstanceHook.Dispose();
|
||||||
|
|
@ -290,6 +294,23 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
this.framework.Update += this.OnFrameworkUpdate;
|
this.framework.Update += this.OnFrameworkUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleZoneInitPacketDetour(nint a1, uint localPlayerEntityId, nint packet, byte type)
|
||||||
|
{
|
||||||
|
this.handleZoneInitPacketHook.Original(a1, localPlayerEntityId, packet, type);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var eventArgs = ZoneInitEventArgs.Read(packet);
|
||||||
|
Log.Debug($"ZoneInit: {eventArgs}");
|
||||||
|
this.ZoneInit?.InvokeSafely(eventArgs);
|
||||||
|
this.TerritoryType = (ushort)eventArgs.TerritoryType.RowId;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Exception during ZoneInit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe void UIModuleHandlePacketDetour(
|
private unsafe void UIModuleHandlePacketDetour(
|
||||||
UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet)
|
UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet)
|
||||||
{
|
{
|
||||||
|
|
@ -335,15 +356,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case (UIModulePacketType)5: // TODO: Use UIModulePacketType.InitZone when available
|
|
||||||
{
|
|
||||||
var eventArgs = ZoneInitEventArgs.Read((nint)packet);
|
|
||||||
Log.Debug($"ZoneInit: {eventArgs}");
|
|
||||||
this.ZoneInit?.InvokeSafely(eventArgs);
|
|
||||||
this.TerritoryType = (ushort)eventArgs.TerritoryType.RowId;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState;
|
namespace Dalamud.Game.ClientState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -21,6 +19,11 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the method that handles the ZoneInit packet.
|
||||||
|
/// </summary>
|
||||||
|
public nint HandleZoneInitPacket { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the method that sets the current public instance.
|
/// Gets the address of the method that sets the current public instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -32,6 +35,7 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||||
protected override void Setup64Bit(ISigScanner sig)
|
protected override void Setup64Bit(ISigScanner sig)
|
||||||
{
|
{
|
||||||
|
this.HandleZoneInitPacket = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 45");
|
||||||
this.SetCurrentInstance = sig.ScanText("E8 ?? ?? ?? ?? 0F B6 55 ?? 48 8D 0D ?? ?? ?? ?? C0 EA"); // NetworkModuleProxy.SetCurrentInstance
|
this.SetCurrentInstance = sig.ScanText("E8 ?? ?? ?? ?? 0F B6 55 ?? 48 8D 0D ?? ?? ?? ?? C0 EA"); // NetworkModuleProxy.SetCurrentInstance
|
||||||
|
|
||||||
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
|
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ internal sealed class Condition : IInternalDisposableService, ICondition
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal const int MaxConditionEntries = 112;
|
internal const int MaxConditionEntries = 104;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
|
||||||
|
|
@ -520,17 +520,4 @@ public enum ConditionFlag
|
||||||
PilotingMech = 102,
|
PilotingMech = 102,
|
||||||
|
|
||||||
// Unknown103 = 103,
|
// Unknown103 = 103,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unable to execute command while editing a strategy board.
|
|
||||||
/// </summary>
|
|
||||||
EditingStrategyBoard = 104,
|
|
||||||
|
|
||||||
// Unknown105 = 105,
|
|
||||||
// Unknown106 = 106,
|
|
||||||
// Unknown107 = 107,
|
|
||||||
// Unknown108 = 108,
|
|
||||||
// Unknown109 = 109,
|
|
||||||
// Unknown110 = 110,
|
|
||||||
// Unknown111 = 111,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
|
@ -8,12 +7,10 @@ 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 a fate entry that can be seen in the current area.
|
/// Interface representing an fate entry that can be seen in the current area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IFate : IEquatable<IFate>
|
public interface IFate : IEquatable<IFate>
|
||||||
{
|
{
|
||||||
|
|
@ -115,96 +112,129 @@ 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>
|
||||||
nint Address { get; }
|
IntPtr Address { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This struct represents a Fate.
|
/// This class represents an FFXIV Fate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ptr">A pointer to the FateContext.</param>
|
internal unsafe partial class Fate
|
||||||
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 />
|
/// <inheritdoc />
|
||||||
public nint Address => (nint)ptr;
|
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/>
|
/// <inheritdoc/>
|
||||||
public ushort FateId => ptr->FateId;
|
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/>
|
||||||
|
public ushort FateId => this.Struct->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 => ptr->StartTimeEpoch;
|
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public short Duration => ptr->Duration;
|
public short Duration => this.Struct->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(&ptr->Name);
|
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString Description => MemoryHelper.ReadSeString(&ptr->Description);
|
public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString Objective => MemoryHelper.ReadSeString(&ptr->Objective);
|
public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public FateState State => (FateState)ptr->State;
|
public FateState State => (FateState)this.Struct->State;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte HandInCount => ptr->HandInCount;
|
public byte HandInCount => this.Struct->HandInCount;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Progress => ptr->Progress;
|
public byte Progress => this.Struct->Progress;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool HasBonus => ptr->IsBonus;
|
public bool HasBonus => this.Struct->IsBonus;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint IconId => ptr->IconId;
|
public uint IconId => this.Struct->IconId;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Level => ptr->Level;
|
public byte Level => this.Struct->Level;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte MaxLevel => ptr->MaxLevel;
|
public byte MaxLevel => this.Struct->MaxLevel;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Vector3 Position => ptr->Location;
|
public Vector3 Position => this.Struct->Location;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public float Radius => ptr->Radius;
|
public float Radius => this.Struct->Radius;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint MapIconId => ptr->MapIconId;
|
public uint MapIconId => this.Struct->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>(ptr->MapMarkers[0].MapMarkerData.TerritoryTypeId);
|
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->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,7 +6,6 @@ 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;
|
||||||
|
|
@ -27,7 +26,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe nint Address => (nint)CSFateManager.Instance();
|
public unsafe IntPtr Address => (nint)CSFateManager.Instance();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe int Length
|
public unsafe int Length
|
||||||
|
|
@ -70,29 +69,29 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe nint GetFateAddress(int index)
|
public unsafe IntPtr GetFateAddress(int index)
|
||||||
{
|
{
|
||||||
if (index >= this.Length)
|
if (index >= this.Length)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
var fateManager = CSFateManager.Instance();
|
var fateManager = CSFateManager.Instance();
|
||||||
if (fateManager == null)
|
if (fateManager == null)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return (nint)fateManager->Fates[index].Value;
|
return (IntPtr)fateManager->Fates[index].Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe IFate? CreateFateReference(IntPtr address)
|
public IFate? CreateFateReference(IntPtr offset)
|
||||||
{
|
{
|
||||||
if (address == 0)
|
if (offset == IntPtr.Zero)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var clientState = Service<ClientState>.Get();
|
var playerState = Service<PlayerState>.Get();
|
||||||
if (clientState.LocalContentId == 0)
|
if (!playerState.IsLoaded)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new Fate((CSFateContext*)address);
|
return new Fate(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,39 +106,12 @@ internal sealed partial class FateTable
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IFate> GetEnumerator()
|
public IEnumerator<IFate> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new Enumerator(this);
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
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,6 +13,8 @@ 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;
|
||||||
|
|
||||||
|
|
@ -35,6 +37,8 @@ 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()
|
||||||
{
|
{
|
||||||
|
|
@ -44,6 +48,9 @@ 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/>
|
||||||
|
|
@ -236,25 +243,43 @@ 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 struct Enumerator(ObjectTable owner) : IEnumerator<IGameObject>
|
private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator<IGameObject>, IResettable
|
||||||
{
|
{
|
||||||
|
private ObjectTable? owner = owner;
|
||||||
|
|
||||||
private int index = -1;
|
private int index = -1;
|
||||||
|
|
||||||
public IGameObject Current { get; private set; }
|
public IGameObject Current { get; private set; } = null!;
|
||||||
|
|
||||||
object IEnumerator.Current => this.Current;
|
object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
var cache = owner.cachedObjectTable.AsSpan();
|
if (this.index == objectTableLength)
|
||||||
|
return false;
|
||||||
|
|
||||||
while (++this.index < objectTableLength)
|
var cache = this.owner!.cachedObjectTable.AsSpan();
|
||||||
|
for (this.index++; this.index < objectTableLength; this.index++)
|
||||||
{
|
{
|
||||||
if (cache[this.index].Update() is { } ao)
|
if (cache[this.index].Update() is { } ao)
|
||||||
{
|
{
|
||||||
|
|
@ -263,17 +288,24 @@ internal sealed partial class ObjectTable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Current = default;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset() => this.index = -1;
|
||||||
{
|
|
||||||
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,7 +1,6 @@
|
||||||
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,7 +9,6 @@ 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;
|
||||||
|
|
||||||
|
|
@ -44,20 +43,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 nint GroupManagerAddress => (nint)CSGroupManager.Instance();
|
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public nint GroupListAddress => (nint)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
public IntPtr AllianceListAddress => (IntPtr)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<CSPartyMember>();
|
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
|
||||||
|
|
||||||
private CSGroupManager* GroupManagerStruct => (CSGroupManager*)this.GroupManagerAddress;
|
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IPartyMember? this[int index]
|
public IPartyMember? this[int index]
|
||||||
|
|
@ -82,45 +81,39 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public nint GetPartyMemberAddress(int index)
|
public IntPtr GetPartyMemberAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= GroupLength)
|
if (index < 0 || index >= GroupLength)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return this.GroupListAddress + (index * PartyMemberSize);
|
return this.GroupListAddress + (index * PartyMemberSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IPartyMember? CreatePartyMemberReference(nint address)
|
public IPartyMember? CreatePartyMemberReference(IntPtr address)
|
||||||
{
|
{
|
||||||
if (this.playerState.ContentId == 0)
|
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == 0)
|
return new PartyMember(address);
|
||||||
return null;
|
|
||||||
|
|
||||||
return new PartyMember((CSPartyMember*)address);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public nint GetAllianceMemberAddress(int index)
|
public IntPtr GetAllianceMemberAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= AllianceLength)
|
if (index < 0 || index >= AllianceLength)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return this.AllianceListAddress + (index * PartyMemberSize);
|
return this.AllianceListAddress + (index * PartyMemberSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IPartyMember? CreateAllianceMemberReference(nint address)
|
public IPartyMember? CreateAllianceMemberReference(IntPtr address)
|
||||||
{
|
{
|
||||||
if (this.playerState.ContentId == 0)
|
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == 0)
|
return new PartyMember(address);
|
||||||
return null;
|
|
||||||
|
|
||||||
return new PartyMember((CSPartyMember*)address);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,43 +128,18 @@ internal sealed partial class PartyList
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IPartyMember> GetEnumerator()
|
public IEnumerator<IPartyMember> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new Enumerator(this);
|
// Normally using Length results in a recursion crash, however we know the party size via ptr.
|
||||||
|
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,28 +1,26 @@
|
||||||
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.Utility;
|
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 : IEquatable<IPartyMember>
|
public interface IPartyMember
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of this party member in memory.
|
/// Gets the address of this party member in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
nint Address { get; }
|
IntPtr 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.
|
||||||
|
|
@ -110,82 +108,69 @@ public interface IPartyMember : IEquatable<IPartyMember>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This struct represents a party member in the group manager.
|
/// This class represents a party member in the group manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ptr">A pointer to the PartyMember.</param>
|
internal unsafe class PartyMember : IPartyMember
|
||||||
internal unsafe readonly struct PartyMember(CSPartyMember* ptr) : IPartyMember
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
public nint Address => (nint)ptr;
|
/// Initializes a new instance of the <see cref="PartyMember"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Address of the party member.</param>
|
||||||
|
internal PartyMember(IntPtr address)
|
||||||
|
{
|
||||||
|
this.Address = address;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public StatusList Statuses => new(&ptr->StatusManager);
|
public IntPtr Address { get; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Vector3 Position => ptr->Position;
|
public StatusList Statuses => new(&this.Struct->StatusManager);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[Api15ToDo("Change type to ulong.")]
|
public Vector3 Position => this.Struct->Position;
|
||||||
public long ContentId => (long)ptr->ContentId;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint ObjectId => ptr->EntityId;
|
public long ContentId => (long)this.Struct->ContentId;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint EntityId => ptr->EntityId;
|
public uint ObjectId => this.Struct->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 => ptr->CurrentHP;
|
public uint CurrentHP => this.Struct->CurrentHP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint MaxHP => ptr->MaxHP;
|
public uint MaxHP => this.Struct->MaxHP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ushort CurrentMP => ptr->CurrentMP;
|
public ushort CurrentMP => this.Struct->CurrentMP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ushort MaxMP => ptr->MaxMP;
|
public ushort MaxMP => this.Struct->MaxMP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->TerritoryType);
|
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryType);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(ptr->HomeWorld);
|
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(this.Struct->HomeWorld);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString Name => SeString.Parse(ptr->Name);
|
public SeString Name => SeString.Parse(this.Struct->Name);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Sex => ptr->Sex;
|
public byte Sex => this.Struct->Sex;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(ptr->ClassJob);
|
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(this.Struct->ClassJob);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte Level => ptr->Level;
|
public byte Level => this.Struct->Level;
|
||||||
|
|
||||||
public static bool operator ==(PartyMember x, PartyMember y) => x.Equals(y);
|
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 == 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,49 +1,61 @@
|
||||||
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>
|
||||||
/// Interface representing a status.
|
/// This class represents a status effect an actor is afflicted by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IStatus : IEquatable<IStatus>
|
public unsafe class Status
|
||||||
{
|
{
|
||||||
|
/// <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>
|
||||||
nint Address { get; }
|
public IntPtr Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the status ID of this status.
|
/// Gets the status ID of this status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
uint StatusId { get; }
|
public uint StatusId => this.Struct->StatusId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the GameData associated with this status.
|
/// Gets the GameData associated with this status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RowRef<Lumina.Excel.Sheets.Status> GameData { get; }
|
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(this.Struct->StatusId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parameter value of the status.
|
/// Gets the parameter value of the status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ushort Param { get; }
|
public ushort Param => this.Struct->Param;
|
||||||
|
|
||||||
|
/// <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>
|
||||||
float RemainingTime { get; }
|
public float RemainingTime => this.Struct->RemainingTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the source ID of this status.
|
/// Gets the source ID of this status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
uint SourceId { get; }
|
public uint SourceId => this.Struct->SourceObject.ObjectId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the source actor associated with this status.
|
/// Gets the source actor associated with this status.
|
||||||
|
|
@ -51,55 +63,7 @@ public interface IStatus : IEquatable<IStatus>
|
||||||
/// <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);
|
||||||
|
|
||||||
public static bool operator ==(Status x, Status y) => x.Equals(y);
|
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
|
||||||
|
|
||||||
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 CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
|
using Dalamud.Game.Player;
|
||||||
|
|
||||||
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(nint address)
|
internal StatusList(IntPtr 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((nint)pointer)
|
: this((IntPtr)pointer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the status list in memory.
|
/// Gets the address of the status list in memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public nint Address { get; }
|
public IntPtr 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 IStatus? this[int index]
|
public Status? 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(nint address)
|
public static StatusList? CreateStatusListReference(IntPtr address)
|
||||||
{
|
{
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -74,12 +74,8 @@ 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 clientState = Service<ClientState>.Get();
|
var playerState = Service<PlayerState>.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);
|
||||||
|
|
@ -90,15 +86,16 @@ 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 IStatus? CreateStatusReference(nint address)
|
public static Status? CreateStatusReference(IntPtr address)
|
||||||
{
|
{
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == 0)
|
var playerState = Service<PlayerState>.Get();
|
||||||
|
if (!playerState.IsLoaded)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new Status((CSStatus*)address);
|
return new Status(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -106,22 +103,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 nint GetStatusAddress(int index)
|
public IntPtr GetStatusAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= this.Length)
|
if (index < 0 || index >= this.Length)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return (nint)Unsafe.AsPointer(ref this.Struct->Status[index]);
|
return (IntPtr)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<IStatus>, ICollection
|
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int IReadOnlyCollection<IStatus>.Count => this.Length;
|
int IReadOnlyCollection<Status>.Count => this.Length;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int ICollection.Count => this.Length;
|
int ICollection.Count => this.Length;
|
||||||
|
|
@ -133,9 +130,17 @@ public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollecti
|
||||||
object ICollection.SyncRoot => this;
|
object ICollection.SyncRoot => this;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<IStatus> GetEnumerator()
|
public IEnumerator<Status> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new Enumerator(this);
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
{
|
||||||
|
var status = this[i];
|
||||||
|
|
||||||
|
if (status == null || status.StatusId == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
yield return status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -150,38 +155,4 @@ public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollecti
|
||||||
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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
Dalamud/Game/ClientState/Structs/StatusEffect.cs
Normal file
35
Dalamud/Game/ClientState/Structs/StatusEffect.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -59,7 +59,7 @@ public class ZoneInitEventArgs : EventArgs
|
||||||
eventArgs.ContentFinderCondition = dataManager.GetExcelSheet<ContentFinderCondition>().GetRow(*(ushort*)(packet + 0x06));
|
eventArgs.ContentFinderCondition = dataManager.GetExcelSheet<ContentFinderCondition>().GetRow(*(ushort*)(packet + 0x06));
|
||||||
eventArgs.Weather = dataManager.GetExcelSheet<Weather>().GetRow(*(byte*)(packet + 0x10));
|
eventArgs.Weather = dataManager.GetExcelSheet<Weather>().GetRow(*(byte*)(packet + 0x10));
|
||||||
|
|
||||||
const int NumFestivals = 8;
|
const int NumFestivals = 4;
|
||||||
eventArgs.ActiveFestivals = new Festival[NumFestivals];
|
eventArgs.ActiveFestivals = new Festival[NumFestivals];
|
||||||
eventArgs.ActiveFestivalPhases = new ushort[NumFestivals];
|
eventArgs.ActiveFestivalPhases = new ushort[NumFestivals];
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ public class ZoneInitEventArgs : EventArgs
|
||||||
// but it's unclear why they exist as separate entries and why they would be different.
|
// but it's unclear why they exist as separate entries and why they would be different.
|
||||||
for (var i = 0; i < NumFestivals; i++)
|
for (var i = 0; i < NumFestivals; i++)
|
||||||
{
|
{
|
||||||
eventArgs.ActiveFestivals[i] = dataManager.GetExcelSheet<Festival>().GetRow(*(ushort*)(packet + 0x26 + (i * 2)));
|
eventArgs.ActiveFestivals[i] = dataManager.GetExcelSheet<Festival>().GetRow(*(ushort*)(packet + 0x2E + (i * 2)));
|
||||||
eventArgs.ActiveFestivalPhases[i] = *(ushort*)(packet + 0x36 + (i * 2));
|
eventArgs.ActiveFestivalPhases[i] = *(ushort*)(packet + 0x36 + (i * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using Dalamud.Plugin.Services;
|
namespace Dalamud.Game.Config;
|
||||||
|
|
||||||
namespace Dalamud.Game.Config;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Game config system address resolver.
|
/// Game config system address resolver.
|
||||||
|
|
|
||||||
|
|
@ -4069,13 +4069,6 @@ 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,5 +1,3 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.DutyState;
|
namespace Dalamud.Game.DutyState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ using Lumina.Text;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||||
using SeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
using SeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||||
using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
|
using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
|
||||||
|
|
||||||
|
|
@ -206,21 +207,21 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
if (this.chatQueue.Count == 0)
|
if (this.chatQueue.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var sb = LSeStringBuilder.SharedPool.Get();
|
||||||
Span<byte> namebuf = stackalloc byte[256];
|
Span<byte> namebuf = stackalloc byte[256];
|
||||||
using var sender = new Utf8String();
|
using var sender = new Utf8String();
|
||||||
using var message = new Utf8String();
|
using var message = new Utf8String();
|
||||||
while (this.chatQueue.TryDequeue(out var chat))
|
while (this.chatQueue.TryDequeue(out var chat))
|
||||||
{
|
{
|
||||||
rssb.Builder.Clear();
|
sb.Clear();
|
||||||
foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString))
|
foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString))
|
||||||
{
|
{
|
||||||
if (c.IsSeStringPayload)
|
if (c.IsSeStringPayload)
|
||||||
rssb.Builder.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength));
|
sb.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength));
|
||||||
else if (c.Value.IntValue == 0x202F)
|
else if (c.Value.IntValue == 0x202F)
|
||||||
rssb.Builder.BeginMacro(MacroCode.NonBreakingSpace).EndMacro();
|
sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro();
|
||||||
else
|
else
|
||||||
rssb.Builder.Append(c);
|
sb.Append(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.NameBytes.Length + 1 < namebuf.Length)
|
if (chat.NameBytes.Length + 1 < namebuf.Length)
|
||||||
|
|
@ -234,7 +235,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
sender.SetString(chat.NameBytes.NullTerminate());
|
sender.SetString(chat.NameBytes.NullTerminate());
|
||||||
}
|
}
|
||||||
|
|
||||||
message.SetString(rssb.Builder.GetViewAsSpan());
|
message.SetString(sb.GetViewAsSpan());
|
||||||
|
|
||||||
var targetChannel = chat.Type ?? this.configuration.GeneralChatType;
|
var targetChannel = chat.Type ?? this.configuration.GeneralChatType;
|
||||||
|
|
||||||
|
|
@ -246,6 +247,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
chat.Timestamp,
|
chat.Timestamp,
|
||||||
(byte)(chat.Silent ? 1 : 0));
|
(byte)(chat.Silent ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LSeStringBuilder.SharedPool.Return(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -323,28 +326,29 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
|
|
||||||
private void PrintTagged(ReadOnlySpan<byte> message, XivChatType channel, string? tag, ushort? color)
|
private void PrintTagged(ReadOnlySpan<byte> message, XivChatType channel, string? tag, ushort? color)
|
||||||
{
|
{
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var sb = LSeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
if (!tag.IsNullOrEmpty())
|
if (!tag.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
if (color is not null)
|
if (color is not null)
|
||||||
{
|
{
|
||||||
rssb.Builder
|
sb.PushColorType(color.Value);
|
||||||
.PushColorType(color.Value)
|
sb.Append($"[{tag}] ");
|
||||||
.Append($"[{tag}] ")
|
sb.PopColorType();
|
||||||
.PopColorType();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rssb.Builder.Append($"[{tag}] ");
|
sb.Append($"[{tag}] ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Print(new XivChatEntry
|
this.Print(new XivChatEntry
|
||||||
{
|
{
|
||||||
MessageBytes = rssb.Builder.Append((ReadOnlySeStringSpan)message).ToArray(),
|
MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(),
|
||||||
Type = channel,
|
Type = channel,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
LSeStringBuilder.SharedPool.Return(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
|
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
|
||||||
|
|
@ -453,8 +457,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
|
|
||||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var sb = LSeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload);
|
var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload);
|
||||||
|
|
@ -462,7 +465,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
// read until link terminator
|
// read until link terminator
|
||||||
foreach (var payload in seStringSpan)
|
foreach (var payload in seStringSpan)
|
||||||
{
|
{
|
||||||
rssb.Builder.Append(payload);
|
sb.Append(payload);
|
||||||
|
|
||||||
if (payload.Type == ReadOnlySePayloadType.Macro &&
|
if (payload.Type == ReadOnlySePayloadType.Macro &&
|
||||||
payload.MacroCode == MacroCode.Link &&
|
payload.MacroCode == MacroCode.Link &&
|
||||||
|
|
@ -474,7 +477,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var seStr = SeString.Parse(rssb.Builder.ToArray());
|
var seStr = SeString.Parse(sb.ToArray());
|
||||||
if (seStr.Payloads.Count == 0 || seStr.Payloads[0] is not DalamudLinkPayload link)
|
if (seStr.Payloads.Count == 0 || seStr.Payloads[0] is not DalamudLinkPayload link)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -492,6 +495,10 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LSeStringBuilder.SharedPool.Return(sb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
private static readonly ModuleLog Log = new("ContextMenu");
|
private static readonly ModuleLog Log = new("ContextMenu");
|
||||||
|
|
||||||
private readonly Hook<AtkModuleVf22OpenAddonByAgentDelegate> atkModuleVf22OpenAddonByAgentHook;
|
private readonly Hook<AtkModuleVf22OpenAddonByAgentDelegate> atkModuleVf22OpenAddonByAgentHook;
|
||||||
private readonly Hook<AddonContextMenu.Delegates.OnMenuSelected> addonContextMenuOnMenuSelectedHook;
|
private readonly Hook<AddonContextMenuOnMenuSelectedDelegate> addonContextMenuOnMenuSelectedHook;
|
||||||
|
|
||||||
private uint? addonContextSubNameId;
|
private uint? addonContextSubNameId;
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
{
|
{
|
||||||
var raptureAtkModuleVtable = (nint*)RaptureAtkModule.StaticVirtualTablePointer;
|
var raptureAtkModuleVtable = (nint*)RaptureAtkModule.StaticVirtualTablePointer;
|
||||||
this.atkModuleVf22OpenAddonByAgentHook = Hook<AtkModuleVf22OpenAddonByAgentDelegate>.FromAddress(raptureAtkModuleVtable[22], this.AtkModuleVf22OpenAddonByAgentDetour);
|
this.atkModuleVf22OpenAddonByAgentHook = Hook<AtkModuleVf22OpenAddonByAgentDelegate>.FromAddress(raptureAtkModuleVtable[22], this.AtkModuleVf22OpenAddonByAgentDetour);
|
||||||
this.addonContextMenuOnMenuSelectedHook = Hook<AddonContextMenu.Delegates.OnMenuSelected>.FromAddress((nint)AddonContextMenu.StaticVirtualTablePointer->OnMenuSelected, this.AddonContextMenuOnMenuSelectedDetour);
|
this.addonContextMenuOnMenuSelectedHook = Hook<AddonContextMenuOnMenuSelectedDelegate>.FromAddress((nint)AddonContextMenu.StaticVirtualTablePointer->OnMenuSelected, this.AddonContextMenuOnMenuSelectedDetour);
|
||||||
|
|
||||||
this.atkModuleVf22OpenAddonByAgentHook.Enable();
|
this.atkModuleVf22OpenAddonByAgentHook.Enable();
|
||||||
this.addonContextMenuOnMenuSelectedHook.Enable();
|
this.addonContextMenuOnMenuSelectedHook.Enable();
|
||||||
|
|
@ -48,6 +48,10 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
|
|
||||||
private delegate ushort AtkModuleVf22OpenAddonByAgentDelegate(AtkModule* module, byte* addonName, int valueCount, AtkValue* values, AgentInterface* agent, nint a7, bool a8);
|
private delegate ushort AtkModuleVf22OpenAddonByAgentDelegate(AtkModule* module, byte* addonName, int valueCount, AtkValue* values, AgentInterface* agent, nint a7, bool a8);
|
||||||
|
|
||||||
|
private delegate bool AddonContextMenuOnMenuSelectedDelegate(AddonContextMenu* addon, int selectedIdx, byte a3);
|
||||||
|
|
||||||
|
private delegate ushort RaptureAtkModuleOpenAddonDelegate(RaptureAtkModule* a1, uint addonNameId, uint valueCount, AtkValue* values, AgentInterface* parentAgent, ulong unk, ushort parentAddonId, int unk2);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IContextMenu.OnMenuOpenedDelegate? OnMenuOpened;
|
public event IContextMenu.OnMenuOpenedDelegate? OnMenuOpened;
|
||||||
|
|
||||||
|
|
@ -181,7 +185,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
values[0].ChangeType(ValueType.UInt);
|
values[0].ChangeType(ValueType.UInt);
|
||||||
values[0].UInt = 0;
|
values[0].UInt = 0;
|
||||||
values[1].ChangeType(ValueType.String);
|
values[1].ChangeType(ValueType.String);
|
||||||
values[1].SetManagedString(name.EncodeWithNullTerminator());
|
values[1].SetManagedString(name.Encode().NullTerminate());
|
||||||
values[2].ChangeType(ValueType.Int);
|
values[2].ChangeType(ValueType.Int);
|
||||||
values[2].Int = x;
|
values[2].Int = x;
|
||||||
values[3].ChangeType(ValueType.Int);
|
values[3].ChangeType(ValueType.Int);
|
||||||
|
|
@ -261,7 +265,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
submenuMask |= 1u << i;
|
submenuMask |= 1u << i;
|
||||||
|
|
||||||
nameData[i].ChangeType(ValueType.String);
|
nameData[i].ChangeType(ValueType.String);
|
||||||
nameData[i].SetManagedString(this.GetPrefixedName(item).EncodeWithNullTerminator());
|
nameData[i].SetManagedString(this.GetPrefixedName(item).Encode().NullTerminate());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < prefixMenuSize; ++i)
|
for (var i = 0; i < prefixMenuSize; ++i)
|
||||||
|
|
@ -291,9 +295,8 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
// 2: UInt = Return Mask (?)
|
// 2: UInt = Return Mask (?)
|
||||||
// 3: UInt = Submenu Mask
|
// 3: UInt = Submenu Mask
|
||||||
// 4: UInt = OpenAtCursorPosition ? 2 : 1
|
// 4: UInt = OpenAtCursorPosition ? 2 : 1
|
||||||
// 5: UInt = ?
|
// 5: UInt = 0
|
||||||
// 6: UInt = ?
|
// 6: UInt = 0
|
||||||
// 7: UInt = ?
|
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
|
|
@ -309,7 +312,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.SetupGenericMenu(8, 0, 2, 3, items, ref valueCount, ref values);
|
this.SetupGenericMenu(7, 0, 2, 3, items, ref valueCount, ref values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupContextSubMenu(IReadOnlyList<IMenuItem> items, ref int valueCount, ref AtkValue* values)
|
private void SetupContextSubMenu(IReadOnlyList<IMenuItem> items, ref int valueCount, ref AtkValue* values)
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[Api15ToDo("Maybe make this config scoped to internal name?")]
|
[Api13ToDo("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,16 +92,34 @@ 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,5 +1,3 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui;
|
namespace Dalamud.Game.Gui;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -14,145 +14,140 @@ public enum HoverActionKind
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A regular action is hovered.
|
/// A regular action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Action = 29,
|
Action = 28,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A crafting action is hovered.
|
/// A crafting action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CraftingAction = 30,
|
CraftingAction = 29,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A general action is hovered.
|
/// A general action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
GeneralAction = 31,
|
GeneralAction = 30,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A companion order type of action is hovered.
|
/// A companion order type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CompanionOrder = 32, // Game Term: BuddyOrder
|
CompanionOrder = 31, // Game Term: BuddyOrder
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A main command type of action is hovered.
|
/// A main command type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MainCommand = 33,
|
MainCommand = 32,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An extras command type of action is hovered.
|
/// An extras command type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ExtraCommand = 34,
|
ExtraCommand = 33,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A companion action is hovered.
|
/// A companion action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Companion = 35,
|
Companion = 34,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A pet order type of action is hovered.
|
/// A pet order type of action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PetOrder = 36,
|
PetOrder = 35,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A trait is hovered.
|
/// A trait is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Trait = 37,
|
Trait = 36,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A buddy action is hovered.
|
/// A buddy action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BuddyAction = 38,
|
BuddyAction = 37,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A company action is hovered.
|
/// A company action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CompanyAction = 39,
|
CompanyAction = 38,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A mount is hovered.
|
/// A mount is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Mount = 40,
|
Mount = 39,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A chocobo race action is hovered.
|
/// A chocobo race action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ChocoboRaceAction = 41,
|
ChocoboRaceAction = 40,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A chocobo race item is hovered.
|
/// A chocobo race item is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ChocoboRaceItem = 42,
|
ChocoboRaceItem = 41,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A deep dungeon equipment is hovered.
|
/// A deep dungeon equipment is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DeepDungeonEquipment = 43,
|
DeepDungeonEquipment = 42,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A deep dungeon equipment 2 is hovered.
|
/// A deep dungeon equipment 2 is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DeepDungeonEquipment2 = 44,
|
DeepDungeonEquipment2 = 43,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A deep dungeon item is hovered.
|
/// A deep dungeon item is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DeepDungeonItem = 45,
|
DeepDungeonItem = 44,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A quick chat is hovered.
|
/// A quick chat is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
QuickChat = 46,
|
QuickChat = 45,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action combo route is hovered.
|
/// An action combo route is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ActionComboRoute = 47,
|
ActionComboRoute = 46,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A pvp trait is hovered.
|
/// A pvp trait is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PvPSelectTrait = 48,
|
PvPSelectTrait = 47,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A squadron action is hovered.
|
/// A squadron action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BgcArmyAction = 49,
|
BgcArmyAction = 48,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A perform action is hovered.
|
/// A perform action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Perform = 50,
|
Perform = 49,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A deep dungeon magic stone is hovered.
|
/// A deep dungeon magic stone is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DeepDungeonMagicStone = 51,
|
DeepDungeonMagicStone = 50,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A deep dungeon demiclone is hovered.
|
/// A deep dungeon demiclone is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DeepDungeonDemiclone = 52,
|
DeepDungeonDemiclone = 51,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An eureka magia action is hovered.
|
/// An eureka magia action is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EurekaMagiaAction = 53,
|
EurekaMagiaAction = 52,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An island sanctuary temporary item is hovered.
|
/// An island sanctuary temporary item is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MYCTemporaryItem = 54,
|
MYCTemporaryItem = 53,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An ornament is hovered.
|
/// An ornament is hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Ornament = 55,
|
Ornament = 54,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Glasses are hovered.
|
/// Glasses are hovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Glasses = 56,
|
Glasses = 55,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Phantom Job Trait is hovered.
|
|
||||||
/// </summary>
|
|
||||||
MKDTrait = 58,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.NamePlate;
|
namespace Dalamud.Game.Gui.NamePlate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Component.Completion;
|
using FFXIVClientStructs.FFXIV.Component.Completion;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
using Lumina.Text;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal;
|
namespace Dalamud.Game.Internal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -251,14 +253,16 @@ internal sealed unsafe class DalamudCompletion : IInternalDisposableService
|
||||||
{
|
{
|
||||||
public EntryStrings(string command)
|
public EntryStrings(string command)
|
||||||
{
|
{
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var rssb = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
this.Display = Utf8String.FromSequence(rssb.Builder
|
this.Display = Utf8String.FromSequence(rssb
|
||||||
.PushColorType(539)
|
.PushColorType(539)
|
||||||
.Append(command)
|
.Append(command)
|
||||||
.PopColorType()
|
.PopColorType()
|
||||||
.GetViewAsSpan());
|
.GetViewAsSpan());
|
||||||
|
|
||||||
|
SeStringBuilder.SharedPool.Return(rssb);
|
||||||
|
|
||||||
this.Match = Utf8String.FromString(command);
|
this.Match = Utf8String.FromString(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -305,8 +305,7 @@ internal class GameInventory : IInternalDisposableService
|
||||||
private GameInventoryItem[] CreateItemsArray(int length)
|
private GameInventoryItem[] CreateItemsArray(int length)
|
||||||
{
|
{
|
||||||
var items = new GameInventoryItem[length];
|
var items = new GameInventoryItem[length];
|
||||||
foreach (ref var item in items.AsSpan())
|
items.Initialize();
|
||||||
item = new();
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network;
|
namespace Dalamud.Game.Network;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using Dalamud.Plugin.Services;
|
namespace Dalamud.Game.Network.Internal;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Internal;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal address resolver for the network handlers.
|
/// Internal address resolver for the network handlers.
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ 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,9 +1,8 @@
|
||||||
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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,15 +102,16 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
// TODO: remove culture info toggling after supporting CultureInfo for SeStringBuilder.Append,
|
// TODO: remove culture info toggling after supporting CultureInfo for SeStringBuilder.Append,
|
||||||
// and then remove try...finally block (discard builder from the pool on exception)
|
// and then remove try...finally block (discard builder from the pool on exception)
|
||||||
var previousCulture = CultureInfo.CurrentCulture;
|
var previousCulture = CultureInfo.CurrentCulture;
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = SeStringBuilder.SharedPool.Get();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CultureInfo.CurrentCulture = Localization.GetCultureInfoFromLangCode(lang.ToCode());
|
CultureInfo.CurrentCulture = Localization.GetCultureInfoFromLangCode(lang.ToCode());
|
||||||
return this.EvaluateAndAppendTo(rssb.Builder, str, localParameters, lang).ToReadOnlySeString();
|
return this.EvaluateAndAppendTo(builder, str, localParameters, lang).ToReadOnlySeString();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
CultureInfo.CurrentCulture = previousCulture;
|
CultureInfo.CurrentCulture = previousCulture;
|
||||||
|
SeStringBuilder.SharedPool.Return(builder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -929,8 +930,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
itemId += 1000000;
|
itemId += 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var sb = SeStringBuilder.SharedPool.Get();
|
||||||
var sb = rssb.Builder;
|
|
||||||
|
|
||||||
sb.Append(this.EvaluateFromAddon(6, [rarity], context.Language));
|
sb.Append(this.EvaluateFromAddon(6, [rarity], context.Language));
|
||||||
|
|
||||||
|
|
@ -956,6 +956,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
sb.PopLink();
|
sb.PopLink();
|
||||||
|
|
||||||
text = sb.ToReadOnlySeString();
|
text = sb.ToReadOnlySeString();
|
||||||
|
SeStringBuilder.SharedPool.Return(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateSheetLink(in SeStringContext context, string resolvedSheetName, ReadOnlySeString text, uint eRowIdValue, uint eColParamValue)
|
private void CreateSheetLink(in SeStringContext context, string resolvedSheetName, ReadOnlySeString text, uint eRowIdValue, uint eColParamValue)
|
||||||
|
|
@ -1027,33 +1028,40 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!payload.TryGetExpression(out var eStr))
|
if (!payload.TryGetExpression(out var eStr))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
var headContext = new SeStringContext(rssb.Builder, context.LocalParameters, context.Language);
|
try
|
||||||
|
|
||||||
if (!this.ResolveStringExpression(headContext, eStr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var str = rssb.Builder.ToReadOnlySeString();
|
|
||||||
var pIdx = 0;
|
|
||||||
|
|
||||||
foreach (var p in str)
|
|
||||||
{
|
{
|
||||||
pIdx++;
|
var headContext = new SeStringContext(builder, context.LocalParameters, context.Language);
|
||||||
|
|
||||||
if (p.Type == ReadOnlySePayloadType.Invalid)
|
if (!this.ResolveStringExpression(headContext, eStr))
|
||||||
continue;
|
return false;
|
||||||
|
|
||||||
if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text)
|
var str = builder.ToReadOnlySeString();
|
||||||
|
var pIdx = 0;
|
||||||
|
|
||||||
|
foreach (var p in str)
|
||||||
{
|
{
|
||||||
context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToUpper(context.CultureInfo));
|
pIdx++;
|
||||||
continue;
|
|
||||||
|
if (p.Type == ReadOnlySePayloadType.Invalid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text)
|
||||||
|
{
|
||||||
|
context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToUpper(context.CultureInfo));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Builder.Append(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Builder.Append(p);
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SeStringBuilder.SharedPool.Return(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveHead(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
private bool TryResolveHead(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||||
|
|
@ -1061,33 +1069,40 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!payload.TryGetExpression(out var eStr))
|
if (!payload.TryGetExpression(out var eStr))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
var headContext = new SeStringContext(rssb.Builder, context.LocalParameters, context.Language);
|
try
|
||||||
|
|
||||||
if (!this.ResolveStringExpression(headContext, eStr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var str = rssb.Builder.ToReadOnlySeString();
|
|
||||||
var pIdx = 0;
|
|
||||||
|
|
||||||
foreach (var p in str)
|
|
||||||
{
|
{
|
||||||
pIdx++;
|
var headContext = new SeStringContext(builder, context.LocalParameters, context.Language);
|
||||||
|
|
||||||
if (p.Type == ReadOnlySePayloadType.Invalid)
|
if (!this.ResolveStringExpression(headContext, eStr))
|
||||||
continue;
|
return false;
|
||||||
|
|
||||||
if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text)
|
var str = builder.ToReadOnlySeString();
|
||||||
|
var pIdx = 0;
|
||||||
|
|
||||||
|
foreach (var p in str)
|
||||||
{
|
{
|
||||||
context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToUpper(context.CultureInfo));
|
pIdx++;
|
||||||
continue;
|
|
||||||
|
if (p.Type == ReadOnlySePayloadType.Invalid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text)
|
||||||
|
{
|
||||||
|
context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToUpper(context.CultureInfo));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Builder.Append(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Builder.Append(p);
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SeStringBuilder.SharedPool.Return(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveSplit(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
private bool TryResolveSplit(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||||
|
|
@ -1098,25 +1113,32 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!eSeparator.TryGetString(out var eSeparatorVal) || !eIndex.TryGetUInt(out var eIndexVal) || eIndexVal <= 0)
|
if (!eSeparator.TryGetString(out var eSeparatorVal) || !eIndex.TryGetUInt(out var eIndexVal) || eIndexVal <= 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
var headContext = new SeStringContext(rssb.Builder, context.LocalParameters, context.Language);
|
try
|
||||||
|
|
||||||
if (!this.ResolveStringExpression(headContext, eText))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var separator = eSeparatorVal.ExtractText();
|
|
||||||
if (separator.Length < 1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var splitted = rssb.Builder.ToReadOnlySeString().ExtractText().Split(separator[0]);
|
|
||||||
if (eIndexVal <= splitted.Length)
|
|
||||||
{
|
{
|
||||||
context.Builder.Append(splitted[eIndexVal - 1]);
|
var headContext = new SeStringContext(builder, context.LocalParameters, context.Language);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
if (!this.ResolveStringExpression(headContext, eText))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var separator = eSeparatorVal.ExtractText();
|
||||||
|
if (separator.Length < 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var splitted = builder.ToReadOnlySeString().ExtractText().Split(separator[0]);
|
||||||
|
if (eIndexVal <= splitted.Length)
|
||||||
|
{
|
||||||
|
context.Builder.Append(splitted[eIndexVal - 1]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SeStringBuilder.SharedPool.Return(builder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveHeadAll(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
private bool TryResolveHeadAll(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||||
|
|
@ -1124,30 +1146,37 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!payload.TryGetExpression(out var eStr))
|
if (!payload.TryGetExpression(out var eStr))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
var headContext = new SeStringContext(rssb.Builder, context.LocalParameters, context.Language);
|
try
|
||||||
|
|
||||||
if (!this.ResolveStringExpression(headContext, eStr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var str = rssb.Builder.ToReadOnlySeString();
|
|
||||||
|
|
||||||
foreach (var p in str)
|
|
||||||
{
|
{
|
||||||
if (p.Type == ReadOnlySePayloadType.Invalid)
|
var headContext = new SeStringContext(builder, context.LocalParameters, context.Language);
|
||||||
continue;
|
|
||||||
|
|
||||||
if (p.Type == ReadOnlySePayloadType.Text)
|
if (!this.ResolveStringExpression(headContext, eStr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var str = builder.ToReadOnlySeString();
|
||||||
|
|
||||||
|
foreach (var p in str)
|
||||||
{
|
{
|
||||||
context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).ToUpper(true, true, false, context.Language));
|
if (p.Type == ReadOnlySePayloadType.Invalid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (p.Type == ReadOnlySePayloadType.Text)
|
||||||
|
{
|
||||||
|
context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).ToUpper(true, true, false, context.Language));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Builder.Append(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Builder.Append(p);
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SeStringBuilder.SharedPool.Return(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveFixed(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
private bool TryResolveFixed(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||||
|
|
@ -1277,13 +1306,14 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!this.dataManager.GetExcelSheet<Lumina.Excel.Sheets.Map>().TryGetRow(mapId, out var mapRow))
|
if (!this.dataManager.GetExcelSheet<Lumina.Excel.Sheets.Map>().TryGetRow(mapId, out var mapRow))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var sb = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
rssb.Builder.Append(placeNameRow.Name);
|
sb.Append(placeNameRow.Name);
|
||||||
if (instance is > 0 and <= 9)
|
if (instance is > 0 and <= 9)
|
||||||
rssb.Builder.Append((char)((char)0xE0B0 + (char)instance));
|
sb.Append((char)((char)0xE0B0 + (char)instance));
|
||||||
|
|
||||||
var placeNameWithInstance = rssb.Builder.ToReadOnlySeString();
|
var placeNameWithInstance = sb.ToReadOnlySeString();
|
||||||
|
SeStringBuilder.SharedPool.Return(sb);
|
||||||
|
|
||||||
var mapPosX = ConvertRawToMapPosX(mapRow, rawX / 1000f);
|
var mapPosX = ConvertRawToMapPosX(mapRow, rawX / 1000f);
|
||||||
var mapPosY = ConvertRawToMapPosY(mapRow, rawY / 1000f);
|
var mapPosY = ConvertRawToMapPosY(mapRow, rawY / 1000f);
|
||||||
|
|
@ -1432,22 +1462,23 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
statusDescription = statusRow.Description.AsSpan();
|
statusDescription = statusRow.Description.AsSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var sb = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
switch (statusRow.StatusCategory)
|
switch (statusRow.StatusCategory)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
rssb.Builder.Append(this.EvaluateFromAddon(376, default, context.Language));
|
sb.Append(this.EvaluateFromAddon(376, default, context.Language));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
rssb.Builder.Append(this.EvaluateFromAddon(377, default, context.Language));
|
sb.Append(this.EvaluateFromAddon(377, default, context.Language));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
rssb.Builder.Append(statusName);
|
sb.Append(statusName);
|
||||||
|
|
||||||
var linkText = rssb.Builder.ToReadOnlySeString();
|
var linkText = sb.ToReadOnlySeString();
|
||||||
|
SeStringBuilder.SharedPool.Return(sb);
|
||||||
|
|
||||||
context.Builder
|
context.Builder
|
||||||
.BeginMacro(MacroCode.Link)
|
.BeginMacro(MacroCode.Link)
|
||||||
|
|
@ -1702,31 +1733,38 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!payload.TryGetExpression(out var eStr))
|
if (!payload.TryGetExpression(out var eStr))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
var headContext = new SeStringContext(rssb.Builder, context.LocalParameters, context.Language);
|
try
|
||||||
|
|
||||||
if (!this.ResolveStringExpression(headContext, eStr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var str = rssb.Builder.ToReadOnlySeString();
|
|
||||||
|
|
||||||
foreach (var p in str)
|
|
||||||
{
|
{
|
||||||
if (p.Type == ReadOnlySePayloadType.Invalid)
|
var headContext = new SeStringContext(builder, context.LocalParameters, context.Language);
|
||||||
continue;
|
|
||||||
|
|
||||||
if (p.Type == ReadOnlySePayloadType.Text)
|
if (!this.ResolveStringExpression(headContext, eStr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var str = builder.ToReadOnlySeString();
|
||||||
|
|
||||||
|
foreach (var p in str)
|
||||||
{
|
{
|
||||||
context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToLower(context.CultureInfo));
|
if (p.Type == ReadOnlySePayloadType.Invalid)
|
||||||
|
continue;
|
||||||
|
|
||||||
continue;
|
if (p.Type == ReadOnlySePayloadType.Text)
|
||||||
|
{
|
||||||
|
context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToLower(context.CultureInfo));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Builder.Append(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Builder.Append(p);
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SeStringBuilder.SharedPool.Return(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveNoun(ClientLanguage language, in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
private bool TryResolveNoun(ClientLanguage language, in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||||
|
|
@ -1796,33 +1834,40 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!payload.TryGetExpression(out var eStr))
|
if (!payload.TryGetExpression(out var eStr))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = SeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
var headContext = new SeStringContext(rssb.Builder, context.LocalParameters, context.Language);
|
try
|
||||||
|
|
||||||
if (!this.ResolveStringExpression(headContext, eStr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var str = rssb.Builder.ToReadOnlySeString();
|
|
||||||
var pIdx = 0;
|
|
||||||
|
|
||||||
foreach (var p in str)
|
|
||||||
{
|
{
|
||||||
pIdx++;
|
var headContext = new SeStringContext(builder, context.LocalParameters, context.Language);
|
||||||
|
|
||||||
if (p.Type == ReadOnlySePayloadType.Invalid)
|
if (!this.ResolveStringExpression(headContext, eStr))
|
||||||
continue;
|
return false;
|
||||||
|
|
||||||
if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text)
|
var str = builder.ToReadOnlySeString();
|
||||||
|
var pIdx = 0;
|
||||||
|
|
||||||
|
foreach (var p in str)
|
||||||
{
|
{
|
||||||
context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToLower(context.CultureInfo));
|
pIdx++;
|
||||||
continue;
|
|
||||||
|
if (p.Type == ReadOnlySePayloadType.Invalid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text)
|
||||||
|
{
|
||||||
|
context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToLower(context.CultureInfo));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Builder.Append(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Builder.Append(p);
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SeStringBuilder.SharedPool.Return(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveColorType(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
private bool TryResolveColorType(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||||
|
|
@ -2087,19 +2132,19 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
|
|
||||||
if (operand1.TryGetString(out var strval1) && operand2.TryGetString(out var strval2))
|
if (operand1.TryGetString(out var strval1) && operand2.TryGetString(out var strval2))
|
||||||
{
|
{
|
||||||
using var rssb1 = new RentedSeStringBuilder();
|
|
||||||
using var rssb2 = new RentedSeStringBuilder();
|
|
||||||
var resolvedStr1 = this.EvaluateAndAppendTo(
|
var resolvedStr1 = this.EvaluateAndAppendTo(
|
||||||
rssb1.Builder,
|
SeStringBuilder.SharedPool.Get(),
|
||||||
strval1,
|
strval1,
|
||||||
context.LocalParameters,
|
context.LocalParameters,
|
||||||
context.Language);
|
context.Language);
|
||||||
var resolvedStr2 = this.EvaluateAndAppendTo(
|
var resolvedStr2 = this.EvaluateAndAppendTo(
|
||||||
rssb2.Builder,
|
SeStringBuilder.SharedPool.Get(),
|
||||||
strval2,
|
strval2,
|
||||||
context.LocalParameters,
|
context.LocalParameters,
|
||||||
context.Language);
|
context.Language);
|
||||||
var equals = resolvedStr1.GetViewAsSpan().SequenceEqual(resolvedStr2.GetViewAsSpan());
|
var equals = resolvedStr1.GetViewAsSpan().SequenceEqual(resolvedStr2.GetViewAsSpan());
|
||||||
|
SeStringBuilder.SharedPool.Return(resolvedStr1);
|
||||||
|
SeStringBuilder.SharedPool.Return(resolvedStr2);
|
||||||
|
|
||||||
if ((ExpressionType)exprType == ExpressionType.Equal)
|
if ((ExpressionType)exprType == ExpressionType.Equal)
|
||||||
value = equals ? 1u : 0u;
|
value = equals ? 1u : 0u;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ 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;
|
||||||
|
|
||||||
|
|
@ -70,6 +71,9 @@ 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);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Dalamud.Utility;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||||
using LSheets = Lumina.Excel.Sheets;
|
using LSheets = Lumina.Excel.Sheets;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.Noun;
|
namespace Dalamud.Game.Text.Noun;
|
||||||
|
|
@ -146,28 +147,30 @@ internal class NounProcessor : IServiceType
|
||||||
|
|
||||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = LSeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
// Ko-So-A-Do
|
// Ko-So-A-Do
|
||||||
var ksad = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(nounParams.Quantity > 1 ? 1 : 0);
|
var ksad = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(nounParams.Quantity > 1 ? 1 : 0);
|
||||||
if (!ksad.IsEmpty)
|
if (!ksad.IsEmpty)
|
||||||
{
|
{
|
||||||
rssb.Builder.Append(ksad);
|
builder.Append(ksad);
|
||||||
|
|
||||||
if (nounParams.Quantity > 1)
|
if (nounParams.Quantity > 1)
|
||||||
{
|
{
|
||||||
rssb.Builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nounParams.LinkMarker.IsEmpty)
|
if (!nounParams.LinkMarker.IsEmpty)
|
||||||
rssb.Builder.Append(nounParams.LinkMarker);
|
builder.Append(nounParams.LinkMarker);
|
||||||
|
|
||||||
var text = row.ReadStringColumn(nounParams.ColumnOffset);
|
var text = row.ReadStringColumn(nounParams.ColumnOffset);
|
||||||
if (!text.IsEmpty)
|
if (!text.IsEmpty)
|
||||||
rssb.Builder.Append(text);
|
builder.Append(text);
|
||||||
|
|
||||||
return rssb.Builder.ToReadOnlySeString();
|
var ross = builder.ToReadOnlySeString();
|
||||||
|
LSeStringBuilder.SharedPool.Return(builder);
|
||||||
|
return ross;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -197,7 +200,7 @@ internal class NounProcessor : IServiceType
|
||||||
|
|
||||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = LSeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
var isProperNounColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
var isProperNounColumn = nounParams.ColumnOffset + ArticleColumnIdx;
|
||||||
var isProperNoun = isProperNounColumn >= 0 ? row.ReadInt8Column(isProperNounColumn) : ~isProperNounColumn;
|
var isProperNoun = isProperNounColumn >= 0 ? row.ReadInt8Column(isProperNounColumn) : ~isProperNounColumn;
|
||||||
|
|
@ -213,19 +216,21 @@ internal class NounProcessor : IServiceType
|
||||||
var article = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
var article = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
||||||
.ReadStringColumn(articleColumn + grammaticalNumberColumnOffset);
|
.ReadStringColumn(articleColumn + grammaticalNumberColumnOffset);
|
||||||
if (!article.IsEmpty)
|
if (!article.IsEmpty)
|
||||||
rssb.Builder.Append(article);
|
builder.Append(article);
|
||||||
|
|
||||||
if (!nounParams.LinkMarker.IsEmpty)
|
if (!nounParams.LinkMarker.IsEmpty)
|
||||||
rssb.Builder.Append(nounParams.LinkMarker);
|
builder.Append(nounParams.LinkMarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx));
|
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||||
if (!text.IsEmpty)
|
if (!text.IsEmpty)
|
||||||
rssb.Builder.Append(text);
|
builder.Append(text);
|
||||||
|
|
||||||
rssb.Builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||||
|
|
||||||
return rssb.Builder.ToReadOnlySeString();
|
var ross = builder.ToReadOnlySeString();
|
||||||
|
LSeStringBuilder.SharedPool.Return(builder);
|
||||||
|
return ross;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -257,13 +262,17 @@ internal class NounProcessor : IServiceType
|
||||||
|
|
||||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = LSeStringBuilder.SharedPool.Get();
|
||||||
|
ReadOnlySeString ross;
|
||||||
|
|
||||||
if (nounParams.IsActionSheet)
|
if (nounParams.IsActionSheet)
|
||||||
{
|
{
|
||||||
rssb.Builder.Append(row.ReadStringColumn(nounParams.GrammaticalCase));
|
builder.Append(row.ReadStringColumn(nounParams.GrammaticalCase));
|
||||||
rssb.Builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||||
return rssb.Builder.ToReadOnlySeString();
|
|
||||||
|
ross = builder.ToReadOnlySeString();
|
||||||
|
LSeStringBuilder.SharedPool.Return(builder);
|
||||||
|
return ross;
|
||||||
}
|
}
|
||||||
|
|
||||||
var genderIndexColumn = nounParams.ColumnOffset + PronounColumnIdx;
|
var genderIndexColumn = nounParams.ColumnOffset + PronounColumnIdx;
|
||||||
|
|
@ -293,32 +302,35 @@ internal class NounProcessor : IServiceType
|
||||||
var grammaticalGender = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
var grammaticalGender = attributiveSheet.GetRow((uint)nounParams.ArticleType)
|
||||||
.ReadStringColumn(caseColumnOffset + genderIndex); // Genus
|
.ReadStringColumn(caseColumnOffset + genderIndex); // Genus
|
||||||
if (!grammaticalGender.IsEmpty)
|
if (!grammaticalGender.IsEmpty)
|
||||||
rssb.Builder.Append(grammaticalGender);
|
builder.Append(grammaticalGender);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nounParams.LinkMarker.IsEmpty)
|
if (!nounParams.LinkMarker.IsEmpty)
|
||||||
rssb.Builder.Append(nounParams.LinkMarker);
|
builder.Append(nounParams.LinkMarker);
|
||||||
|
|
||||||
rssb.Builder.Append(text);
|
builder.Append(text);
|
||||||
|
|
||||||
var plural = attributiveSheet.GetRow((uint)(caseRowOffset + 26))
|
var plural = attributiveSheet.GetRow((uint)(caseRowOffset + 26))
|
||||||
.ReadStringColumn(caseColumnOffset + genderIndex);
|
.ReadStringColumn(caseColumnOffset + genderIndex);
|
||||||
if (rssb.Builder.ContainsText("[p]"u8))
|
if (builder.ContainsText("[p]"u8))
|
||||||
rssb.Builder.ReplaceText("[p]"u8, plural);
|
builder.ReplaceText("[p]"u8, plural);
|
||||||
else
|
else
|
||||||
rssb.Builder.Append(plural);
|
builder.Append(plural);
|
||||||
|
|
||||||
if (hasT)
|
if (hasT)
|
||||||
{
|
{
|
||||||
var article =
|
var article =
|
||||||
attributiveSheet.GetRow(39).ReadStringColumn(caseColumnOffset + genderIndex); // Definiter Artikel
|
attributiveSheet.GetRow(39).ReadStringColumn(caseColumnOffset + genderIndex); // Definiter Artikel
|
||||||
rssb.Builder.ReplaceText("[t]"u8, article);
|
builder.ReplaceText("[t]"u8, article);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rssb.Builder.ReplaceText("[pa]"u8, attributiveSheet.GetRow(24).ReadStringColumn(caseColumnOffset + genderIndex));
|
var pa = attributiveSheet.GetRow(24).ReadStringColumn(caseColumnOffset + genderIndex);
|
||||||
|
builder.ReplaceText("[pa]"u8, pa);
|
||||||
|
|
||||||
var declensionRow = (GermanArticleType)nounParams.ArticleType switch
|
RawRow declensionRow;
|
||||||
|
|
||||||
|
declensionRow = (GermanArticleType)nounParams.ArticleType switch
|
||||||
{
|
{
|
||||||
// Schwache Flexion eines Adjektivs?!
|
// Schwache Flexion eines Adjektivs?!
|
||||||
GermanArticleType.Possessive or GermanArticleType.Demonstrative => attributiveSheet.GetRow(25),
|
GermanArticleType.Possessive or GermanArticleType.Demonstrative => attributiveSheet.GetRow(25),
|
||||||
|
|
@ -335,10 +347,14 @@ internal class NounProcessor : IServiceType
|
||||||
_ => attributiveSheet.GetRow(26),
|
_ => attributiveSheet.GetRow(26),
|
||||||
};
|
};
|
||||||
|
|
||||||
rssb.Builder.ReplaceText("[a]"u8, declensionRow.ReadStringColumn(caseColumnOffset + genderIndex));
|
var declension = declensionRow.ReadStringColumn(caseColumnOffset + genderIndex);
|
||||||
rssb.Builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
builder.ReplaceText("[a]"u8, declension);
|
||||||
|
|
||||||
return rssb.Builder.ToReadOnlySeString();
|
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||||
|
|
||||||
|
ross = builder.ToReadOnlySeString();
|
||||||
|
LSeStringBuilder.SharedPool.Return(builder);
|
||||||
|
return ross;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -369,7 +385,8 @@ internal class NounProcessor : IServiceType
|
||||||
|
|
||||||
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
var attributiveSheet = this.dataManager.Excel.GetSheet<RawRow>(nounParams.Language.ToLumina(), nameof(LSheets.Attributive));
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var builder = LSeStringBuilder.SharedPool.Get();
|
||||||
|
ReadOnlySeString ross;
|
||||||
|
|
||||||
var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx;
|
var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx;
|
||||||
var startsWithVowel = startsWithVowelColumn >= 0
|
var startsWithVowel = startsWithVowelColumn >= 0
|
||||||
|
|
@ -388,19 +405,21 @@ internal class NounProcessor : IServiceType
|
||||||
{
|
{
|
||||||
var v21 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20);
|
var v21 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20);
|
||||||
if (!v21.IsEmpty)
|
if (!v21.IsEmpty)
|
||||||
rssb.Builder.Append(v21);
|
builder.Append(v21);
|
||||||
|
|
||||||
if (!nounParams.LinkMarker.IsEmpty)
|
if (!nounParams.LinkMarker.IsEmpty)
|
||||||
rssb.Builder.Append(nounParams.LinkMarker);
|
builder.Append(nounParams.LinkMarker);
|
||||||
|
|
||||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity <= 1 ? SingularColumnIdx : PluralColumnIdx));
|
var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity <= 1 ? SingularColumnIdx : PluralColumnIdx));
|
||||||
if (!text.IsEmpty)
|
if (!text.IsEmpty)
|
||||||
rssb.Builder.Append(text);
|
builder.Append(text);
|
||||||
|
|
||||||
if (nounParams.Quantity <= 1)
|
if (nounParams.Quantity <= 1)
|
||||||
rssb.Builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||||
|
|
||||||
return rssb.Builder.ToReadOnlySeString();
|
ross = builder.ToReadOnlySeString();
|
||||||
|
LSeStringBuilder.SharedPool.Return(builder);
|
||||||
|
return ross;
|
||||||
}
|
}
|
||||||
|
|
||||||
var v17 = row.ReadInt8Column(nounParams.ColumnOffset + Unknown5ColumnIdx);
|
var v17 = row.ReadInt8Column(nounParams.ColumnOffset + Unknown5ColumnIdx);
|
||||||
|
|
@ -409,32 +428,34 @@ internal class NounProcessor : IServiceType
|
||||||
var v29 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + 2);
|
var v29 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + 2);
|
||||||
if (!v29.IsEmpty)
|
if (!v29.IsEmpty)
|
||||||
{
|
{
|
||||||
rssb.Builder.Append(v29);
|
builder.Append(v29);
|
||||||
|
|
||||||
if (!nounParams.LinkMarker.IsEmpty)
|
if (!nounParams.LinkMarker.IsEmpty)
|
||||||
rssb.Builder.Append(nounParams.LinkMarker);
|
builder.Append(nounParams.LinkMarker);
|
||||||
|
|
||||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + PluralColumnIdx);
|
var text = row.ReadStringColumn(nounParams.ColumnOffset + PluralColumnIdx);
|
||||||
if (!text.IsEmpty)
|
if (!text.IsEmpty)
|
||||||
rssb.Builder.Append(text);
|
builder.Append(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var v27 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + (v17 != 0 ? 1 : 3));
|
var v27 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + (v17 != 0 ? 1 : 3));
|
||||||
if (!v27.IsEmpty)
|
if (!v27.IsEmpty)
|
||||||
rssb.Builder.Append(v27);
|
builder.Append(v27);
|
||||||
|
|
||||||
if (!nounParams.LinkMarker.IsEmpty)
|
if (!nounParams.LinkMarker.IsEmpty)
|
||||||
rssb.Builder.Append(nounParams.LinkMarker);
|
builder.Append(nounParams.LinkMarker);
|
||||||
|
|
||||||
var text = row.ReadStringColumn(nounParams.ColumnOffset + SingularColumnIdx);
|
var text = row.ReadStringColumn(nounParams.ColumnOffset + SingularColumnIdx);
|
||||||
if (!text.IsEmpty)
|
if (!text.IsEmpty)
|
||||||
rssb.Builder.Append(text);
|
builder.Append(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
rssb.Builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString()));
|
||||||
|
|
||||||
return rssb.Builder.ToReadOnlySeString();
|
ross = builder.ToReadOnlySeString();
|
||||||
|
LSeStringBuilder.SharedPool.Return(builder);
|
||||||
|
return ross;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Dalamud.Game.Text.Evaluator;
|
using Dalamud.Game.Text.Evaluator;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
@ -33,14 +32,13 @@ public class AutoTranslatePayload : Payload, ITextProvider
|
||||||
this.Group = group;
|
this.Group = group;
|
||||||
this.Key = key;
|
this.Key = key;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get();
|
||||||
|
this.payload = ssb.BeginMacro(MacroCode.Fixed)
|
||||||
this.payload = rssb.Builder
|
.AppendUIntExpression(group - 1)
|
||||||
.BeginMacro(MacroCode.Fixed)
|
.AppendUIntExpression(key)
|
||||||
.AppendUIntExpression(group - 1)
|
.EndMacro()
|
||||||
.AppendUIntExpression(key)
|
.ToReadOnlySeString();
|
||||||
.EndMacro()
|
Lumina.Text.SeStringBuilder.SharedPool.Return(ssb);
|
||||||
.ToReadOnlySeString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
|
@ -39,18 +37,19 @@ public class DalamudLinkPayload : Payload
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override byte[] EncodeImpl()
|
protected override byte[] EncodeImpl()
|
||||||
{
|
{
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get();
|
||||||
return rssb.Builder
|
var res = ssb.BeginMacro(MacroCode.Link)
|
||||||
.BeginMacro(MacroCode.Link)
|
.AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1)
|
||||||
.AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1)
|
.AppendUIntExpression(this.CommandId)
|
||||||
.AppendUIntExpression(this.CommandId)
|
.AppendIntExpression(this.Extra1)
|
||||||
.AppendIntExpression(this.Extra1)
|
.AppendIntExpression(this.Extra2)
|
||||||
.AppendIntExpression(this.Extra2)
|
.BeginStringExpression()
|
||||||
.BeginStringExpression()
|
.Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString }))
|
||||||
.Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString }))
|
.EndExpression()
|
||||||
.EndExpression()
|
.EndMacro()
|
||||||
.EndMacro()
|
.ToArray();
|
||||||
.ToArray();
|
Lumina.Text.SeStringBuilder.SharedPool.Return(ssb);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
@ -86,12 +87,14 @@ public class PlayerPayload : Payload
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override byte[] EncodeImpl()
|
protected override byte[] EncodeImpl()
|
||||||
{
|
{
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get();
|
||||||
return rssb.Builder
|
var res = ssb
|
||||||
.PushLinkCharacter(this.playerName, this.serverId)
|
.PushLinkCharacter(this.playerName, this.serverId)
|
||||||
.Append(this.playerName)
|
.Append(this.playerName)
|
||||||
.PopLink()
|
.PopLink()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
Lumina.Text.SeStringBuilder.SharedPool.Return(ssb);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,14 @@ 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>
|
||||||
|
|
@ -198,9 +206,8 @@ public class SeString
|
||||||
var textColor = ItemUtil.GetItemRarityColorType(rawId);
|
var textColor = ItemUtil.GetItemRarityColorType(rawId);
|
||||||
var textEdgeColor = textColor + 1u;
|
var textEdgeColor = textColor + 1u;
|
||||||
|
|
||||||
using var rssb = new RentedSeStringBuilder();
|
var sb = LSeStringBuilder.SharedPool.Get();
|
||||||
|
var itemLink = sb
|
||||||
var itemLink = rssb.Builder
|
|
||||||
.PushColorType(textColor)
|
.PushColorType(textColor)
|
||||||
.PushEdgeColorType(textEdgeColor)
|
.PushEdgeColorType(textEdgeColor)
|
||||||
.PushLinkItem(rawId, copyName)
|
.PushLinkItem(rawId, copyName)
|
||||||
|
|
@ -209,6 +216,7 @@ public class SeString
|
||||||
.PopEdgeColorType()
|
.PopEdgeColorType()
|
||||||
.PopColorType()
|
.PopColorType()
|
||||||
.ToReadOnlySeString();
|
.ToReadOnlySeString();
|
||||||
|
LSeStringBuilder.SharedPool.Return(sb);
|
||||||
|
|
||||||
return SeString.Parse(seStringEvaluator.EvaluateFromAddon(371, [itemLink], clientState.ClientLanguage));
|
return SeString.Parse(seStringEvaluator.EvaluateFromAddon(371, [itemLink], clientState.ClientLanguage));
|
||||||
}
|
}
|
||||||
|
|
@ -250,12 +258,16 @@ public class SeString
|
||||||
var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY);
|
var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY);
|
||||||
var nameString = GetMapLinkNameString(mapPayload.PlaceName, instance, mapPayload.CoordinateString);
|
var nameString = GetMapLinkNameString(mapPayload.PlaceName, instance, mapPayload.CoordinateString);
|
||||||
|
|
||||||
return new SeString(new List<Payload>([
|
var payloads = new List<Payload>(new Payload[]
|
||||||
|
{
|
||||||
mapPayload,
|
mapPayload,
|
||||||
..TextArrowPayloads,
|
// arrow goes here
|
||||||
new TextPayload(nameString),
|
new TextPayload(nameString),
|
||||||
RawPayload.LinkTerminator,
|
RawPayload.LinkTerminator,
|
||||||
]));
|
});
|
||||||
|
payloads.InsertRange(1, TextArrowPayloads);
|
||||||
|
|
||||||
|
return new SeString(payloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -286,12 +298,16 @@ public class SeString
|
||||||
var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor);
|
var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor);
|
||||||
var nameString = GetMapLinkNameString(mapPayload.PlaceName, instance, mapPayload.CoordinateString);
|
var nameString = GetMapLinkNameString(mapPayload.PlaceName, instance, mapPayload.CoordinateString);
|
||||||
|
|
||||||
return new SeString(new List<Payload>([
|
var payloads = new List<Payload>(new Payload[]
|
||||||
|
{
|
||||||
mapPayload,
|
mapPayload,
|
||||||
..TextArrowPayloads,
|
// arrow goes here
|
||||||
new TextPayload(nameString),
|
new TextPayload(nameString),
|
||||||
RawPayload.LinkTerminator,
|
RawPayload.LinkTerminator,
|
||||||
]));
|
});
|
||||||
|
payloads.InsertRange(1, TextArrowPayloads);
|
||||||
|
|
||||||
|
return new SeString(payloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -347,15 +363,21 @@ public class SeString
|
||||||
/// <returns>An SeString containing all the payloads necessary to display a party finder link in the chat log.</returns>
|
/// <returns>An SeString containing all the payloads necessary to display a party finder link in the chat log.</returns>
|
||||||
public static SeString CreatePartyFinderLink(uint listingId, string recruiterName, bool isCrossWorld = false)
|
public static SeString CreatePartyFinderLink(uint listingId, string recruiterName, bool isCrossWorld = false)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState.ClientState>.Get();
|
var payloads = new List<Payload>()
|
||||||
var seStringEvaluator = Service<SeStringEvaluator>.Get();
|
{
|
||||||
|
|
||||||
return new SeString(new List<Payload>([
|
|
||||||
new PartyFinderPayload(listingId, isCrossWorld ? PartyFinderPayload.PartyFinderLinkType.NotSpecified : PartyFinderPayload.PartyFinderLinkType.LimitedToHomeWorld),
|
new PartyFinderPayload(listingId, isCrossWorld ? PartyFinderPayload.PartyFinderLinkType.NotSpecified : PartyFinderPayload.PartyFinderLinkType.LimitedToHomeWorld),
|
||||||
..TextArrowPayloads,
|
// ->
|
||||||
..SeString.Parse(seStringEvaluator.EvaluateFromAddon(2265, [recruiterName, isCrossWorld ? 0 : 1], clientState.ClientLanguage)).Payloads,
|
new TextPayload($"Looking for Party ({recruiterName})" + (isCrossWorld ? " " : string.Empty)),
|
||||||
RawPayload.LinkTerminator
|
};
|
||||||
]));
|
|
||||||
|
payloads.InsertRange(1, TextArrowPayloads);
|
||||||
|
|
||||||
|
if (isCrossWorld)
|
||||||
|
payloads.Add(new IconPayload(BitmapFontIcon.CrossWorld));
|
||||||
|
|
||||||
|
payloads.Add(RawPayload.LinkTerminator);
|
||||||
|
|
||||||
|
return new SeString(payloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -365,12 +387,16 @@ public class SeString
|
||||||
/// <returns>An SeString containing all the payloads necessary to display a link to the party finder search conditions.</returns>
|
/// <returns>An SeString containing all the payloads necessary to display a link to the party finder search conditions.</returns>
|
||||||
public static SeString CreatePartyFinderSearchConditionsLink(string message)
|
public static SeString CreatePartyFinderSearchConditionsLink(string message)
|
||||||
{
|
{
|
||||||
return new SeString(new List<Payload>([
|
var payloads = new List<Payload>()
|
||||||
|
{
|
||||||
new PartyFinderPayload(),
|
new PartyFinderPayload(),
|
||||||
..TextArrowPayloads,
|
// ->
|
||||||
new TextPayload(message),
|
new TextPayload(message),
|
||||||
RawPayload.LinkTerminator
|
};
|
||||||
]));
|
payloads.InsertRange(1, TextArrowPayloads);
|
||||||
|
payloads.Add(RawPayload.LinkTerminator);
|
||||||
|
|
||||||
|
return new SeString(payloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ using Lumina.Excel.Sheets;
|
||||||
namespace Dalamud.Game.UnlockState;
|
namespace Dalamud.Game.UnlockState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum for <see cref="ItemAction.Action"/>.
|
/// Enum for <see cref="ItemAction.Type"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal enum ItemActionAction : ushort
|
internal enum ItemActionType : ushort
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// No item action.
|
/// No item action.
|
||||||
|
|
@ -158,23 +158,67 @@ internal unsafe class RecipeData : IInternalDisposableService
|
||||||
{
|
{
|
||||||
noteBookDivisionIndex++;
|
noteBookDivisionIndex++;
|
||||||
|
|
||||||
if (!noteBookDivisionRow.AllowedCraftTypes[craftType])
|
// For future Lumina.Excel update, replace with:
|
||||||
continue;
|
// if (!notebookDivisionRow.AllowedCraftTypes[craftType])
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
switch (craftTypeRow.RowId)
|
||||||
|
{
|
||||||
|
case 0 when !noteBookDivisionRow.CRPCraft: continue;
|
||||||
|
case 1 when !noteBookDivisionRow.BSMCraft: continue;
|
||||||
|
case 2 when !noteBookDivisionRow.ARMCraft: continue;
|
||||||
|
case 3 when !noteBookDivisionRow.GSMCraft: continue;
|
||||||
|
case 4 when !noteBookDivisionRow.LTWCraft: continue;
|
||||||
|
case 5 when !noteBookDivisionRow.WVRCraft: continue;
|
||||||
|
case 6 when !noteBookDivisionRow.ALCCraft: continue;
|
||||||
|
case 7 when !noteBookDivisionRow.CULCraft: continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (noteBookDivisionRow.GatheringOpeningLevel != byte.MaxValue)
|
if (noteBookDivisionRow.GatheringOpeningLevel != byte.MaxValue)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (noteBookDivisionRow.RequiresSecretRecipeBookGroupUnlock)
|
// For future Lumina.Excel update, replace with:
|
||||||
|
// if (notebookDivisionRow.RequiresSecretRecipeBookGroupUnlock)
|
||||||
|
if (noteBookDivisionRow.Unknown1)
|
||||||
{
|
{
|
||||||
var secretRecipeBookUnlocked = false;
|
var secretRecipeBookUnlocked = false;
|
||||||
|
|
||||||
foreach (var secretRecipeBookGroup in noteBookDivisionRow.SecretRecipeBookGroups)
|
// For future Lumina.Excel update, iterate over notebookDivisionRow.SecretRecipeBookGroups
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
if (secretRecipeBookGroup.RowId == 0 || !secretRecipeBookGroup.IsValid)
|
// For future Lumina.Excel update, replace with:
|
||||||
|
// if (secretRecipeBookGroup.RowId == 0 || !secretRecipeBookGroup.IsValid)
|
||||||
|
// continue;
|
||||||
|
var secretRecipeBookGroupRowId = i switch
|
||||||
|
{
|
||||||
|
0 => noteBookDivisionRow.Unknown2,
|
||||||
|
1 => noteBookDivisionRow.Unknown2,
|
||||||
|
_ => default,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (secretRecipeBookGroupRowId == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var bitIndex = secretRecipeBookGroup.Value.SecretRecipeBook[craftType].RowId;
|
if (!this.dataManager.GetExcelSheet<SecretRecipeBookGroup>().TryGetRow(secretRecipeBookGroupRowId, out var secretRecipeBookGroupRow))
|
||||||
if (PlayerState.Instance()->UnlockedSecretRecipeBooksBitArray.Get((int)bitIndex))
|
continue;
|
||||||
|
|
||||||
|
// For future Lumina.Excel update, replace with:
|
||||||
|
// var bitIndex = secretRecipeBookGroup.Value.UnlockBitIndex[craftType];
|
||||||
|
|
||||||
|
var bitIndex = craftType switch
|
||||||
|
{
|
||||||
|
0 => secretRecipeBookGroupRow.Unknown0,
|
||||||
|
1 => secretRecipeBookGroupRow.Unknown1,
|
||||||
|
2 => secretRecipeBookGroupRow.Unknown2,
|
||||||
|
3 => secretRecipeBookGroupRow.Unknown3,
|
||||||
|
4 => secretRecipeBookGroupRow.Unknown4,
|
||||||
|
5 => secretRecipeBookGroupRow.Unknown5,
|
||||||
|
6 => secretRecipeBookGroupRow.Unknown6,
|
||||||
|
7 => secretRecipeBookGroupRow.Unknown7,
|
||||||
|
_ => default,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (PlayerState.Instance()->UnlockedSecretRecipeBooksBitArray.Get(bitIndex))
|
||||||
{
|
{
|
||||||
secretRecipeBookUnlocked = true;
|
secretRecipeBookUnlocked = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsEmjVoiceNpcUnlocked(EmjVoiceNpc row)
|
public bool IsEmjVoiceNpcUnlocked(EmjVoiceNpc row)
|
||||||
{
|
{
|
||||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
return this.IsUnlockLinkUnlocked(row.Unknown26);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -217,7 +217,7 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
{
|
{
|
||||||
return this.dataManager.GetExcelSheet<EmjVoiceNpc>().TryGetRow(row.RowId, out var emjVoiceNpcRow)
|
return this.dataManager.GetExcelSheet<EmjVoiceNpc>().TryGetRow(row.RowId, out var emjVoiceNpcRow)
|
||||||
&& this.IsEmjVoiceNpcUnlocked(emjVoiceNpcRow)
|
&& this.IsEmjVoiceNpcUnlocked(emjVoiceNpcRow)
|
||||||
&& QuestManager.IsQuestComplete(row.UnlockQuest.RowId);
|
&& QuestManager.IsQuestComplete(row.Unknown1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -264,47 +264,47 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
|
|
||||||
// To avoid the ExdModule.GetItemRowById call, which can return null if the excel page
|
// To avoid the ExdModule.GetItemRowById call, which can return null if the excel page
|
||||||
// is not loaded, we're going to imitate the IsItemActionUnlocked call first:
|
// is not loaded, we're going to imitate the IsItemActionUnlocked call first:
|
||||||
switch ((ItemActionAction)row.ItemAction.Value.Action.RowId)
|
switch ((ItemActionType)row.ItemAction.Value.Type)
|
||||||
{
|
{
|
||||||
case ItemActionAction.Companion:
|
case ItemActionType.Companion:
|
||||||
return UIState.Instance()->IsCompanionUnlocked(row.ItemAction.Value.Data[0]);
|
return UIState.Instance()->IsCompanionUnlocked(row.ItemAction.Value.Data[0]);
|
||||||
|
|
||||||
case ItemActionAction.BuddyEquip:
|
case ItemActionType.BuddyEquip:
|
||||||
return UIState.Instance()->Buddy.CompanionInfo.IsBuddyEquipUnlocked(row.ItemAction.Value.Data[0]);
|
return UIState.Instance()->Buddy.CompanionInfo.IsBuddyEquipUnlocked(row.ItemAction.Value.Data[0]);
|
||||||
|
|
||||||
case ItemActionAction.Mount:
|
case ItemActionType.Mount:
|
||||||
return PlayerState.Instance()->IsMountUnlocked(row.ItemAction.Value.Data[0]);
|
return PlayerState.Instance()->IsMountUnlocked(row.ItemAction.Value.Data[0]);
|
||||||
|
|
||||||
case ItemActionAction.SecretRecipeBook:
|
case ItemActionType.SecretRecipeBook:
|
||||||
return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.ItemAction.Value.Data[0]);
|
return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.ItemAction.Value.Data[0]);
|
||||||
|
|
||||||
case ItemActionAction.UnlockLink:
|
case ItemActionType.UnlockLink:
|
||||||
case ItemActionAction.OccultRecords:
|
case ItemActionType.OccultRecords:
|
||||||
return UIState.Instance()->IsUnlockLinkUnlocked(row.ItemAction.Value.Data[0]);
|
return UIState.Instance()->IsUnlockLinkUnlocked(row.ItemAction.Value.Data[0]);
|
||||||
|
|
||||||
case ItemActionAction.TripleTriadCard when row.AdditionalData.Is<TripleTriadCard>():
|
case ItemActionType.TripleTriadCard when row.AdditionalData.Is<TripleTriadCard>():
|
||||||
return UIState.Instance()->IsTripleTriadCardUnlocked((ushort)row.AdditionalData.RowId);
|
return UIState.Instance()->IsTripleTriadCardUnlocked((ushort)row.AdditionalData.RowId);
|
||||||
|
|
||||||
case ItemActionAction.FolkloreTome:
|
case ItemActionType.FolkloreTome:
|
||||||
return PlayerState.Instance()->IsFolkloreBookUnlocked(row.ItemAction.Value.Data[0]);
|
return PlayerState.Instance()->IsFolkloreBookUnlocked(row.ItemAction.Value.Data[0]);
|
||||||
|
|
||||||
case ItemActionAction.OrchestrionRoll when row.AdditionalData.Is<Orchestrion>():
|
case ItemActionType.OrchestrionRoll when row.AdditionalData.Is<Orchestrion>():
|
||||||
return PlayerState.Instance()->IsOrchestrionRollUnlocked(row.AdditionalData.RowId);
|
return PlayerState.Instance()->IsOrchestrionRollUnlocked(row.AdditionalData.RowId);
|
||||||
|
|
||||||
case ItemActionAction.FramersKit:
|
case ItemActionType.FramersKit:
|
||||||
return PlayerState.Instance()->IsFramersKitUnlocked(row.AdditionalData.RowId);
|
return PlayerState.Instance()->IsFramersKitUnlocked(row.AdditionalData.RowId);
|
||||||
|
|
||||||
case ItemActionAction.Ornament:
|
case ItemActionType.Ornament:
|
||||||
return PlayerState.Instance()->IsOrnamentUnlocked(row.ItemAction.Value.Data[0]);
|
return PlayerState.Instance()->IsOrnamentUnlocked(row.ItemAction.Value.Data[0]);
|
||||||
|
|
||||||
case ItemActionAction.Glasses:
|
case ItemActionType.Glasses:
|
||||||
return PlayerState.Instance()->IsGlassesUnlocked((ushort)row.AdditionalData.RowId);
|
return PlayerState.Instance()->IsGlassesUnlocked((ushort)row.AdditionalData.RowId);
|
||||||
|
|
||||||
case ItemActionAction.SoulShards when PublicContentOccultCrescent.GetState() is var occultCrescentState && occultCrescentState != null:
|
case ItemActionType.SoulShards when PublicContentOccultCrescent.GetState() is var occultCrescentState && occultCrescentState != null:
|
||||||
var supportJobId = (byte)row.ItemAction.Value.Data[0];
|
var supportJobId = (byte)row.ItemAction.Value.Data[0];
|
||||||
return supportJobId < occultCrescentState->SupportJobLevels.Length && occultCrescentState->SupportJobLevels[supportJobId] != 0;
|
return supportJobId < occultCrescentState->SupportJobLevels.Length && occultCrescentState->SupportJobLevels[supportJobId] != 0;
|
||||||
|
|
||||||
case ItemActionAction.CompanySealVouchers:
|
case ItemActionType.CompanySealVouchers:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,7 +327,7 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsMKDLoreUnlocked(MKDLore row)
|
public bool IsMKDLoreUnlocked(MKDLore row)
|
||||||
{
|
{
|
||||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
return this.IsUnlockLinkUnlocked(row.Unknown2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -414,20 +414,20 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
if (row.ItemAction.RowId == 0)
|
if (row.ItemAction.RowId == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return (ItemActionAction)row.ItemAction.Value.Action.RowId
|
return (ItemActionType)row.ItemAction.Value.Type
|
||||||
is ItemActionAction.Companion
|
is ItemActionType.Companion
|
||||||
or ItemActionAction.BuddyEquip
|
or ItemActionType.BuddyEquip
|
||||||
or ItemActionAction.Mount
|
or ItemActionType.Mount
|
||||||
or ItemActionAction.SecretRecipeBook
|
or ItemActionType.SecretRecipeBook
|
||||||
or ItemActionAction.UnlockLink
|
or ItemActionType.UnlockLink
|
||||||
or ItemActionAction.TripleTriadCard
|
or ItemActionType.TripleTriadCard
|
||||||
or ItemActionAction.FolkloreTome
|
or ItemActionType.FolkloreTome
|
||||||
or ItemActionAction.OrchestrionRoll
|
or ItemActionType.OrchestrionRoll
|
||||||
or ItemActionAction.FramersKit
|
or ItemActionType.FramersKit
|
||||||
or ItemActionAction.Ornament
|
or ItemActionType.Ornament
|
||||||
or ItemActionAction.Glasses
|
or ItemActionType.Glasses
|
||||||
or ItemActionAction.OccultRecords
|
or ItemActionType.OccultRecords
|
||||||
or ItemActionAction.SoulShards;
|
or ItemActionType.SoulShards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ 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")]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Hooking.Internal;
|
using Dalamud.Hooking.Internal;
|
||||||
using Dalamud.Hooking.Internal.Verification;
|
|
||||||
|
|
||||||
namespace Dalamud.Hooking;
|
namespace Dalamud.Hooking;
|
||||||
|
|
||||||
|
|
@ -202,19 +201,19 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||||
useMinHook = true;
|
useMinHook = true;
|
||||||
|
|
||||||
var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
|
||||||
if (moduleHandle.IsNull)
|
if (moduleHandle.IsInvalid)
|
||||||
throw new Exception($"Could not get a handle to module {moduleName}");
|
throw new Exception($"Could not get a handle to module {moduleName}");
|
||||||
|
|
||||||
var procAddress = Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
|
||||||
if (procAddress.IsNull)
|
if (procAddress == IntPtr.Zero)
|
||||||
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
||||||
|
|
||||||
var address = HookManager.FollowJmp(procAddress.Value);
|
procAddress = HookManager.FollowJmp(procAddress);
|
||||||
if (useMinHook)
|
if (useMinHook)
|
||||||
return new MinHookHook<T>(address, detour, Assembly.GetCallingAssembly());
|
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
else
|
else
|
||||||
return new ReloadedHook<T>(address, detour, Assembly.GetCallingAssembly());
|
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -231,8 +230,6 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||||
useMinHook = true;
|
useMinHook = true;
|
||||||
|
|
||||||
HookVerifier.Verify<T>(procAddress);
|
|
||||||
|
|
||||||
procAddress = HookManager.FollowJmp(procAddress);
|
procAddress = HookManager.FollowJmp(procAddress);
|
||||||
if (useMinHook)
|
if (useMinHook)
|
||||||
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
|
|
|
||||||
100
Dalamud/Hooking/Internal/CallHook.cs
Normal file
100
Dalamud/Hooking/Internal/CallHook.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Dalamud.Hooking.Internal.Verification;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exception thrown when a provided delegate for a hook does not match a known delegate.
|
|
||||||
/// </summary>
|
|
||||||
public class HookVerificationException : Exception
|
|
||||||
{
|
|
||||||
private HookVerificationException(string message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new <see cref="HookVerificationException"/> exception.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of the function that is being hooked.</param>
|
|
||||||
/// <param name="passed">The delegate passed by the user.</param>
|
|
||||||
/// <param name="enforced">The delegate we think is correct.</param>
|
|
||||||
/// <param name="message">Additional context to show to the user.</param>
|
|
||||||
/// <returns>The created exception.</returns>
|
|
||||||
internal static HookVerificationException Create(IntPtr address, Type passed, Type enforced, string message)
|
|
||||||
{
|
|
||||||
return new HookVerificationException(
|
|
||||||
$"Hook verification failed for address 0x{address.ToInt64():X}\n\n" +
|
|
||||||
$"Why: {message}\n" +
|
|
||||||
$"Passed Delegate: {GetSignature(passed)}\n" +
|
|
||||||
$"Correct Delegate: {GetSignature(enforced)}\n\n" +
|
|
||||||
"The hook delegate must exactly match the provided signature to prevent memory corruption and wrong data passed to originals.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetSignature(Type delegateType)
|
|
||||||
{
|
|
||||||
var method = delegateType.GetMethod("Invoke");
|
|
||||||
if (method == null) return delegateType.Name;
|
|
||||||
|
|
||||||
var parameters = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name));
|
|
||||||
return $"{method.ReturnType.Name} ({parameters})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using Dalamud.Game;
|
|
||||||
using Dalamud.Logging.Internal;
|
|
||||||
|
|
||||||
namespace Dalamud.Hooking.Internal.Verification;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Global utility that can verify whether hook delegates are correctly declared.
|
|
||||||
/// Initialized out-of-band, since Hook is instantiated all over the place without a service, so this cannot be
|
|
||||||
/// a service either.
|
|
||||||
/// </summary>
|
|
||||||
internal static class HookVerifier
|
|
||||||
{
|
|
||||||
private static readonly ModuleLog Log = new("HookVerifier");
|
|
||||||
|
|
||||||
private static readonly VerificationEntry[] ToVerify =
|
|
||||||
[
|
|
||||||
new(
|
|
||||||
"ActorControlSelf",
|
|
||||||
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
|
|
||||||
typeof(ActorControlSelfDelegate),
|
|
||||||
"Signature changed in Patch 7.4") // 7.4 (new parameters)
|
|
||||||
];
|
|
||||||
|
|
||||||
private delegate void ActorControlSelfDelegate(uint category, uint eventId, uint param1, uint param2, uint param3, uint param4, uint param5, uint param6, uint param7, uint param8, ulong targetId, byte param9);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="HookVerifier"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scanner">Process to scan in.</param>
|
|
||||||
public static void Initialize(TargetSigScanner scanner)
|
|
||||||
{
|
|
||||||
foreach (var entry in ToVerify)
|
|
||||||
{
|
|
||||||
if (!scanner.TryScanText(entry.Signature, out var address))
|
|
||||||
{
|
|
||||||
Log.Error("Could not resolve signature for hook {Name} ({Sig})", entry.Name, entry.Signature);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Address = address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verify the hook with the provided address and exception.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The address of the function we are hooking.</param>
|
|
||||||
/// <typeparam name="T">The delegate type passed by the creator of the hook.</typeparam>
|
|
||||||
/// <exception cref="HookVerificationException">Exception thrown when we think the hook is not correctly declared.</exception>
|
|
||||||
public static void Verify<T>(IntPtr address) where T : Delegate
|
|
||||||
{
|
|
||||||
var entry = ToVerify.FirstOrDefault(x => x.Address == address);
|
|
||||||
|
|
||||||
// Nothing to verify for this hook?
|
|
||||||
if (entry == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var passedType = typeof(T);
|
|
||||||
|
|
||||||
// Directly compare delegates
|
|
||||||
if (passedType == entry.TargetDelegateType)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var passedInvoke = passedType.GetMethod("Invoke")!;
|
|
||||||
var enforcedInvoke = entry.TargetDelegateType.GetMethod("Invoke")!;
|
|
||||||
|
|
||||||
// Compare Return Type
|
|
||||||
var mismatch = passedInvoke.ReturnType != enforcedInvoke.ReturnType;
|
|
||||||
|
|
||||||
// Compare Parameter Count
|
|
||||||
var passedParams = passedInvoke.GetParameters();
|
|
||||||
var enforcedParams = enforcedInvoke.GetParameters();
|
|
||||||
|
|
||||||
if (passedParams.Length != enforcedParams.Length)
|
|
||||||
{
|
|
||||||
mismatch = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Compare Parameter Types
|
|
||||||
for (var i = 0; i < passedParams.Length; i++)
|
|
||||||
{
|
|
||||||
if (passedParams[i].ParameterType != enforcedParams[i].ParameterType)
|
|
||||||
{
|
|
||||||
mismatch = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mismatch)
|
|
||||||
{
|
|
||||||
throw HookVerificationException.Create(address, passedType, entry.TargetDelegateType, entry.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record VerificationEntry(string Name, string Signature, Type TargetDelegateType, string Message)
|
|
||||||
{
|
|
||||||
public nint Address { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)]
|
||||||
[Api15ToDo("Map this field to ValueClamped, probably.")]
|
[Api13ToDo("Map this field to ValueClamped, probably.")]
|
||||||
public double Value => this.ValueUnclamped;
|
public double Value => this.ValueUnclamped;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,6 @@ public enum SettingsOpenKind
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ServerInfoBar,
|
ServerInfoBar,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Open to the "Badges" page.
|
|
||||||
/// </summary>
|
|
||||||
Badge,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open to the "Experimental" page.
|
/// Open to the "Experimental" page.
|
||||||
/// </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, buf, maxStrLen).ThrowOnError();
|
fn->GetLocaleName(i, (ushort*)buf, maxStrLen).ThrowOnError();
|
||||||
var key = new string(buf);
|
var key = new string(buf);
|
||||||
fn->GetString(i, buf, maxStrLen).ThrowOnError();
|
fn->GetString(i, (ushort*)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 (char* pName = this.EnglishName)
|
fixed (void* pName = this.EnglishName)
|
||||||
sfc.Get()->FindFamilyName(pName, &familyIndex, &exists).ThrowOnError();
|
sfc.Get()->FindFamilyName((ushort*)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 (char* name = this.Family.EnglishName)
|
fixed (void* name = this.Family.EnglishName)
|
||||||
sfc.Get()->FindFamilyName(name, &familyIndex, &exists).ThrowOnError();
|
sfc.Get()->FindFamilyName((ushort*)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, path, pathSize + 1).ThrowOnError();
|
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, (ushort*)path, pathSize + 1).ThrowOnError();
|
||||||
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
|
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
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