mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 06:13:40 +01:00
Merge branch 'master' into new_im_hooks
This commit is contained in:
commit
50f74c55a7
460 changed files with 40079 additions and 12893 deletions
|
|
@ -35,7 +35,7 @@ dotnet_naming_rule.private_instance_fields_rule.severity = warning
|
||||||
dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
|
dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
|
||||||
dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
|
dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
|
||||||
dotnet_naming_rule.private_static_fields_rule.severity = warning
|
dotnet_naming_rule.private_static_fields_rule.severity = warning
|
||||||
dotnet_naming_rule.private_static_fields_rule.style = upper_camel_case_style
|
dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style
|
||||||
dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
|
dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
|
||||||
dotnet_naming_rule.private_static_readonly_rule.severity = warning
|
dotnet_naming_rule.private_static_readonly_rule.severity = warning
|
||||||
dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style
|
dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style
|
||||||
|
|
@ -57,6 +57,7 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
||||||
|
dotnet_separate_import_directive_groups = true
|
||||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
||||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
||||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
||||||
|
|
@ -97,22 +98,32 @@ resharper_apply_on_completion = true
|
||||||
resharper_auto_property_can_be_made_get_only_global_highlighting = none
|
resharper_auto_property_can_be_made_get_only_global_highlighting = none
|
||||||
resharper_auto_property_can_be_made_get_only_local_highlighting = none
|
resharper_auto_property_can_be_made_get_only_local_highlighting = none
|
||||||
resharper_autodetect_indent_settings = true
|
resharper_autodetect_indent_settings = true
|
||||||
|
resharper_blank_lines_around_single_line_auto_property = 1
|
||||||
resharper_braces_for_ifelse = required_for_multiline
|
resharper_braces_for_ifelse = required_for_multiline
|
||||||
resharper_can_use_global_alias = false
|
resharper_can_use_global_alias = false
|
||||||
resharper_csharp_align_multiline_parameter = true
|
resharper_csharp_align_multiline_parameter = true
|
||||||
resharper_csharp_align_multiple_declaration = true
|
resharper_csharp_align_multiple_declaration = true
|
||||||
resharper_csharp_empty_block_style = multiline
|
resharper_csharp_empty_block_style = multiline
|
||||||
resharper_csharp_int_align_comments = true
|
resharper_csharp_int_align_comments = false
|
||||||
resharper_csharp_new_line_before_while = true
|
resharper_csharp_new_line_before_while = true
|
||||||
resharper_csharp_wrap_after_declaration_lpar = true
|
resharper_csharp_wrap_after_declaration_lpar = true
|
||||||
|
resharper_csharp_wrap_after_invocation_lpar = true
|
||||||
|
resharper_csharp_wrap_arguments_style = chop_if_long
|
||||||
resharper_enforce_line_ending_style = true
|
resharper_enforce_line_ending_style = true
|
||||||
|
resharper_instance_members_qualify_declared_in = this_class, base_class
|
||||||
|
resharper_int_align = false
|
||||||
resharper_member_can_be_private_global_highlighting = none
|
resharper_member_can_be_private_global_highlighting = none
|
||||||
resharper_member_can_be_private_local_highlighting = none
|
resharper_member_can_be_private_local_highlighting = none
|
||||||
resharper_new_line_before_finally = false
|
resharper_new_line_before_finally = true
|
||||||
|
resharper_parentheses_non_obvious_operations = none, multiplicative, additive, arithmetic, shift, bitwise_and, bitwise_exclusive_or, bitwise_inclusive_or, bitwise
|
||||||
|
resharper_parentheses_redundancy_style = remove_if_not_clarifies_precedence
|
||||||
resharper_place_accessorholder_attribute_on_same_line = false
|
resharper_place_accessorholder_attribute_on_same_line = false
|
||||||
resharper_place_field_attribute_on_same_line = false
|
resharper_place_field_attribute_on_same_line = false
|
||||||
|
resharper_place_simple_initializer_on_single_line = true
|
||||||
resharper_show_autodetect_configure_formatting_tip = false
|
resharper_show_autodetect_configure_formatting_tip = false
|
||||||
|
resharper_space_within_single_line_array_initializer_braces = true
|
||||||
resharper_use_indent_from_vs = false
|
resharper_use_indent_from_vs = false
|
||||||
|
resharper_wrap_array_initializer_style = chop_if_long
|
||||||
|
|
||||||
# ReSharper inspection severities
|
# ReSharper inspection severities
|
||||||
resharper_arrange_missing_parentheses_highlighting = hint
|
resharper_arrange_missing_parentheses_highlighting = hint
|
||||||
|
|
|
||||||
43
.github/workflows/main.yml
vendored
43
.github/workflows/main.yml
vendored
|
|
@ -42,7 +42,48 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: dalamud-artifact
|
name: dalamud-artifact
|
||||||
path: bin\Release
|
path: bin\Release
|
||||||
|
|
||||||
|
check_api_compat:
|
||||||
|
name: "Check API Compatibility"
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
needs: build
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: "Install .NET SDK"
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 7
|
||||||
|
- name: "Install ApiCompat"
|
||||||
|
run: |
|
||||||
|
dotnet tool install -g Microsoft.DotNet.ApiCompat.Tool
|
||||||
|
- name: "Download Proposed Artifacts"
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: dalamud-artifact
|
||||||
|
path: .\right
|
||||||
|
- name: "Download Live (Stg) Artifacts"
|
||||||
|
run: |
|
||||||
|
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip
|
||||||
|
Expand-Archive -Force latest.zip "left"
|
||||||
|
- name: "Verify Compatibility"
|
||||||
|
run: |
|
||||||
|
$FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll"
|
||||||
|
|
||||||
|
$retcode = 0
|
||||||
|
|
||||||
|
foreach ($file in $FILES_TO_VALIDATE) {
|
||||||
|
$testout = ""
|
||||||
|
Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ==="
|
||||||
|
apicompat -l "left\${file}" -r "right\${file}" | Tee-Object -Variable testout
|
||||||
|
Write-Output "::endgroup::"
|
||||||
|
if ($testout -ne "APICompat ran successfully without finding any breaking changes.") {
|
||||||
|
Write-Output "::error::${file} did not pass. Please review it for problems."
|
||||||
|
$retcode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $retcode
|
||||||
|
|
||||||
deploy_stg:
|
deploy_stg:
|
||||||
name: Deploy dalamud-distrib staging
|
name: Deploy dalamud-distrib staging
|
||||||
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
||||||
|
|
|
||||||
3
.github/workflows/rollup.yml
vendored
3
.github/workflows/rollup.yml
vendored
|
|
@ -11,7 +11,8 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
branches:
|
branches:
|
||||||
- v9
|
- net8
|
||||||
|
#- new_im_hooks # Unmergeable
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@
|
||||||
<IntDir>obj\$(Configuration)\</IntDir>
|
<IntDir>obj\$(Configuration)\</IntDir>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||||
|
</ImportGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
|
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
|
||||||
|
|
@ -56,7 +59,7 @@
|
||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
<EnableUAC>false</EnableUAC>
|
<EnableUAC>false</EnableUAC>
|
||||||
<AdditionalDependencies>Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
<AdditionalDependencies>Version.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
|
|
@ -72,6 +75,7 @@
|
||||||
<Link>
|
<Link>
|
||||||
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
||||||
<OptimizeReferences>false</OptimizeReferences>
|
<OptimizeReferences>false</OptimizeReferences>
|
||||||
|
<ModuleDefinitionFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">module.def</ModuleDefinitionFile>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
|
@ -85,9 +89,13 @@
|
||||||
<Link>
|
<Link>
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<ModuleDefinitionFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">module.def</ModuleDefinitionFile>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||||
|
</ImportGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
|
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
|
||||||
<Link>nethost.dll</Link>
|
<Link>nethost.dll</Link>
|
||||||
|
|
@ -131,6 +139,7 @@
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="ntdll.cpp" />
|
||||||
<ClCompile Include="unicode.cpp">
|
<ClCompile Include="unicode.cpp">
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||||
|
|
@ -178,6 +187,7 @@
|
||||||
<ClInclude Include="DalamudStartInfo.h" />
|
<ClInclude Include="DalamudStartInfo.h" />
|
||||||
<ClInclude Include="hooks.h" />
|
<ClInclude Include="hooks.h" />
|
||||||
<ClInclude Include="logging.h" />
|
<ClInclude Include="logging.h" />
|
||||||
|
<ClInclude Include="ntdll.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="unicode.h" />
|
<ClInclude Include="unicode.h" />
|
||||||
<ClInclude Include="utils.h" />
|
<ClInclude Include="utils.h" />
|
||||||
|
|
@ -191,8 +201,14 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="dalamud.ico" />
|
<Image Include="dalamud.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<MASM Include="rewrite_entrypoint_thunks.asm" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="module.def" />
|
||||||
|
</ItemGroup>
|
||||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@
|
||||||
<ClCompile Include="DalamudStartInfo.cpp">
|
<ClCompile Include="DalamudStartInfo.cpp">
|
||||||
<Filter>Dalamud.Boot DLL</Filter>
|
<Filter>Dalamud.Boot DLL</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="ntdll.cpp">
|
||||||
|
<Filter>Dalamud.Boot DLL</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
||||||
|
|
@ -143,6 +146,9 @@
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="crashhandler_shared.h" />
|
<ClInclude Include="crashhandler_shared.h" />
|
||||||
|
<ClInclude Include="ntdll.h">
|
||||||
|
<Filter>Dalamud.Boot DLL</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\lib\reshade\include\reshade.hpp">
|
<ClInclude Include="..\lib\reshade\include\reshade.hpp">
|
||||||
<Filter>ReshadePlugin</Filter>
|
<Filter>ReshadePlugin</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
@ -174,4 +180,14 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="dalamud.ico" />
|
<Image Include="dalamud.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
<ItemGroup>
|
||||||
|
<MASM Include="rewrite_entrypoint_thunks.asm">
|
||||||
|
<Filter>Dalamud.Boot DLL</Filter>
|
||||||
|
</MASM>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="module.def">
|
||||||
|
<Filter>Dalamud.Boot DLL</Filter>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -68,19 +68,37 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::ClientLanguage& val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json& json, DalamudStartInfo::LoadMethod& value) {
|
||||||
|
if (json.is_number_integer()) {
|
||||||
|
value = static_cast<DalamudStartInfo::LoadMethod>(json.get<int>());
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (json.is_string()) {
|
||||||
|
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
|
||||||
|
if (langstr == "entrypoint")
|
||||||
|
value = DalamudStartInfo::LoadMethod::Entrypoint;
|
||||||
|
else if (langstr == "inject")
|
||||||
|
value = DalamudStartInfo::LoadMethod::DllInject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
||||||
if (!json.is_object())
|
if (!json.is_object())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
config.DalamudLoadMethod = json.value("LoadMethod", config.DalamudLoadMethod);
|
||||||
config.WorkingDirectory = json.value("WorkingDirectory", config.WorkingDirectory);
|
config.WorkingDirectory = json.value("WorkingDirectory", config.WorkingDirectory);
|
||||||
config.ConfigurationPath = json.value("ConfigurationPath", config.ConfigurationPath);
|
config.ConfigurationPath = json.value("ConfigurationPath", config.ConfigurationPath);
|
||||||
|
config.LogPath = json.value("LogPath", config.LogPath);
|
||||||
|
config.LogName = json.value("LogName", config.LogName);
|
||||||
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
|
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
|
||||||
config.DefaultPluginDirectory = json.value("DefaultPluginDirectory", config.DefaultPluginDirectory);
|
|
||||||
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
|
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
|
||||||
config.Language = json.value("Language", config.Language);
|
config.Language = json.value("Language", config.Language);
|
||||||
config.GameVersion = json.value("GameVersion", config.GameVersion);
|
config.GameVersion = json.value("GameVersion", config.GameVersion);
|
||||||
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
|
|
||||||
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
|
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
|
||||||
|
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
|
||||||
|
config.NoLoadPlugins = json.value("NoLoadPlugins", config.NoLoadPlugins);
|
||||||
|
config.NoLoadThirdPartyPlugins = json.value("NoLoadThirdPartyPlugins", config.NoLoadThirdPartyPlugins);
|
||||||
|
|
||||||
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
|
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
|
||||||
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
|
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
|
||||||
|
|
@ -103,6 +121,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow);
|
config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow);
|
||||||
|
config.NoExceptionHandlers = json.value("NoExceptionHandlers", config.NoExceptionHandlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DalamudStartInfo::from_envvars() {
|
void DalamudStartInfo::from_envvars() {
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,25 @@ struct DalamudStartInfo {
|
||||||
};
|
};
|
||||||
friend void from_json(const nlohmann::json&, ClientLanguage&);
|
friend void from_json(const nlohmann::json&, ClientLanguage&);
|
||||||
|
|
||||||
|
enum class LoadMethod : int {
|
||||||
|
Entrypoint,
|
||||||
|
DllInject,
|
||||||
|
};
|
||||||
|
friend void from_json(const nlohmann::json&, LoadMethod&);
|
||||||
|
|
||||||
|
LoadMethod DalamudLoadMethod = LoadMethod::Entrypoint;
|
||||||
std::string WorkingDirectory;
|
std::string WorkingDirectory;
|
||||||
std::string ConfigurationPath;
|
std::string ConfigurationPath;
|
||||||
|
std::string LogPath;
|
||||||
|
std::string LogName;
|
||||||
std::string PluginDirectory;
|
std::string PluginDirectory;
|
||||||
std::string DefaultPluginDirectory;
|
|
||||||
std::string AssetDirectory;
|
std::string AssetDirectory;
|
||||||
ClientLanguage Language = ClientLanguage::English;
|
ClientLanguage Language = ClientLanguage::English;
|
||||||
std::string GameVersion;
|
std::string GameVersion;
|
||||||
int DelayInitializeMs = 0;
|
|
||||||
std::string TroubleshootingPackData;
|
std::string TroubleshootingPackData;
|
||||||
|
int DelayInitializeMs = 0;
|
||||||
|
bool NoLoadPlugins;
|
||||||
|
bool NoLoadThirdPartyPlugins;
|
||||||
|
|
||||||
std::string BootLogPath;
|
std::string BootLogPath;
|
||||||
bool BootShowConsole = false;
|
bool BootShowConsole = false;
|
||||||
|
|
@ -49,6 +59,7 @@ struct DalamudStartInfo {
|
||||||
std::set<std::string> BootUnhookDlls{};
|
std::set<std::string> BootUnhookDlls{};
|
||||||
|
|
||||||
bool CrashHandlerShow = false;
|
bool CrashHandlerShow = false;
|
||||||
|
bool NoExceptionHandlers = false;
|
||||||
|
|
||||||
friend void from_json(const nlohmann::json&, DalamudStartInfo&);
|
friend void from_json(const nlohmann::json&, DalamudStartInfo&);
|
||||||
void from_envvars();
|
void from_envvars();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ struct exception_info
|
||||||
CONTEXT ContextRecord;
|
CONTEXT ContextRecord;
|
||||||
uint64_t nLifetime;
|
uint64_t nLifetime;
|
||||||
HANDLE hThreadHandle;
|
HANDLE hThreadHandle;
|
||||||
|
HANDLE hEventHandle;
|
||||||
DWORD dwStackTraceLength;
|
DWORD dwStackTraceLength;
|
||||||
DWORD dwTroubleshootingPackDataLength;
|
DWORD dwTroubleshootingPackDataLength;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ static void OnReshadeOverlay(reshade::api::effect_runtime *runtime) {
|
||||||
s_pfnReshadeOverlayCallback(reinterpret_cast<void*>(runtime->get_native()));
|
s_pfnReshadeOverlayCallback(reinterpret_cast<void*>(runtime->get_native()));
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
g_startInfo.from_envvars();
|
g_startInfo.from_envvars();
|
||||||
|
|
||||||
std::string jsonParseError;
|
std::string jsonParseError;
|
||||||
|
|
@ -122,7 +122,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
logging::I("Calling InitializeClrAndGetEntryPoint");
|
logging::I("Calling InitializeClrAndGetEntryPoint");
|
||||||
|
|
||||||
void* entrypoint_vfn;
|
void* entrypoint_vfn;
|
||||||
int result = InitializeClrAndGetEntryPoint(
|
const auto result = InitializeClrAndGetEntryPoint(
|
||||||
g_hModule,
|
g_hModule,
|
||||||
g_startInfo.BootEnableEtw,
|
g_startInfo.BootEnableEtw,
|
||||||
runtimeconfig_path,
|
runtimeconfig_path,
|
||||||
|
|
@ -132,7 +132,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
L"Dalamud.EntryPoint+InitDelegate, Dalamud",
|
L"Dalamud.EntryPoint+InitDelegate, Dalamud",
|
||||||
&entrypoint_vfn);
|
&entrypoint_vfn);
|
||||||
|
|
||||||
if (result != 0)
|
if (FAILED(result))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE, LPVOID);
|
using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE, LPVOID);
|
||||||
|
|
@ -141,8 +141,8 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
// ============================== VEH ======================================== //
|
// ============================== VEH ======================================== //
|
||||||
|
|
||||||
logging::I("Initializing VEH...");
|
logging::I("Initializing VEH...");
|
||||||
if (utils::is_running_on_linux()) {
|
if (g_startInfo.NoExceptionHandlers) {
|
||||||
logging::I("=> VEH was disabled, running on linux");
|
logging::W("=> Exception handlers are disabled from DalamudStartInfo.");
|
||||||
} else if (g_startInfo.BootVehEnabled) {
|
} else if (g_startInfo.BootVehEnabled) {
|
||||||
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))
|
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))
|
||||||
logging::I("=> Done!");
|
logging::I("=> Done!");
|
||||||
|
|
@ -164,10 +164,10 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
entrypoint_fn(lpParam, hMainThreadContinue, g_bReshadeAvailable ? &s_pfnReshadeOverlayCallback : nullptr);
|
entrypoint_fn(lpParam, hMainThreadContinue, g_bReshadeAvailable ? &s_pfnReshadeOverlayCallback : nullptr);
|
||||||
logging::I("Done!");
|
logging::I("Done!");
|
||||||
|
|
||||||
return 0;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam) {
|
extern "C" DWORD WINAPI Initialize(LPVOID lpParam) {
|
||||||
return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr));
|
return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,39 +2,9 @@
|
||||||
|
|
||||||
#include "hooks.h"
|
#include "hooks.h"
|
||||||
|
|
||||||
|
#include "ntdll.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
|
||||||
enum {
|
|
||||||
LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
|
|
||||||
LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LDR_DLL_UNLOADED_NOTIFICATION_DATA {
|
|
||||||
ULONG Flags; //Reserved.
|
|
||||||
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
|
|
||||||
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
|
|
||||||
PVOID DllBase; //A pointer to the base address for the DLL in memory.
|
|
||||||
ULONG SizeOfImage; //The size of the DLL image, in bytes.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LDR_DLL_LOADED_NOTIFICATION_DATA {
|
|
||||||
ULONG Flags; //Reserved.
|
|
||||||
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
|
|
||||||
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
|
|
||||||
PVOID DllBase; //A pointer to the base address for the DLL in memory.
|
|
||||||
ULONG SizeOfImage; //The size of the DLL image, in bytes.
|
|
||||||
};
|
|
||||||
|
|
||||||
union LDR_DLL_NOTIFICATION_DATA {
|
|
||||||
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
|
|
||||||
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
|
|
||||||
};
|
|
||||||
|
|
||||||
using PLDR_DLL_NOTIFICATION_FUNCTION = VOID CALLBACK(_In_ ULONG NotificationReason, _In_ const LDR_DLL_NOTIFICATION_DATA* NotificationData, _In_opt_ PVOID Context);
|
|
||||||
|
|
||||||
static const auto LdrRegisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie)>("LdrRegisterDllNotification");
|
|
||||||
static const auto LdrUnregisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(PVOID Cookie)>("LdrUnregisterDllNotification");
|
|
||||||
|
|
||||||
hooks::getprocaddress_singleton_import_hook::getprocaddress_singleton_import_hook()
|
hooks::getprocaddress_singleton_import_hook::getprocaddress_singleton_import_hook()
|
||||||
: m_pfnGetProcAddress(GetProcAddress)
|
: m_pfnGetProcAddress(GetProcAddress)
|
||||||
, m_thunk("kernel32!GetProcAddress(Singleton Import Hook)",
|
, m_thunk("kernel32!GetProcAddress(Singleton Import Hook)",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <limits>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
|
||||||
5
Dalamud.Boot/module.def
Normal file
5
Dalamud.Boot/module.def
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
LIBRARY Dalamud.Boot
|
||||||
|
EXPORTS
|
||||||
|
Initialize @1
|
||||||
|
RewriteRemoteEntryPointW @2
|
||||||
|
RewrittenEntryPoint @3
|
||||||
15
Dalamud.Boot/ntdll.cpp
Normal file
15
Dalamud.Boot/ntdll.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "ntdll.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
NTSTATUS LdrRegisterDllNotification(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie) {
|
||||||
|
static const auto pfn = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie)>("LdrRegisterDllNotification");
|
||||||
|
return pfn(Flags, NotificationFunction, Context, Cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS LdrUnregisterDllNotification(PVOID Cookie) {
|
||||||
|
static const auto pfn = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(PVOID Cookie)>("LdrUnregisterDllNotification");
|
||||||
|
return pfn(Cookie);
|
||||||
|
}
|
||||||
33
Dalamud.Boot/ntdll.h
Normal file
33
Dalamud.Boot/ntdll.h
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// ntdll exports
|
||||||
|
enum {
|
||||||
|
LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
|
||||||
|
LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LDR_DLL_UNLOADED_NOTIFICATION_DATA {
|
||||||
|
ULONG Flags; //Reserved.
|
||||||
|
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
|
||||||
|
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
|
||||||
|
PVOID DllBase; //A pointer to the base address for the DLL in memory.
|
||||||
|
ULONG SizeOfImage; //The size of the DLL image, in bytes.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LDR_DLL_LOADED_NOTIFICATION_DATA {
|
||||||
|
ULONG Flags; //Reserved.
|
||||||
|
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
|
||||||
|
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
|
||||||
|
PVOID DllBase; //A pointer to the base address for the DLL in memory.
|
||||||
|
ULONG SizeOfImage; //The size of the DLL image, in bytes.
|
||||||
|
};
|
||||||
|
|
||||||
|
union LDR_DLL_NOTIFICATION_DATA {
|
||||||
|
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
|
||||||
|
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
|
||||||
|
};
|
||||||
|
|
||||||
|
using PLDR_DLL_NOTIFICATION_FUNCTION = VOID CALLBACK(_In_ ULONG NotificationReason, _In_ const LDR_DLL_NOTIFICATION_DATA* NotificationData, _In_opt_ PVOID Context);
|
||||||
|
|
||||||
|
NTSTATUS LdrRegisterDllNotification(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie);
|
||||||
|
NTSTATUS LdrUnregisterDllNotification(PVOID Cookie);
|
||||||
|
|
@ -15,18 +15,28 @@
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
// Windows Header Files (2)
|
// Windows Header Files (2)
|
||||||
|
#include <DbgHelp.h>
|
||||||
#include <Dbt.h>
|
#include <Dbt.h>
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
#include <PathCch.h>
|
#include <PathCch.h>
|
||||||
#include <Psapi.h>
|
#include <Psapi.h>
|
||||||
#include <ShlObj.h>
|
#include <ShlObj.h>
|
||||||
|
#include <Shlwapi.h>
|
||||||
#include <SubAuth.h>
|
#include <SubAuth.h>
|
||||||
#include <TlHelp32.h>
|
#include <TlHelp32.h>
|
||||||
|
|
||||||
|
// Windows Header Files (3)
|
||||||
|
#include <icmpapi.h> // Must be loaded after iphlpapi.h
|
||||||
|
|
||||||
// MSVC Compiler Intrinsic
|
// MSVC Compiler Intrinsic
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
|
|
||||||
|
// COM
|
||||||
|
#include <comdef.h>
|
||||||
|
|
||||||
// C++ Standard Libraries
|
// C++ Standard Libraries
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
@ -64,9 +74,6 @@
|
||||||
|
|
||||||
#include "unicode.h"
|
#include "unicode.h"
|
||||||
|
|
||||||
// Commonly used macros
|
|
||||||
#define DllExport extern "C" __declspec(dllexport)
|
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
extern HMODULE g_hModule;
|
extern HMODULE g_hModule;
|
||||||
extern HINSTANCE g_hGameInstance;
|
extern HINSTANCE g_hGameInstance;
|
||||||
|
|
|
||||||
|
|
@ -1,115 +1,92 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
||||||
|
|
||||||
struct RewrittenEntryPointParameters {
|
struct RewrittenEntryPointParameters {
|
||||||
void* pAllocation;
|
|
||||||
char* pEntrypoint;
|
char* pEntrypoint;
|
||||||
char* pEntrypointBytes;
|
|
||||||
size_t entrypointLength;
|
size_t entrypointLength;
|
||||||
char* pLoadInfo;
|
|
||||||
HANDLE hMainThread;
|
|
||||||
HANDLE hMainThreadContinue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
namespace thunks {
|
||||||
struct EntryPointThunkTemplate {
|
constexpr uint64_t Terminator = 0xCCCCCCCCCCCCCCCCu;
|
||||||
struct DUMMYSTRUCTNAME {
|
constexpr uint64_t Placeholder = 0x0606060606060606u;
|
||||||
struct {
|
|
||||||
const uint8_t op_mov_rdi[2]{ 0x48, 0xbf };
|
extern "C" void EntryPointReplacement();
|
||||||
void* ptr = nullptr;
|
extern "C" void RewrittenEntryPoint_Standalone();
|
||||||
} fn;
|
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
void* resolve_thunk_address(void (*pfn)()) {
|
||||||
} CallTrampoline;
|
const auto ptr = reinterpret_cast<uint8_t*>(pfn);
|
||||||
};
|
if (*ptr == 0xe9)
|
||||||
|
return ptr + 5 + *reinterpret_cast<int32_t*>(ptr + 1);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
struct TrampolineTemplate {
|
size_t get_thunk_length(void (*pfn)()) {
|
||||||
const struct {
|
size_t length = 0;
|
||||||
const uint8_t op_sub_rsp_imm[3]{ 0x48, 0x81, 0xec };
|
for (auto ptr = reinterpret_cast<char*>(resolve_thunk_address(pfn)); *reinterpret_cast<uint64_t*>(ptr) != Terminator; ptr++)
|
||||||
const uint32_t length = 0x80;
|
length++;
|
||||||
} stack_alloc;
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
struct DUMMYSTRUCTNAME {
|
template<typename T>
|
||||||
struct {
|
void* fill_placeholders(void* pfn, const T& value) {
|
||||||
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
|
auto ptr = static_cast<char*>(pfn);
|
||||||
void* val = nullptr;
|
|
||||||
} lpLibFileName;
|
|
||||||
|
|
||||||
struct {
|
while (*reinterpret_cast<uint64_t*>(ptr) != Placeholder)
|
||||||
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
|
ptr++;
|
||||||
decltype(&LoadLibraryW) ptr = nullptr;
|
|
||||||
} fn;
|
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
*reinterpret_cast<uint64_t*>(ptr) = 0;
|
||||||
} CallLoadLibrary_nethost;
|
*reinterpret_cast<T*>(ptr) = value;
|
||||||
|
return ptr + sizeof(value);
|
||||||
|
}
|
||||||
|
|
||||||
struct DUMMYSTRUCTNAME {
|
template<typename T, typename...TArgs>
|
||||||
struct {
|
void* fill_placeholders(void* ptr, const T& value, TArgs&&...more_values) {
|
||||||
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
|
return fill_placeholders(fill_placeholders(ptr, value), std::forward<TArgs>(more_values)...);
|
||||||
void* val = nullptr;
|
}
|
||||||
} lpLibFileName;
|
|
||||||
|
|
||||||
struct {
|
std::vector<char> create_entrypointreplacement() {
|
||||||
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
|
std::vector<char> buf(get_thunk_length(&EntryPointReplacement));
|
||||||
decltype(&LoadLibraryW) ptr = nullptr;
|
memcpy(buf.data(), resolve_thunk_address(&EntryPointReplacement), buf.size());
|
||||||
} fn;
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
std::vector<char> create_standalone_rewrittenentrypoint(const std::filesystem::path& dalamud_path) {
|
||||||
} CallLoadLibrary_DalamudBoot;
|
const auto nethost_path = std::filesystem::path(dalamud_path).replace_filename(L"nethost.dll");
|
||||||
|
|
||||||
struct {
|
// These are null terminated, since pointers are returned from .c_str()
|
||||||
const uint8_t hModule_op_mov_rcx_rax[3]{ 0x48, 0x89, 0xc1 };
|
const auto dalamud_path_wview = std::wstring_view(dalamud_path.c_str());
|
||||||
|
const auto nethost_path_wview = std::wstring_view(nethost_path.c_str());
|
||||||
|
|
||||||
struct {
|
// +2 is for null terminator
|
||||||
const uint8_t op_mov_rdx_imm[2]{ 0x48, 0xba };
|
const auto dalamud_path_view = std::span(reinterpret_cast<const char*>(dalamud_path_wview.data()), dalamud_path_wview.size() * 2 + 2);
|
||||||
void* val = nullptr;
|
const auto nethost_path_view = std::span(reinterpret_cast<const char*>(nethost_path_wview.data()), nethost_path_wview.size() * 2 + 2);
|
||||||
} lpProcName;
|
|
||||||
|
|
||||||
struct {
|
std::vector<char> buffer;
|
||||||
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
|
const auto thunk_template_length = thunks::get_thunk_length(&thunks::RewrittenEntryPoint_Standalone);
|
||||||
decltype(&GetProcAddress) ptr = nullptr;
|
buffer.reserve(thunk_template_length + dalamud_path_view.size() + nethost_path_view.size());
|
||||||
} fn;
|
buffer.resize(thunk_template_length);
|
||||||
|
memcpy(buffer.data(), resolve_thunk_address(&thunks::RewrittenEntryPoint_Standalone), thunk_template_length);
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
// &::GetProcAddress will return Dalamud.dll's import table entry.
|
||||||
} CallGetProcAddress;
|
// GetProcAddress(..., "GetProcAddress") returns the address inside kernel32.dll.
|
||||||
|
const auto kernel32 = GetModuleHandleA("kernel32.dll");
|
||||||
|
|
||||||
struct {
|
thunks::fill_placeholders(buffer.data(),
|
||||||
const uint8_t op_add_rsp_imm[3]{ 0x48, 0x81, 0xc4 };
|
/* pfnLoadLibraryW = */ GetProcAddress(kernel32, "LoadLibraryW"),
|
||||||
const uint32_t length = 0x80;
|
/* pfnGetProcAddress = */ GetProcAddress(kernel32, "GetProcAddress"),
|
||||||
} stack_release;
|
/* pRewrittenEntryPointParameters = */ Placeholder,
|
||||||
|
/* nNethostOffset = */ 0,
|
||||||
struct DUMMYSTRUCTNAME2 {
|
/* nDalamudOffset = */ nethost_path_view.size_bytes()
|
||||||
// rdi := returned value from GetProcAddress
|
);
|
||||||
const uint8_t op_mov_rdi_rax[3]{ 0x48, 0x89, 0xc7 };
|
buffer.insert(buffer.end(), nethost_path_view.begin(), nethost_path_view.end());
|
||||||
// rax := return address
|
buffer.insert(buffer.end(), dalamud_path_view.begin(), dalamud_path_view.end());
|
||||||
const uint8_t op_pop_rax[1]{ 0x58 };
|
return buffer;
|
||||||
|
}
|
||||||
// rax := rax - sizeof thunk (last instruction must be call)
|
}
|
||||||
struct {
|
|
||||||
const uint8_t op_sub_rax_imm4[2]{ 0x48, 0x2d };
|
|
||||||
const uint32_t displacement = static_cast<uint32_t>(sizeof EntryPointThunkTemplate);
|
|
||||||
} op_sub_rax_to_entry_point;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
|
|
||||||
void* val = nullptr;
|
|
||||||
} param;
|
|
||||||
|
|
||||||
const uint8_t op_push_rax[1]{ 0x50 };
|
|
||||||
const uint8_t op_jmp_rdi[2]{ 0xff, 0xe7 };
|
|
||||||
} CallInjectEntryPoint;
|
|
||||||
|
|
||||||
const char buf_CallGetProcAddress_lpProcName[20] = "RewrittenEntryPoint";
|
|
||||||
uint8_t buf_EntryPointBackup[sizeof EntryPointThunkTemplate]{};
|
|
||||||
|
|
||||||
#pragma pack(push, 8)
|
|
||||||
RewrittenEntryPointParameters parameters{};
|
|
||||||
#pragma pack(pop)
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, void* data, size_t len) {
|
void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, void* data, size_t len) {
|
||||||
SIZE_T read = 0;
|
SIZE_T read = 0;
|
||||||
|
|
@ -126,6 +103,7 @@ void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, T& data) {
|
||||||
|
|
||||||
void write_process_memory_or_throw(HANDLE hProcess, void* pAddress, const void* data, size_t len) {
|
void write_process_memory_or_throw(HANDLE hProcess, void* pAddress, const void* data, size_t len) {
|
||||||
SIZE_T written = 0;
|
SIZE_T written = 0;
|
||||||
|
const utils::memory_tenderizer tenderizer(hProcess, pAddress, len, PAGE_EXECUTE_READWRITE);
|
||||||
if (!WriteProcessMemory(hProcess, pAddress, data, len, &written))
|
if (!WriteProcessMemory(hProcess, pAddress, data, len, &written))
|
||||||
throw std::runtime_error("WriteProcessMemory failure");
|
throw std::runtime_error("WriteProcessMemory failure");
|
||||||
if (written != len)
|
if (written != len)
|
||||||
|
|
@ -170,10 +148,17 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
|
||||||
exe.read(reinterpret_cast<char*>(&exe_section_headers[0]), sizeof IMAGE_SECTION_HEADER * exe_section_headers.size());
|
exe.read(reinterpret_cast<char*>(&exe_section_headers[0]), sizeof IMAGE_SECTION_HEADER * exe_section_headers.size());
|
||||||
if (!exe)
|
if (!exe)
|
||||||
throw std::runtime_error("Game executable is corrupt (Truncated section header).");
|
throw std::runtime_error("Game executable is corrupt (Truncated section header).");
|
||||||
|
|
||||||
|
SYSTEM_INFO sysinfo;
|
||||||
|
GetSystemInfo(&sysinfo);
|
||||||
|
|
||||||
for (MEMORY_BASIC_INFORMATION mbi{};
|
for (MEMORY_BASIC_INFORMATION mbi{};
|
||||||
VirtualQueryEx(hProcess, mbi.BaseAddress, &mbi, sizeof mbi);
|
VirtualQueryEx(hProcess, mbi.BaseAddress, &mbi, sizeof mbi);
|
||||||
mbi.BaseAddress = static_cast<char*>(mbi.BaseAddress) + mbi.RegionSize) {
|
mbi.BaseAddress = static_cast<char*>(mbi.BaseAddress) + mbi.RegionSize) {
|
||||||
|
|
||||||
|
// wine: apparently there exists a RegionSize of 0xFFF
|
||||||
|
mbi.RegionSize = (mbi.RegionSize + sysinfo.dwPageSize - 1) / sysinfo.dwPageSize * sysinfo.dwPageSize;
|
||||||
|
|
||||||
if (!(mbi.State & MEM_COMMIT) || mbi.Type != MEM_IMAGE)
|
if (!(mbi.State & MEM_COMMIT) || mbi.Type != MEM_IMAGE)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -241,31 +226,22 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
|
||||||
throw std::runtime_error("corresponding base address not found");
|
throw std::runtime_error("corresponding base address not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string from_utf16(const std::wstring& wstr, UINT codePage = CP_UTF8) {
|
|
||||||
std::string str(WideCharToMultiByte(codePage, 0, &wstr[0], static_cast<int>(wstr.size()), nullptr, 0, nullptr, nullptr), 0);
|
|
||||||
WideCharToMultiByte(codePage, 0, &wstr[0], static_cast<int>(wstr.size()), &str[0], static_cast<int>(str.size()), nullptr, nullptr);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring to_utf16(const std::string& str, UINT codePage = CP_UTF8, bool errorOnInvalidChars = false) {
|
|
||||||
std::wstring wstr(MultiByteToWideChar(codePage, 0, &str[0], static_cast<int>(str.size()), nullptr, 0), 0);
|
|
||||||
MultiByteToWideChar(codePage, errorOnInvalidChars ? MB_ERR_INVALID_CHARS : 0, &str[0], static_cast<int>(str.size()), &wstr[0], static_cast<int>(wstr.size()));
|
|
||||||
return wstr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Rewrite target process' entry point so that this DLL can be loaded and executed first.
|
/// @brief Rewrite target process' entry point so that this DLL can be loaded and executed first.
|
||||||
/// @param hProcess Process handle.
|
/// @param hProcess Process handle.
|
||||||
/// @param pcwzPath Path to target process.
|
/// @param pcwzPath Path to target process.
|
||||||
/// @param pcszLoadInfo JSON string to be passed to Initialize.
|
/// @param pcwzLoadInfo JSON string to be passed to Initialize.
|
||||||
/// @return 0 if successful; nonzero if unsuccessful
|
/// @return null if successful; memory containing wide string allocated via GlobalAlloc if unsuccessful
|
||||||
///
|
///
|
||||||
/// When the process has just been started up via CreateProcess (CREATE_SUSPENDED), GetModuleFileName and alikes result in an error.
|
/// When the process has just been started up via CreateProcess (CREATE_SUSPENDED), GetModuleFileName and alikes result in an error.
|
||||||
/// Instead, we have to enumerate through all the files mapped into target process' virtual address space and find the base address
|
/// Instead, we have to enumerate through all the files mapped into target process' virtual address space and find the base address
|
||||||
/// of memory region corresponding to the path given.
|
/// of memory region corresponding to the path given.
|
||||||
///
|
///
|
||||||
DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) {
|
extern "C" HRESULT WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) {
|
||||||
|
std::wstring last_operation;
|
||||||
|
SetLastError(ERROR_SUCCESS);
|
||||||
try {
|
try {
|
||||||
const auto base_address = reinterpret_cast<char*>(get_mapped_image_base_address(hProcess, pcwzPath));
|
last_operation = L"get_mapped_image_base_address";
|
||||||
|
const auto base_address = static_cast<char*>(get_mapped_image_base_address(hProcess, pcwzPath));
|
||||||
|
|
||||||
IMAGE_DOS_HEADER dos_header{};
|
IMAGE_DOS_HEADER dos_header{};
|
||||||
union {
|
union {
|
||||||
|
|
@ -273,113 +249,150 @@ DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t*
|
||||||
IMAGE_NT_HEADERS64 nt_header64{};
|
IMAGE_NT_HEADERS64 nt_header64{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
last_operation = L"read_process_memory_or_throw(base_address)";
|
||||||
read_process_memory_or_throw(hProcess, base_address, dos_header);
|
read_process_memory_or_throw(hProcess, base_address, dos_header);
|
||||||
|
|
||||||
|
last_operation = L"read_process_memory_or_throw(base_address + dos_header.e_lfanew)";
|
||||||
read_process_memory_or_throw(hProcess, base_address + dos_header.e_lfanew, nt_header64);
|
read_process_memory_or_throw(hProcess, base_address + dos_header.e_lfanew, nt_header64);
|
||||||
const auto entrypoint = base_address + (nt_header32.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC
|
const auto entrypoint = base_address + (nt_header32.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC
|
||||||
? nt_header32.OptionalHeader.AddressOfEntryPoint
|
? nt_header32.OptionalHeader.AddressOfEntryPoint
|
||||||
: nt_header64.OptionalHeader.AddressOfEntryPoint);
|
: nt_header64.OptionalHeader.AddressOfEntryPoint);
|
||||||
|
|
||||||
auto path = get_path_from_local_module(g_hModule).wstring();
|
last_operation = L"get_path_from_local_module(g_hModule)";
|
||||||
path.resize(path.size() + 1); // ensure null termination
|
auto local_module_path = get_path_from_local_module(g_hModule);
|
||||||
auto path_bytes = std::span(reinterpret_cast<const char*>(&path[0]), std::span(path).size_bytes());
|
|
||||||
|
last_operation = L"thunks::create_standalone_rewrittenentrypoint(local_module_path)";
|
||||||
|
auto standalone_rewrittenentrypoint = thunks::create_standalone_rewrittenentrypoint(local_module_path);
|
||||||
|
|
||||||
auto nethost_path = (get_path_from_local_module(g_hModule).parent_path() / L"nethost.dll").wstring();
|
last_operation = L"thunks::create_entrypointreplacement()";
|
||||||
nethost_path.resize(nethost_path.size() + 1); // ensure null termination
|
auto entrypoint_replacement = thunks::create_entrypointreplacement();
|
||||||
auto nethost_path_bytes = std::span(reinterpret_cast<const char*>(&nethost_path[0]), std::span(nethost_path).size_bytes());
|
|
||||||
|
|
||||||
auto load_info = from_utf16(pcwzLoadInfo);
|
last_operation = L"unicode::convert<std::string>(pcwzLoadInfo)";
|
||||||
|
auto load_info = unicode::convert<std::string>(pcwzLoadInfo);
|
||||||
load_info.resize(load_info.size() + 1); //ensure null termination
|
load_info.resize(load_info.size() + 1); //ensure null termination
|
||||||
|
|
||||||
// Allocate full buffer in advance to keep reference to trampoline valid.
|
const auto bufferSize = sizeof(RewrittenEntryPointParameters) + entrypoint_replacement.size() + load_info.size() + standalone_rewrittenentrypoint.size();
|
||||||
std::vector<uint8_t> buffer(sizeof TrampolineTemplate + load_info.size() + nethost_path_bytes.size() + path_bytes.size());
|
last_operation = std::format(L"std::vector alloc({}b)", bufferSize);
|
||||||
auto& trampoline = *reinterpret_cast<TrampolineTemplate*>(&buffer[0]);
|
std::vector<uint8_t> buffer(bufferSize);
|
||||||
const auto load_info_buffer = std::span(buffer).subspan(sizeof trampoline, load_info.size());
|
|
||||||
const auto nethost_path_buffer = std::span(buffer).subspan(sizeof trampoline + load_info.size(), nethost_path_bytes.size());
|
|
||||||
const auto dalamud_path_buffer = std::span(buffer).subspan(sizeof trampoline + load_info.size() + nethost_path_bytes.size(), path_bytes.size());
|
|
||||||
|
|
||||||
new(&trampoline)TrampolineTemplate(); // this line initializes given buffer instead of allocating memory
|
|
||||||
memcpy(&load_info_buffer[0], &load_info[0], load_info_buffer.size());
|
|
||||||
memcpy(&nethost_path_buffer[0], &nethost_path_bytes[0], nethost_path_buffer.size());
|
|
||||||
memcpy(&dalamud_path_buffer[0], &path_bytes[0], dalamud_path_buffer.size());
|
|
||||||
|
|
||||||
// Backup remote process' original entry point.
|
|
||||||
read_process_memory_or_throw(hProcess, entrypoint, trampoline.buf_EntryPointBackup);
|
|
||||||
|
|
||||||
// Allocate buffer in remote process, which will be used to fill addresses in the local buffer.
|
// Allocate buffer in remote process, which will be used to fill addresses in the local buffer.
|
||||||
const auto remote_buffer = reinterpret_cast<char*>(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
|
last_operation = std::format(L"VirtualAllocEx({}b)", bufferSize);
|
||||||
|
const auto remote_buffer = static_cast<char*>(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
|
||||||
// Fill the values to be used in RewrittenEntryPoint
|
|
||||||
trampoline.parameters = {
|
auto& params = *reinterpret_cast<RewrittenEntryPointParameters*>(buffer.data());
|
||||||
.pAllocation = remote_buffer,
|
params.entrypointLength = entrypoint_replacement.size();
|
||||||
.pEntrypoint = entrypoint,
|
params.pEntrypoint = entrypoint;
|
||||||
.pEntrypointBytes = remote_buffer + offsetof(TrampolineTemplate, buf_EntryPointBackup),
|
|
||||||
.entrypointLength = sizeof trampoline.buf_EntryPointBackup,
|
|
||||||
.pLoadInfo = remote_buffer + (&load_info_buffer[0] - &buffer[0]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fill the addresses referred in machine code.
|
// Backup original entry point.
|
||||||
trampoline.CallLoadLibrary_nethost.lpLibFileName.val = remote_buffer + (&nethost_path_buffer[0] - &buffer[0]);
|
last_operation = std::format(L"read_process_memory_or_throw(entrypoint, {}b)", entrypoint_replacement.size());
|
||||||
trampoline.CallLoadLibrary_nethost.fn.ptr = LoadLibraryW;
|
read_process_memory_or_throw(hProcess, entrypoint, &buffer[sizeof params], entrypoint_replacement.size());
|
||||||
trampoline.CallLoadLibrary_DalamudBoot.lpLibFileName.val = remote_buffer + (&dalamud_path_buffer[0] - &buffer[0]);
|
|
||||||
trampoline.CallLoadLibrary_DalamudBoot.fn.ptr = LoadLibraryW;
|
memcpy(&buffer[sizeof params + entrypoint_replacement.size()], load_info.data(), load_info.size());
|
||||||
trampoline.CallGetProcAddress.lpProcName.val = remote_buffer + offsetof(TrampolineTemplate, buf_CallGetProcAddress_lpProcName);
|
|
||||||
trampoline.CallGetProcAddress.fn.ptr = GetProcAddress;
|
last_operation = L"thunks::fill_placeholders(EntryPointReplacement)";
|
||||||
trampoline.CallInjectEntryPoint.param.val = remote_buffer + offsetof(TrampolineTemplate, parameters);
|
thunks::fill_placeholders(standalone_rewrittenentrypoint.data(), remote_buffer);
|
||||||
|
memcpy(&buffer[sizeof params + entrypoint_replacement.size() + load_info.size()], standalone_rewrittenentrypoint.data(), standalone_rewrittenentrypoint.size());
|
||||||
|
|
||||||
// Write the local buffer into the buffer in remote process.
|
// Write the local buffer into the buffer in remote process.
|
||||||
|
last_operation = std::format(L"write_process_memory_or_throw(remote_buffer, {}b)", buffer.size());
|
||||||
write_process_memory_or_throw(hProcess, remote_buffer, buffer.data(), buffer.size());
|
write_process_memory_or_throw(hProcess, remote_buffer, buffer.data(), buffer.size());
|
||||||
|
|
||||||
// Overwrite remote process' entry point with a thunk that immediately calls our trampoline function.
|
last_operation = L"thunks::fill_placeholders(RewrittenEntryPoint_Standalone::pRewrittenEntryPointParameters)";
|
||||||
EntryPointThunkTemplate thunk{};
|
thunks::fill_placeholders(entrypoint_replacement.data(), remote_buffer + sizeof params + entrypoint_replacement.size() + load_info.size());
|
||||||
thunk.CallTrampoline.fn.ptr = remote_buffer;
|
|
||||||
write_process_memory_or_throw(hProcess, entrypoint, thunk);
|
|
||||||
|
|
||||||
return 0;
|
// Overwrite remote process' entry point with a thunk that will load our DLLs and call our trampoline function.
|
||||||
|
last_operation = std::format(L"write_process_memory_or_throw(entrypoint={:X}, {}b)", reinterpret_cast<uintptr_t>(entrypoint), buffer.size());
|
||||||
|
write_process_memory_or_throw(hProcess, entrypoint, entrypoint_replacement.data(), entrypoint_replacement.size());
|
||||||
|
FlushInstructionCache(hProcess, entrypoint, entrypoint_replacement.size());
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
OutputDebugStringA(std::format("RewriteRemoteEntryPoint failure: {} (GetLastError: {})\n", e.what(), GetLastError()).c_str());
|
const auto err = GetLastError();
|
||||||
return 1;
|
const auto hr = err == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(err);
|
||||||
}
|
auto formatted = std::format(
|
||||||
}
|
L"{}: {} ({})",
|
||||||
|
last_operation,
|
||||||
|
unicode::convert<std::wstring>(e.what()),
|
||||||
|
utils::format_win32_error(err));
|
||||||
|
OutputDebugStringW((formatted + L"\r\n").c_str());
|
||||||
|
|
||||||
/// @deprecated
|
ICreateErrorInfoPtr cei;
|
||||||
DllExport DWORD WINAPI RewriteRemoteEntryPoint(HANDLE hProcess, const wchar_t* pcwzPath, const char* pcszLoadInfo) {
|
if (FAILED(CreateErrorInfo(&cei)))
|
||||||
return RewriteRemoteEntryPointW(hProcess, pcwzPath, to_utf16(pcszLoadInfo).c_str());
|
return hr;
|
||||||
|
if (FAILED(cei->SetSource(const_cast<LPOLESTR>(L"Dalamud.Boot"))))
|
||||||
|
return hr;
|
||||||
|
if (FAILED(cei->SetDescription(const_cast<LPOLESTR>(formatted.c_str()))))
|
||||||
|
return hr;
|
||||||
|
|
||||||
|
IErrorInfoPtr ei;
|
||||||
|
if (FAILED(cei.QueryInterface(IID_PPV_ARGS(&ei))))
|
||||||
|
return hr;
|
||||||
|
|
||||||
|
(void)SetErrorInfo(0, ei);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Entry point function "called" instead of game's original main entry point.
|
/// @brief Entry point function "called" instead of game's original main entry point.
|
||||||
/// @param params Parameters set up from RewriteRemoteEntryPoint.
|
/// @param params Parameters set up from RewriteRemoteEntryPoint.
|
||||||
DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) {
|
extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) {
|
||||||
params.hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
|
HANDLE hMainThreadContinue = nullptr;
|
||||||
if (!params.hMainThreadContinue)
|
auto hr = S_OK;
|
||||||
ExitProcess(-1);
|
std::wstring last_operation;
|
||||||
|
std::wstring exc_msg;
|
||||||
|
SetLastError(ERROR_SUCCESS);
|
||||||
|
|
||||||
// Do whatever the work in a separate thread to minimize the stack usage at this context,
|
try {
|
||||||
// as this function really should have been a naked procedure but __declspec(naked) isn't supported in x64 version of msvc.
|
const auto pOriginalEntryPointBytes = reinterpret_cast<char*>(¶ms) + sizeof(params);
|
||||||
params.hMainThread = CreateThread(nullptr, 0, [](void* p) -> DWORD {
|
const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength;
|
||||||
try {
|
|
||||||
std::string loadInfo;
|
|
||||||
auto& params = *reinterpret_cast<RewrittenEntryPointParameters*>(p);
|
|
||||||
{
|
|
||||||
// Restore original entry point.
|
|
||||||
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
|
|
||||||
write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, params.pEntrypointBytes, params.entrypointLength);
|
|
||||||
|
|
||||||
// Make a copy of load info, as the whole params will be freed after this code block.
|
// Restore original entry point.
|
||||||
loadInfo = params.pLoadInfo;
|
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
|
||||||
}
|
last_operation = L"restore original entry point";
|
||||||
|
write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength);
|
||||||
|
FlushInstructionCache(GetCurrentProcess(), params.pEntrypoint, params.entrypointLength);
|
||||||
|
|
||||||
if (const auto err = InitializeImpl(&loadInfo[0], params.hMainThreadContinue))
|
hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
|
||||||
throw std::exception(std::format("{:08X}", err).c_str());
|
last_operation = L"hMainThreadContinue = CreateEventW";
|
||||||
return 0;
|
if (!hMainThreadContinue)
|
||||||
} catch (const std::exception& e) {
|
throw std::runtime_error("CreateEventW");
|
||||||
MessageBoxA(nullptr, std::format("Failed to load Dalamud.\n\nError: {}", e.what()).c_str(), "Dalamud.Boot", MB_OK | MB_ICONERROR);
|
|
||||||
ExitProcess(-1);
|
last_operation = L"InitializeImpl";
|
||||||
|
hr = InitializeImpl(pLoadInfo, hMainThreadContinue);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
if (hr == S_OK) {
|
||||||
|
const auto err = GetLastError();
|
||||||
|
hr = err == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(err);
|
||||||
}
|
}
|
||||||
}, ¶ms, 0, nullptr);
|
|
||||||
if (!params.hMainThread)
|
|
||||||
ExitProcess(-1);
|
|
||||||
|
|
||||||
CloseHandle(params.hMainThread);
|
ICreateErrorInfoPtr cei;
|
||||||
WaitForSingleObject(params.hMainThreadContinue, INFINITE);
|
IErrorInfoPtr ei;
|
||||||
VirtualFree(params.pAllocation, 0, MEM_RELEASE);
|
if (SUCCEEDED(CreateErrorInfo(&cei))
|
||||||
|
&& SUCCEEDED(cei->SetDescription(const_cast<wchar_t*>(unicode::convert<std::wstring>(e.what()).c_str())))
|
||||||
|
&& SUCCEEDED(cei.QueryInterface(IID_PPV_ARGS(&ei)))) {
|
||||||
|
(void)SetErrorInfo(0, ei);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
const _com_error err(hr);
|
||||||
|
auto desc = err.Description();
|
||||||
|
if (desc.length() == 0)
|
||||||
|
desc = err.ErrorMessage();
|
||||||
|
if (MessageBoxW(nullptr, std::format(
|
||||||
|
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\n{}\n{}",
|
||||||
|
last_operation,
|
||||||
|
desc.GetBSTR()).c_str(),
|
||||||
|
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
|
||||||
|
ExitProcess(-1);
|
||||||
|
if (hMainThreadContinue) {
|
||||||
|
CloseHandle(hMainThreadContinue);
|
||||||
|
hMainThreadContinue = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hMainThreadContinue)
|
||||||
|
WaitForSingleObject(hMainThreadContinue, INFINITE);
|
||||||
|
|
||||||
|
VirtualFree(¶ms, 0, MEM_RELEASE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
82
Dalamud.Boot/rewrite_entrypoint_thunks.asm
Normal file
82
Dalamud.Boot/rewrite_entrypoint_thunks.asm
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
PUBLIC EntryPointReplacement
|
||||||
|
PUBLIC RewrittenEntryPoint_Standalone
|
||||||
|
PUBLIC RewrittenEntryPoint
|
||||||
|
|
||||||
|
; 06 and 07 are invalid opcodes
|
||||||
|
; CC is int3 = bp
|
||||||
|
; using 0CCCCCCCCCCCCCCCCh as function terminator
|
||||||
|
; using 00606060606060606h as placeholders
|
||||||
|
|
||||||
|
TERMINATOR = 0CCCCCCCCCCCCCCCCh
|
||||||
|
PLACEHOLDER = 00606060606060606h
|
||||||
|
|
||||||
|
.code
|
||||||
|
|
||||||
|
EntryPointReplacement PROC
|
||||||
|
start:
|
||||||
|
; rsp % 0x10 = 0x08
|
||||||
|
lea rax, [start]
|
||||||
|
push rax
|
||||||
|
|
||||||
|
; rsp % 0x10 = 0x00
|
||||||
|
mov rax, PLACEHOLDER
|
||||||
|
|
||||||
|
; this calls RewrittenEntryPoint_Standalone
|
||||||
|
jmp rax
|
||||||
|
|
||||||
|
dq TERMINATOR
|
||||||
|
EntryPointReplacement ENDP
|
||||||
|
|
||||||
|
RewrittenEntryPoint_Standalone PROC
|
||||||
|
start:
|
||||||
|
; stack is aligned to 0x10; see above
|
||||||
|
sub rsp, 20h
|
||||||
|
lea rcx, [embeddedData]
|
||||||
|
add rcx, qword ptr [nNethostOffset]
|
||||||
|
call qword ptr [pfnLoadLibraryW]
|
||||||
|
|
||||||
|
lea rcx, [embeddedData]
|
||||||
|
add rcx, qword ptr [nDalamudOffset]
|
||||||
|
call qword ptr [pfnLoadLibraryW]
|
||||||
|
|
||||||
|
mov rcx, rax
|
||||||
|
lea rdx, [pcszEntryPointName]
|
||||||
|
call qword ptr [pfnGetProcAddress]
|
||||||
|
|
||||||
|
mov rcx, qword ptr [pRewrittenEntryPointParameters]
|
||||||
|
; this calls RewrittenEntryPoint
|
||||||
|
jmp rax
|
||||||
|
|
||||||
|
pfnLoadLibraryW:
|
||||||
|
dq PLACEHOLDER
|
||||||
|
|
||||||
|
pfnGetProcAddress:
|
||||||
|
dq PLACEHOLDER
|
||||||
|
|
||||||
|
pRewrittenEntryPointParameters:
|
||||||
|
dq PLACEHOLDER
|
||||||
|
|
||||||
|
nNethostOffset:
|
||||||
|
dq PLACEHOLDER
|
||||||
|
|
||||||
|
nDalamudOffset:
|
||||||
|
dq PLACEHOLDER
|
||||||
|
|
||||||
|
pcszEntryPointName:
|
||||||
|
db "RewrittenEntryPoint", 0
|
||||||
|
|
||||||
|
embeddedData:
|
||||||
|
|
||||||
|
dq TERMINATOR
|
||||||
|
RewrittenEntryPoint_Standalone ENDP
|
||||||
|
|
||||||
|
EXTERN RewrittenEntryPoint_AdjustedStack :PROC
|
||||||
|
|
||||||
|
RewrittenEntryPoint PROC
|
||||||
|
; stack is aligned to 0x10; see above
|
||||||
|
call RewrittenEntryPoint_AdjustedStack
|
||||||
|
add rsp, 20h
|
||||||
|
ret
|
||||||
|
RewrittenEntryPoint ENDP
|
||||||
|
|
||||||
|
END
|
||||||
|
|
@ -408,14 +408,20 @@ utils::signature_finder::result utils::signature_finder::find_one() const {
|
||||||
return find(1, 1, false).front();
|
return find(1, 1, false).front();
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect) : m_data(reinterpret_cast<char*>(const_cast<void*>(pAddress)), length) {
|
utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect)
|
||||||
|
: memory_tenderizer(GetCurrentProcess(), pAddress, length, dwNewProtect) {
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::memory_tenderizer::memory_tenderizer(HANDLE hProcess, const void* pAddress, size_t length, DWORD dwNewProtect)
|
||||||
|
: m_process(hProcess)
|
||||||
|
, m_data(static_cast<char*>(const_cast<void*>(pAddress)), length) {
|
||||||
try {
|
try {
|
||||||
for (auto pCoveredAddress = &m_data[0];
|
for (auto pCoveredAddress = m_data.data();
|
||||||
pCoveredAddress < &m_data[0] + m_data.size();
|
pCoveredAddress < m_data.data() + m_data.size();
|
||||||
pCoveredAddress = reinterpret_cast<char*>(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
|
pCoveredAddress = static_cast<char*>(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
|
||||||
|
|
||||||
MEMORY_BASIC_INFORMATION region{};
|
MEMORY_BASIC_INFORMATION region{};
|
||||||
if (!VirtualQuery(pCoveredAddress, ®ion, sizeof region)) {
|
if (!VirtualQueryEx(hProcess, pCoveredAddress, ®ion, sizeof region)) {
|
||||||
throw std::runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"VirtualQuery(addr=0x{:X}, ..., cb={}) failed with Win32 code 0x{:X}",
|
"VirtualQuery(addr=0x{:X}, ..., cb={}) failed with Win32 code 0x{:X}",
|
||||||
reinterpret_cast<size_t>(pCoveredAddress),
|
reinterpret_cast<size_t>(pCoveredAddress),
|
||||||
|
|
@ -423,7 +429,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
|
||||||
GetLastError()));
|
GetLastError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VirtualProtect(region.BaseAddress, region.RegionSize, dwNewProtect, ®ion.Protect)) {
|
if (!VirtualProtectEx(hProcess, region.BaseAddress, region.RegionSize, dwNewProtect, ®ion.Protect)) {
|
||||||
throw std::runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"(Change)VirtualProtect(addr=0x{:X}, size=0x{:X}, ..., ...) failed with Win32 code 0x{:X}",
|
"(Change)VirtualProtect(addr=0x{:X}, size=0x{:X}, ..., ...) failed with Win32 code 0x{:X}",
|
||||||
reinterpret_cast<size_t>(region.BaseAddress),
|
reinterpret_cast<size_t>(region.BaseAddress),
|
||||||
|
|
@ -436,7 +442,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
|
||||||
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
||||||
if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
|
if (!VirtualProtectEx(hProcess, region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
|
||||||
// Could not restore; fast fail
|
// Could not restore; fast fail
|
||||||
__fastfail(GetLastError());
|
__fastfail(GetLastError());
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +454,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
|
||||||
|
|
||||||
utils::memory_tenderizer::~memory_tenderizer() {
|
utils::memory_tenderizer::~memory_tenderizer() {
|
||||||
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
||||||
if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
|
if (!VirtualProtectEx(m_process, region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
|
||||||
// Could not restore; fast fail
|
// Could not restore; fast fail
|
||||||
__fastfail(GetLastError());
|
__fastfail(GetLastError());
|
||||||
}
|
}
|
||||||
|
|
@ -578,19 +584,6 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool utils::is_running_on_linux() {
|
|
||||||
if (get_env<bool>(L"XL_WINEONLINUX"))
|
|
||||||
return true;
|
|
||||||
HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
|
|
||||||
if (!hntdll)
|
|
||||||
return true;
|
|
||||||
if (GetProcAddress(hntdll, "wine_get_version"))
|
|
||||||
return true;
|
|
||||||
if (GetProcAddress(hntdll, "wine_get_host_version"))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path utils::get_module_path(HMODULE hModule) {
|
std::filesystem::path utils::get_module_path(HMODULE hModule) {
|
||||||
std::wstring buf(MAX_PATH, L'\0');
|
std::wstring buf(MAX_PATH, L'\0');
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -657,3 +650,25 @@ std::wstring utils::escape_shell_arg(const std::wstring& arg) {
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::wstring utils::format_win32_error(DWORD err) {
|
||||||
|
wchar_t* pwszMsg = nullptr;
|
||||||
|
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||||
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
nullptr,
|
||||||
|
err,
|
||||||
|
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
||||||
|
reinterpret_cast<LPWSTR>(&pwszMsg),
|
||||||
|
0,
|
||||||
|
nullptr);
|
||||||
|
if (pwszMsg) {
|
||||||
|
std::wstring result = std::format(L"Win32 error ({}=0x{:X}): {}", err, err, pwszMsg);
|
||||||
|
while (!result.empty() && std::isspace(result.back()))
|
||||||
|
result.pop_back();
|
||||||
|
LocalFree(pwszMsg);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::format(L"Win32 error ({}=0x{:X})", err, err);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,10 +111,13 @@ namespace utils {
|
||||||
};
|
};
|
||||||
|
|
||||||
class memory_tenderizer {
|
class memory_tenderizer {
|
||||||
|
HANDLE m_process;
|
||||||
std::span<char> m_data;
|
std::span<char> m_data;
|
||||||
std::vector<MEMORY_BASIC_INFORMATION> m_regions;
|
std::vector<MEMORY_BASIC_INFORMATION> m_regions;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
memory_tenderizer(HANDLE hProcess, const void* pAddress, size_t length, DWORD dwNewProtect);
|
||||||
|
|
||||||
memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect);
|
memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect);
|
||||||
|
|
||||||
template<typename T, typename = std::enable_if_t<std::is_trivial_v<T>&& std::is_standard_layout_v<T>>>
|
template<typename T, typename = std::enable_if_t<std::is_trivial_v<T>&& std::is_standard_layout_v<T>>>
|
||||||
|
|
@ -264,8 +267,6 @@ namespace utils {
|
||||||
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_running_on_linux();
|
|
||||||
|
|
||||||
std::filesystem::path get_module_path(HMODULE hModule);
|
std::filesystem::path get_module_path(HMODULE hModule);
|
||||||
|
|
||||||
/// @brief Find the game main window.
|
/// @brief Find the game main window.
|
||||||
|
|
@ -275,4 +276,6 @@ namespace utils {
|
||||||
void wait_for_game_window();
|
void wait_for_game_window();
|
||||||
|
|
||||||
std::wstring escape_shell_arg(const std::wstring& arg);
|
std::wstring escape_shell_arg(const std::wstring& arg);
|
||||||
|
|
||||||
|
std::wstring format_win32_error(DWORD err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "hooks.h"
|
||||||
|
|
||||||
#include "crashhandler_shared.h"
|
#include "crashhandler_shared.h"
|
||||||
#include "DalamudStartInfo.h"
|
#include "DalamudStartInfo.h"
|
||||||
|
|
@ -24,8 +25,10 @@
|
||||||
|
|
||||||
PVOID g_veh_handle = nullptr;
|
PVOID g_veh_handle = nullptr;
|
||||||
bool g_veh_do_full_dump = false;
|
bool g_veh_do_full_dump = false;
|
||||||
|
std::optional<hooks::import_hook<decltype(SetUnhandledExceptionFilter)>> g_HookSetUnhandledExceptionFilter;
|
||||||
|
|
||||||
HANDLE g_crashhandler_process = nullptr;
|
HANDLE g_crashhandler_process = nullptr;
|
||||||
|
HANDLE g_crashhandler_event = nullptr;
|
||||||
HANDLE g_crashhandler_pipe_write = nullptr;
|
HANDLE g_crashhandler_pipe_write = nullptr;
|
||||||
|
|
||||||
std::recursive_mutex g_exception_handler_mutex;
|
std::recursive_mutex g_exception_handler_mutex;
|
||||||
|
|
@ -101,8 +104,24 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
|
||||||
|
|
||||||
static void append_injector_launch_args(std::vector<std::wstring>& args)
|
static void append_injector_launch_args(std::vector<std::wstring>& args)
|
||||||
{
|
{
|
||||||
args.emplace_back(L"-g");
|
args.emplace_back(L"--game=\"" + utils::loaded_module::current_process().path().wstring() + L"\"");
|
||||||
args.emplace_back(utils::loaded_module::current_process().path().wstring());
|
switch (g_startInfo.DalamudLoadMethod) {
|
||||||
|
case DalamudStartInfo::LoadMethod::Entrypoint:
|
||||||
|
args.emplace_back(L"--mode=entrypoint");
|
||||||
|
break;
|
||||||
|
case DalamudStartInfo::LoadMethod::DllInject:
|
||||||
|
args.emplace_back(L"--mode=inject");
|
||||||
|
}
|
||||||
|
args.emplace_back(L"--dalamud-working-directory=\"" + unicode::convert<std::wstring>(g_startInfo.WorkingDirectory) + L"\"");
|
||||||
|
args.emplace_back(L"--dalamud-configuration-path=\"" + unicode::convert<std::wstring>(g_startInfo.ConfigurationPath) + L"\"");
|
||||||
|
args.emplace_back(L"--logpath=\"" + unicode::convert<std::wstring>(g_startInfo.LogPath) + 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-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
|
||||||
|
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));
|
||||||
|
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler
|
||||||
|
|
||||||
if (g_startInfo.BootShowConsole)
|
if (g_startInfo.BootShowConsole)
|
||||||
args.emplace_back(L"--console");
|
args.emplace_back(L"--console");
|
||||||
if (g_startInfo.BootEnableEtw)
|
if (g_startInfo.BootEnableEtw)
|
||||||
|
|
@ -128,6 +147,85 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
|
||||||
}
|
}
|
||||||
|
|
||||||
LONG exception_handler(EXCEPTION_POINTERS* ex)
|
LONG exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
|
{
|
||||||
|
// block any other exceptions hitting the handler while the messagebox is open
|
||||||
|
const auto lock = std::lock_guard(g_exception_handler_mutex);
|
||||||
|
|
||||||
|
exception_info exinfo{};
|
||||||
|
exinfo.pExceptionPointers = ex;
|
||||||
|
exinfo.ExceptionPointers = *ex;
|
||||||
|
exinfo.ContextRecord = *ex->ContextRecord;
|
||||||
|
exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
|
||||||
|
const auto time_now = std::chrono::system_clock::now();
|
||||||
|
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
time_now.time_since_epoch()).count()
|
||||||
|
- std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
g_time_start.time_since_epoch()).count();
|
||||||
|
exinfo.nLifetime = lifetime;
|
||||||
|
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||||
|
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
||||||
|
|
||||||
|
std::wstring stackTrace;
|
||||||
|
if (!g_clr)
|
||||||
|
{
|
||||||
|
stackTrace = L"(no CLR stack trace available)";
|
||||||
|
}
|
||||||
|
else if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
|
||||||
|
L"Dalamud.EntryPoint, Dalamud",
|
||||||
|
L"VehCallback",
|
||||||
|
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
|
||||||
|
nullptr, nullptr, &fn)))
|
||||||
|
{
|
||||||
|
stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stackTrace = static_cast<wchar_t*(*)()>(fn)();
|
||||||
|
// Don't free it, as the program's going to be quit anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size());
|
||||||
|
exinfo.dwTroubleshootingPackDataLength = static_cast<DWORD>(g_startInfo.TroubleshootingPackData.size());
|
||||||
|
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
|
||||||
|
if (const auto nb = static_cast<DWORD>(std::span(stackTrace).size_bytes()))
|
||||||
|
{
|
||||||
|
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, stackTrace.data(), nb, &written, nullptr) || nb != written)
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto nb = static_cast<DWORD>(std::span(g_startInfo.TroubleshootingPackData).size_bytes()))
|
||||||
|
{
|
||||||
|
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, g_startInfo.TroubleshootingPackData.data(), nb, &written, nullptr) || nb != written)
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllowSetForegroundWindow(GetProcessId(g_crashhandler_process));
|
||||||
|
|
||||||
|
HANDLE waitHandles[] = { g_crashhandler_process, g_crashhandler_event };
|
||||||
|
DWORD waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
|
||||||
|
|
||||||
|
switch (waitResult) {
|
||||||
|
case WAIT_OBJECT_0:
|
||||||
|
logging::E("DalamudCrashHandler.exe exited unexpectedly");
|
||||||
|
break;
|
||||||
|
case WAIT_OBJECT_0 + 1:
|
||||||
|
logging::I("Crashing thread was resumed");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logging::E("Unexpected WaitForMultipleObjects return code 0x{:x}", waitResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
|
{
|
||||||
|
return exception_handler(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
{
|
{
|
||||||
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
|
||||||
{
|
{
|
||||||
|
|
@ -143,50 +241,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
// block any other exceptions hitting the veh while the messagebox is open
|
return exception_handler(ex);
|
||||||
const auto lock = std::lock_guard(g_exception_handler_mutex);
|
|
||||||
|
|
||||||
exception_info exinfo{};
|
|
||||||
exinfo.pExceptionPointers = ex;
|
|
||||||
exinfo.ExceptionPointers = *ex;
|
|
||||||
exinfo.ContextRecord = *ex->ContextRecord;
|
|
||||||
exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
|
|
||||||
const auto time_now = std::chrono::system_clock::now();
|
|
||||||
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
|
|
||||||
time_now.time_since_epoch()).count()
|
|
||||||
- std::chrono::duration_cast<std::chrono::seconds>(
|
|
||||||
g_time_start.time_since_epoch()).count();
|
|
||||||
exinfo.nLifetime = lifetime;
|
|
||||||
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
|
|
||||||
|
|
||||||
std::wstring stackTrace;
|
|
||||||
if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
|
|
||||||
L"Dalamud.EntryPoint, Dalamud",
|
|
||||||
L"VehCallback",
|
|
||||||
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
|
|
||||||
nullptr, nullptr, &fn)))
|
|
||||||
{
|
|
||||||
stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stackTrace = static_cast<wchar_t*(*)()>(fn)();
|
|
||||||
// Don't free it, as the program's going to be quit anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size());
|
|
||||||
exinfo.dwTroubleshootingPackDataLength = static_cast<DWORD>(g_startInfo.TroubleshootingPackData.size());
|
|
||||||
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
|
||||||
|
|
||||||
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &stackTrace[0], static_cast<DWORD>(std::span(stackTrace).size_bytes()), &written, nullptr) || std::span(stackTrace).size_bytes() != written)
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
|
||||||
|
|
||||||
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &g_startInfo.TroubleshootingPackData[0], static_cast<DWORD>(std::span(g_startInfo.TroubleshootingPackData).size_bytes()), &written, nullptr) || std::span(g_startInfo.TroubleshootingPackData).size_bytes() != written)
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
|
||||||
|
|
||||||
SuspendThread(GetCurrentThread());
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
||||||
|
|
@ -194,8 +249,15 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
||||||
if (g_veh_handle)
|
if (g_veh_handle)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
g_veh_handle = AddVectoredExceptionHandler(1, exception_handler);
|
g_veh_handle = AddVectoredExceptionHandler(TRUE, vectored_exception_handler);
|
||||||
SetUnhandledExceptionFilter(nullptr);
|
|
||||||
|
g_HookSetUnhandledExceptionFilter.emplace("kernel32.dll!SetUnhandledExceptionFilter (lpTopLevelExceptionFilter)", "kernel32.dll", "SetUnhandledExceptionFilter", 0);
|
||||||
|
g_HookSetUnhandledExceptionFilter->set_detour([](LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) -> LPTOP_LEVEL_EXCEPTION_FILTER
|
||||||
|
{
|
||||||
|
logging::I("Overwriting UnhandledExceptionFilter from {} to {}", reinterpret_cast<ULONG_PTR>(lpTopLevelExceptionFilter), reinterpret_cast<ULONG_PTR>(structured_exception_handler));
|
||||||
|
return g_HookSetUnhandledExceptionFilter->call_original(structured_exception_handler);
|
||||||
|
});
|
||||||
|
SetUnhandledExceptionFilter(structured_exception_handler);
|
||||||
|
|
||||||
g_veh_do_full_dump = doFullDump;
|
g_veh_do_full_dump = doFullDump;
|
||||||
g_time_start = std::chrono::system_clock::now();
|
g_time_start = std::chrono::system_clock::now();
|
||||||
|
|
@ -308,6 +370,12 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(g_crashhandler_event = CreateEventW(NULL, FALSE, FALSE, NULL)))
|
||||||
|
{
|
||||||
|
logging::W("Failed to create crash synchronization event: CreateEventW error 0x{:x}", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
g_crashhandler_process = pi.hProcess;
|
g_crashhandler_process = pi.hProcess;
|
||||||
|
|
@ -321,6 +389,8 @@ bool veh::remove_handler()
|
||||||
if (g_veh_handle && RemoveVectoredExceptionHandler(g_veh_handle) != 0)
|
if (g_veh_handle && RemoveVectoredExceptionHandler(g_veh_handle) != 0)
|
||||||
{
|
{
|
||||||
g_veh_handle = nullptr;
|
g_veh_handle = nullptr;
|
||||||
|
g_HookSetUnhandledExceptionFilter.reset();
|
||||||
|
SetUnhandledExceptionFilter(nullptr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@
|
||||||
#include "DalamudStartInfo.h"
|
#include "DalamudStartInfo.h"
|
||||||
#include "hooks.h"
|
#include "hooks.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
#include "ntdll.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <iphlpapi.h>
|
|
||||||
#include <icmpapi.h>
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static std::span<T> assume_nonempty_span(std::span<T> t, const char* descr) {
|
static std::span<T> assume_nonempty_span(std::span<T> t, const char* descr) {
|
||||||
|
|
@ -513,50 +512,6 @@ void xivfixes::backup_userdata_save(bool bApply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void xivfixes::clr_failfast_hijack(bool bApply)
|
|
||||||
{
|
|
||||||
static const char* LogTag = "[xivfixes:clr_failfast_hijack]";
|
|
||||||
static std::optional<hooks::import_hook<decltype(RaiseFailFastException)>> s_HookClrFatalError;
|
|
||||||
static std::optional<hooks::import_hook<decltype(SetUnhandledExceptionFilter)>> s_HookSetUnhandledExceptionFilter;
|
|
||||||
|
|
||||||
if (bApply)
|
|
||||||
{
|
|
||||||
if (!g_startInfo.BootEnabledGameFixes.contains("clr_failfast_hijack")) {
|
|
||||||
logging::I("{} Turned off via environment variable.", LogTag);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_HookClrFatalError.emplace("kernel32.dll!RaiseFailFastException (import, backup_userdata_save)", "kernel32.dll", "RaiseFailFastException", 0);
|
|
||||||
s_HookSetUnhandledExceptionFilter.emplace("kernel32.dll!SetUnhandledExceptionFilter (lpTopLevelExceptionFilter)", "kernel32.dll", "SetUnhandledExceptionFilter", 0);
|
|
||||||
|
|
||||||
s_HookClrFatalError->set_detour([](PEXCEPTION_RECORD pExceptionRecord,
|
|
||||||
_In_opt_ PCONTEXT pContextRecord,
|
|
||||||
_In_ DWORD dwFlags)
|
|
||||||
{
|
|
||||||
MessageBoxW(nullptr, L"An error in a Dalamud plugin was detected and the game cannot continue.\n\nPlease take a screenshot of this error message and let us know about it.", L"Dalamud", MB_OK | MB_ICONERROR);
|
|
||||||
|
|
||||||
return s_HookClrFatalError->call_original(pExceptionRecord, pContextRecord, dwFlags);
|
|
||||||
});
|
|
||||||
|
|
||||||
s_HookSetUnhandledExceptionFilter->set_detour([](LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) -> LPTOP_LEVEL_EXCEPTION_FILTER
|
|
||||||
{
|
|
||||||
logging::I("{} SetUnhandledExceptionFilter", LogTag);
|
|
||||||
return nullptr;
|
|
||||||
});
|
|
||||||
|
|
||||||
logging::I("{} Enable", LogTag);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (s_HookClrFatalError) {
|
|
||||||
logging::I("{} Disable ClrFatalError", LogTag);
|
|
||||||
s_HookClrFatalError.reset();
|
|
||||||
s_HookSetUnhandledExceptionFilter.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void xivfixes::prevent_icmphandle_crashes(bool bApply) {
|
void xivfixes::prevent_icmphandle_crashes(bool bApply) {
|
||||||
static const char* LogTag = "[xivfixes:prevent_icmphandle_crashes]";
|
static const char* LogTag = "[xivfixes:prevent_icmphandle_crashes]";
|
||||||
|
|
||||||
|
|
@ -590,6 +545,109 @@ void xivfixes::prevent_icmphandle_crashes(bool bApply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void xivfixes::symbol_load_patches(bool bApply) {
|
||||||
|
static const char* LogTag = "[xivfixes:symbol_load_patches]";
|
||||||
|
|
||||||
|
static std::optional<hooks::import_hook<decltype(SymInitialize)>> s_hookSymInitialize;
|
||||||
|
static PVOID s_dllNotificationCookie = nullptr;
|
||||||
|
|
||||||
|
static const auto RemoveFullPathPdbInfo = [](const utils::loaded_module& mod) {
|
||||||
|
const auto ddva = mod.data_directory(IMAGE_DIRECTORY_ENTRY_DEBUG).VirtualAddress;
|
||||||
|
if (!ddva)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& ddir = mod.ref_as<IMAGE_DEBUG_DIRECTORY>(ddva);
|
||||||
|
if (ddir.Type == IMAGE_DEBUG_TYPE_CODEVIEW) {
|
||||||
|
// The Visual C++ debug information.
|
||||||
|
// Ghidra calls it "DotNetPdbInfo".
|
||||||
|
static constexpr DWORD DotNetPdbInfoSignatureValue = 0x53445352;
|
||||||
|
struct DotNetPdbInfo {
|
||||||
|
DWORD Signature; // RSDS
|
||||||
|
GUID Guid;
|
||||||
|
DWORD Age;
|
||||||
|
char PdbPath[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto& pdbref = mod.ref_as<DotNetPdbInfo>(ddir.AddressOfRawData);
|
||||||
|
if (pdbref.Signature == DotNetPdbInfoSignatureValue) {
|
||||||
|
const auto pathSpan = std::string_view(pdbref.PdbPath, strlen(pdbref.PdbPath));
|
||||||
|
const auto pathWide = unicode::convert<std::wstring>(pathSpan);
|
||||||
|
std::wstring windowsDirectory(GetWindowsDirectoryW(nullptr, 0) + 1, L'\0');
|
||||||
|
windowsDirectory.resize(
|
||||||
|
GetWindowsDirectoryW(windowsDirectory.data(), static_cast<UINT>(windowsDirectory.size())));
|
||||||
|
if (!PathIsRelativeW(pathWide.c_str()) && !PathIsSameRootW(windowsDirectory.c_str(), pathWide.c_str())) {
|
||||||
|
utils::memory_tenderizer pathOverwrite(&pdbref.PdbPath, pathSpan.size(), PAGE_READWRITE);
|
||||||
|
auto sep = std::find(pathSpan.rbegin(), pathSpan.rend(), '/');
|
||||||
|
if (sep == pathSpan.rend())
|
||||||
|
sep = std::find(pathSpan.rbegin(), pathSpan.rend(), '\\');
|
||||||
|
if (sep != pathSpan.rend()) {
|
||||||
|
logging::I(
|
||||||
|
"{} Stripping pdb path folder: {} to {}",
|
||||||
|
LogTag,
|
||||||
|
pathSpan,
|
||||||
|
&*sep + 1);
|
||||||
|
memmove(const_cast<char*>(pathSpan.data()), &*sep + 1, sep - pathSpan.rbegin() + 1);
|
||||||
|
} else {
|
||||||
|
logging::I("{} Leaving pdb path unchanged: {}", LogTag, pathSpan);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logging::I("{} Leaving pdb path unchanged: {}", LogTag, pathSpan);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logging::I("{} CODEVIEW struct signature mismatch: got {:08X} instead.", LogTag, pdbref.Signature);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logging::I("{} Debug directory: type {} is unsupported.", LogTag, ddir.Type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bApply) {
|
||||||
|
if (!g_startInfo.BootEnabledGameFixes.contains("symbol_load_patches")) {
|
||||||
|
logging::I("{} Turned off via environment variable.", LogTag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& mod : utils::loaded_module::all_modules())
|
||||||
|
RemoveFullPathPdbInfo(mod);
|
||||||
|
|
||||||
|
if (!s_dllNotificationCookie) {
|
||||||
|
const auto res = LdrRegisterDllNotification(
|
||||||
|
0,
|
||||||
|
[](ULONG notiReason, const LDR_DLL_NOTIFICATION_DATA* pData, void* /* context */) {
|
||||||
|
if (notiReason == LDR_DLL_NOTIFICATION_REASON_LOADED)
|
||||||
|
RemoveFullPathPdbInfo(pData->Loaded.DllBase);
|
||||||
|
},
|
||||||
|
nullptr,
|
||||||
|
&s_dllNotificationCookie);
|
||||||
|
|
||||||
|
if (res != STATUS_SUCCESS) {
|
||||||
|
logging::E("{} LdrRegisterDllNotification failure: 0x{:08X}", LogTag, res);
|
||||||
|
s_dllNotificationCookie = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_hookSymInitialize.emplace("dbghelp.dll!SymInitialize (import, symbol_load_patches)", "dbghelp.dll", "SymInitialize", 0);
|
||||||
|
s_hookSymInitialize->set_detour([](HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess) noexcept {
|
||||||
|
logging::I("{} Suppressed SymInitialize.", LogTag);
|
||||||
|
SetLastError(ERROR_NOT_SUPPORTED);
|
||||||
|
return FALSE;
|
||||||
|
});
|
||||||
|
|
||||||
|
logging::I("{} Enable", LogTag);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (s_hookSymInitialize) {
|
||||||
|
logging::I("{} Disable", LogTag);
|
||||||
|
s_hookSymInitialize.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_dllNotificationCookie) {
|
||||||
|
(void)LdrUnregisterDllNotification(s_dllNotificationCookie);
|
||||||
|
s_dllNotificationCookie = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void xivfixes::apply_all(bool bApply) {
|
void xivfixes::apply_all(bool bApply) {
|
||||||
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
|
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
|
||||||
{
|
{
|
||||||
|
|
@ -598,8 +656,8 @@ void xivfixes::apply_all(bool bApply) {
|
||||||
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
|
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
|
||||||
{ "redirect_openprocess", &redirect_openprocess },
|
{ "redirect_openprocess", &redirect_openprocess },
|
||||||
{ "backup_userdata_save", &backup_userdata_save },
|
{ "backup_userdata_save", &backup_userdata_save },
|
||||||
{ "clr_failfast_hijack", &clr_failfast_hijack },
|
{ "prevent_icmphandle_crashes", &prevent_icmphandle_crashes },
|
||||||
{ "prevent_icmphandle_crashes", &prevent_icmphandle_crashes }
|
{ "symbol_load_patches", &symbol_load_patches },
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ namespace xivfixes {
|
||||||
void disable_game_openprocess_access_check(bool bApply);
|
void disable_game_openprocess_access_check(bool bApply);
|
||||||
void redirect_openprocess(bool bApply);
|
void redirect_openprocess(bool bApply);
|
||||||
void backup_userdata_save(bool bApply);
|
void backup_userdata_save(bool bApply);
|
||||||
void clr_failfast_hijack(bool bApply);
|
|
||||||
void prevent_icmphandle_crashes(bool bApply);
|
void prevent_icmphandle_crashes(bool bApply);
|
||||||
|
void symbol_load_patches(bool bApply);
|
||||||
|
|
||||||
void apply_all(bool bApply);
|
void apply_all(bool bApply);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
27
Dalamud.Common/ClientLanguage.cs
Normal file
27
Dalamud.Common/ClientLanguage.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Dalamud.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum describing the language the game loads in.
|
||||||
|
/// </summary>
|
||||||
|
public enum ClientLanguage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicating a Japanese game client.
|
||||||
|
/// </summary>
|
||||||
|
Japanese,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicating an English game client.
|
||||||
|
/// </summary>
|
||||||
|
English,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicating a German game client.
|
||||||
|
/// </summary>
|
||||||
|
German,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicating a French game client.
|
||||||
|
/// </summary>
|
||||||
|
French,
|
||||||
|
}
|
||||||
13
Dalamud.Common/Dalamud.Common.csproj
Normal file
13
Dalamud.Common/Dalamud.Common.csproj
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
using System;
|
using Dalamud.Common.Game;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Dalamud.Game;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud;
|
namespace Dalamud.Common;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Struct containing information needed to initialize Dalamud.
|
/// Struct containing information needed to initialize Dalamud.
|
||||||
|
/// Modify DalamudStartInfo.h and DalamudStartInfo.cpp along with this record.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public record DalamudStartInfo : IServiceType
|
public record DalamudStartInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
||||||
|
|
@ -21,36 +19,9 @@ public record DalamudStartInfo : IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
/// Gets or sets the Dalamud load method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other">Object to copy values from.</param>
|
public LoadMethod LoadMethod { get; set; }
|
||||||
public DalamudStartInfo(DalamudStartInfo other)
|
|
||||||
{
|
|
||||||
this.WorkingDirectory = other.WorkingDirectory;
|
|
||||||
this.ConfigurationPath = other.ConfigurationPath;
|
|
||||||
this.LogPath = other.LogPath;
|
|
||||||
this.LogName = other.LogName;
|
|
||||||
this.PluginDirectory = other.PluginDirectory;
|
|
||||||
this.AssetDirectory = other.AssetDirectory;
|
|
||||||
this.Language = other.Language;
|
|
||||||
this.GameVersion = other.GameVersion;
|
|
||||||
this.DelayInitializeMs = other.DelayInitializeMs;
|
|
||||||
this.TroubleshootingPackData = other.TroubleshootingPackData;
|
|
||||||
this.NoLoadPlugins = other.NoLoadPlugins;
|
|
||||||
this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins;
|
|
||||||
this.BootLogPath = other.BootLogPath;
|
|
||||||
this.BootShowConsole = other.BootShowConsole;
|
|
||||||
this.BootDisableFallbackConsole = other.BootDisableFallbackConsole;
|
|
||||||
this.BootWaitMessageBox = other.BootWaitMessageBox;
|
|
||||||
this.BootWaitDebugger = other.BootWaitDebugger;
|
|
||||||
this.BootVehEnabled = other.BootVehEnabled;
|
|
||||||
this.BootVehFull = other.BootVehFull;
|
|
||||||
this.BootEnableEtw = other.BootEnableEtw;
|
|
||||||
this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
|
|
||||||
this.BootEnabledGameFixes = other.BootEnabledGameFixes;
|
|
||||||
this.BootUnhookDlls = other.BootUnhookDlls;
|
|
||||||
this.CrashHandlerShow = other.CrashHandlerShow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the working directory of the XIVLauncher installations.
|
/// Gets or sets the working directory of the XIVLauncher installations.
|
||||||
|
|
@ -96,7 +67,7 @@ public record DalamudStartInfo : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets troubleshooting information to attach when generating a tspack file.
|
/// Gets or sets troubleshooting information to attach when generating a tspack file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TroubleshootingPackData { get; set; }
|
public string? TroubleshootingPackData { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value that specifies how much to wait before a new Dalamud session.
|
/// Gets or sets a value that specifies how much to wait before a new Dalamud session.
|
||||||
|
|
@ -172,4 +143,9 @@ public record DalamudStartInfo : IServiceType
|
||||||
/// Gets or sets a value indicating whether to show crash handler console window.
|
/// Gets or sets a value indicating whether to show crash handler console window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CrashHandlerShow { get; set; }
|
public bool CrashHandlerShow { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to disable all kinds of global exception handlers.
|
||||||
|
/// </summary>
|
||||||
|
public bool NoExceptionHandlers { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Common.Game;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A GameVersion object contains give hierarchical numeric components: year, month,
|
/// A GameVersion object contains give hierarchical numeric components: year, month,
|
||||||
|
|
@ -168,14 +166,14 @@ public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersi
|
||||||
return Parse(ver);
|
return Parse(ver);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(GameVersion v1, GameVersion v2)
|
public static bool operator ==(GameVersion? v1, GameVersion? v2)
|
||||||
{
|
{
|
||||||
if (v1 is null)
|
if (v1 is null)
|
||||||
{
|
{
|
||||||
return v2 is null;
|
return v2 is null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return v1.Equals(v2);
|
return v2 is not null && v1.Equals(v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(GameVersion v1, GameVersion v2)
|
public static bool operator !=(GameVersion v1, GameVersion v2)
|
||||||
|
|
@ -290,7 +288,7 @@ public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersi
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
result = null;
|
result = null!;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +297,7 @@ public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersi
|
||||||
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
|
public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int CompareTo(object obj)
|
public int CompareTo(object? obj)
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -315,7 +313,7 @@ public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersi
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int CompareTo(GameVersion value)
|
public int CompareTo(GameVersion? value)
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -348,7 +346,7 @@ public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersi
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is not GameVersion value)
|
if (obj is not GameVersion value)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -357,7 +355,7 @@ public sealed class GameVersion : ICloneable, IComparable, IComparable<GameVersi
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Equals(GameVersion value)
|
public bool Equals(GameVersion? value)
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Common.Game;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
|
/// Converts a <see cref="GameVersion"/> to and from a string (e.g. <c>"2010.01.01.1234.5678"</c>).
|
||||||
17
Dalamud.Common/LoadMethod.cs
Normal file
17
Dalamud.Common/LoadMethod.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace Dalamud.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum describing the method Dalamud has been loaded.
|
||||||
|
/// </summary>
|
||||||
|
public enum LoadMethod
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Load Dalamud by rewriting the games entrypoint.
|
||||||
|
/// </summary>
|
||||||
|
Entrypoint,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load Dalamud via DLL-injection.
|
||||||
|
/// </summary>
|
||||||
|
DllInject,
|
||||||
|
}
|
||||||
|
|
@ -27,8 +27,8 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Lumina" Version="3.10.2" />
|
<PackageReference Include="Lumina" Version="3.16.0" />
|
||||||
<PackageReference Include="Lumina.Excel" Version="6.4.0" />
|
<PackageReference Include="Lumina.Excel" Version="6.5.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|
@ -50,4 +50,10 @@
|
||||||
<Private>false</Private>
|
<Private>false</Private>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Dalamud.CorePlugin.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
9
Dalamud.CorePlugin/Dalamud.CorePlugin.json
Normal file
9
Dalamud.CorePlugin/Dalamud.CorePlugin.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Author": "Dalamud Maintainers",
|
||||||
|
"Name": "CorePlugin",
|
||||||
|
"Punchline": "Testbed for developing Dalamud features.",
|
||||||
|
"Description": "Develop and debug internal Dalamud features using CorePlugin. You have full access to all types in Dalamud assembly.",
|
||||||
|
"InternalName": "CorePlugin",
|
||||||
|
"ApplicableVersion": "any",
|
||||||
|
"Tags": []
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,13 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.CorePlugin
|
namespace Dalamud.CorePlugin
|
||||||
{
|
{
|
||||||
|
|
@ -37,9 +39,6 @@ namespace Dalamud.CorePlugin
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Name => "Dalamud.CorePlugin";
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
@ -50,36 +49,41 @@ namespace Dalamud.CorePlugin
|
||||||
private readonly WindowSystem windowSystem = new("Dalamud.CorePlugin");
|
private readonly WindowSystem windowSystem = new("Dalamud.CorePlugin");
|
||||||
private Localization localization;
|
private Localization localization;
|
||||||
|
|
||||||
|
private IPluginLog pluginLog;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginImpl"/> class.
|
/// Initializes a new instance of the <see cref="PluginImpl"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginInterface">Dalamud plugin interface.</param>
|
/// <param name="pluginInterface">Dalamud plugin interface.</param>
|
||||||
/// <param name="log">Logging service.</param>
|
/// <param name="log">Logging service.</param>
|
||||||
public PluginImpl(DalamudPluginInterface pluginInterface, PluginLog log)
|
public PluginImpl(DalamudPluginInterface pluginInterface, IPluginLog log)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// this.InitLoc();
|
// this.InitLoc();
|
||||||
this.Interface = pluginInterface;
|
this.Interface = pluginInterface;
|
||||||
|
this.pluginLog = log;
|
||||||
|
|
||||||
this.windowSystem.AddWindow(new PluginWindow());
|
this.windowSystem.AddWindow(new PluginWindow());
|
||||||
|
|
||||||
this.Interface.UiBuilder.Draw += this.OnDraw;
|
this.Interface.UiBuilder.Draw += this.OnDraw;
|
||||||
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
||||||
|
this.Interface.UiBuilder.OpenMainUi += this.OnOpenMainUi;
|
||||||
|
this.Interface.UiBuilder.DefaultFontHandle.ImFontChanged += (fc, _) =>
|
||||||
|
{
|
||||||
|
Log.Information($"CorePlugin : DefaultFontHandle.ImFontChanged called {fc}");
|
||||||
|
};
|
||||||
|
|
||||||
Service<CommandManager>.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." });
|
Service<CommandManager>.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = "Access the plugin." });
|
||||||
|
|
||||||
log.Information("CorePlugin ctor!");
|
log.Information("CorePlugin ctor!");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginLog.Error(ex, "kaboom");
|
log.Error(ex, "kaboom");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Name => "Dalamud.CorePlugin";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin interface.
|
/// Gets the plugin interface.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -93,8 +97,6 @@ namespace Dalamud.CorePlugin
|
||||||
this.Interface.UiBuilder.Draw -= this.OnDraw;
|
this.Interface.UiBuilder.Draw -= this.OnDraw;
|
||||||
|
|
||||||
this.windowSystem.RemoveAllWindows();
|
this.windowSystem.RemoveAllWindows();
|
||||||
|
|
||||||
this.Interface.ExplicitDispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -127,13 +129,13 @@ namespace Dalamud.CorePlugin
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginLog.Error(ex, "Boom");
|
this.pluginLog.Error(ex, "Boom");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCommand(string command, string args)
|
private void OnCommand(string command, string args)
|
||||||
{
|
{
|
||||||
PluginLog.Information("Command called!");
|
this.pluginLog.Information("Command called!");
|
||||||
|
|
||||||
// this.window.IsOpen = true;
|
// this.window.IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +145,11 @@ namespace Dalamud.CorePlugin
|
||||||
// this.window.IsOpen = true;
|
// this.window.IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnOpenMainUi()
|
||||||
|
{
|
||||||
|
Log.Verbose("Opened main UI");
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ int wmain(int argc, wchar_t** argv)
|
||||||
// =========================================================================== //
|
// =========================================================================== //
|
||||||
|
|
||||||
void* entrypoint_vfn;
|
void* entrypoint_vfn;
|
||||||
int result = InitializeClrAndGetEntryPoint(
|
const auto result = InitializeClrAndGetEntryPoint(
|
||||||
GetModuleHandleW(nullptr),
|
GetModuleHandleW(nullptr),
|
||||||
false,
|
false,
|
||||||
runtimeconfig_path,
|
runtimeconfig_path,
|
||||||
|
|
@ -33,15 +33,15 @@ int wmain(int argc, wchar_t** argv)
|
||||||
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
|
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
|
||||||
&entrypoint_vfn);
|
&entrypoint_vfn);
|
||||||
|
|
||||||
if (result != 0)
|
if (FAILED(result))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
typedef void (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
|
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);
|
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
||||||
|
|
||||||
logging::I("Running Dalamud Injector...");
|
logging::I("Running Dalamud Injector...");
|
||||||
entrypoint_fn(argc, argv);
|
const auto ret = entrypoint_fn(argc, argv);
|
||||||
logging::I("Done!");
|
logging::I("Done!");
|
||||||
|
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,12 +81,6 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- This prevents us from having to include Dalamud itself as a dependency -->
|
<ProjectReference Include="..\Dalamud.Common\Dalamud.Common.csproj" />
|
||||||
<!-- If the files move just update the paths here -->
|
|
||||||
<Compile Include="..\Dalamud\ClientLanguage.cs" Link="Included\%(Filename)%(Extension)" />
|
|
||||||
<Compile Include="..\Dalamud\IServiceType.cs" Link="Included\%(Filename)%(Extension)" />
|
|
||||||
<Compile Include="..\Dalamud\DalamudStartInfo.cs" Link="Included\%(Filename)%(Extension)" />
|
|
||||||
<Compile Include="..\Dalamud\Game\GameVersion.cs" Link="Included\Game\%(Filename)%(Extension)" />
|
|
||||||
<Compile Include="..\Dalamud\Game\GameVersionConverter.cs" Link="Included\Game\%(Filename)%(Extension)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Common;
|
||||||
|
using Dalamud.Common.Game;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Reloaded.Memory.Buffers;
|
using Reloaded.Memory.Buffers;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -30,88 +31,100 @@ namespace Dalamud.Injector
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="argc">Count of arguments.</param>
|
/// <param name="argc">Count of arguments.</param>
|
||||||
/// <param name="argvPtr">char** string arguments.</param>
|
/// <param name="argvPtr">char** string arguments.</param>
|
||||||
public delegate void MainDelegate(int argc, IntPtr argvPtr);
|
/// <returns>Return value (HRESULT).</returns>
|
||||||
|
public delegate int MainDelegate(int argc, IntPtr argvPtr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the Dalamud injector.
|
/// Start the Dalamud injector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="argc">Count of arguments.</param>
|
/// <param name="argc">Count of arguments.</param>
|
||||||
/// <param name="argvPtr">byte** string arguments.</param>
|
/// <param name="argvPtr">byte** string arguments.</param>
|
||||||
public static void Main(int argc, IntPtr argvPtr)
|
/// <returns>Return value (HRESULT).</returns>
|
||||||
|
public static int Main(int argc, IntPtr argvPtr)
|
||||||
{
|
{
|
||||||
List<string> args = new(argc);
|
try
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
{
|
||||||
var argv = (IntPtr*)argvPtr;
|
List<string> args = new(argc);
|
||||||
for (var i = 0; i < argc; i++)
|
|
||||||
args.Add(Marshal.PtrToStringUni(argv[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Init(args);
|
unsafe
|
||||||
args.Remove("-v"); // Remove "verbose" flag
|
|
||||||
|
|
||||||
if (args.Count >= 2 && args[1].ToLowerInvariant() == "launch-test")
|
|
||||||
{
|
|
||||||
Environment.Exit(ProcessLaunchTestCommand(args));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DalamudStartInfo startInfo = null;
|
|
||||||
if (args.Count == 1)
|
|
||||||
{
|
|
||||||
// No command defaults to inject
|
|
||||||
args.Add("inject");
|
|
||||||
args.Add("--all");
|
|
||||||
|
|
||||||
#if !DEBUG
|
|
||||||
args.Add("--warn");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (int.TryParse(args[1], out var _))
|
|
||||||
{
|
|
||||||
// Assume that PID has been passed.
|
|
||||||
args.Insert(1, "inject");
|
|
||||||
|
|
||||||
// If originally second parameter exists, then assume that it's a base64 encoded start info.
|
|
||||||
// Dalamud.Injector.exe inject [pid] [base64]
|
|
||||||
if (args.Count == 4)
|
|
||||||
{
|
{
|
||||||
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(args[3])));
|
var argv = (IntPtr*)argvPtr;
|
||||||
args.RemoveAt(3);
|
for (var i = 0; i < argc; i++)
|
||||||
|
args.Add(Marshal.PtrToStringUni(argv[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Init(args);
|
||||||
|
args.Remove("-v"); // Remove "verbose" flag
|
||||||
|
|
||||||
|
if (args.Count >= 2 && args[1].ToLowerInvariant() == "launch-test")
|
||||||
|
{
|
||||||
|
return ProcessLaunchTestCommand(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
DalamudStartInfo startInfo = null;
|
||||||
|
if (args.Count == 1)
|
||||||
|
{
|
||||||
|
// No command defaults to inject
|
||||||
|
args.Add("inject");
|
||||||
|
args.Add("--all");
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
args.Add("--warn");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (int.TryParse(args[1], out var _))
|
||||||
|
{
|
||||||
|
// Assume that PID has been passed.
|
||||||
|
args.Insert(1, "inject");
|
||||||
|
|
||||||
|
// If originally second parameter exists, then assume that it's a base64 encoded start info.
|
||||||
|
// Dalamud.Injector.exe inject [pid] [base64]
|
||||||
|
if (args.Count == 4)
|
||||||
|
{
|
||||||
|
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(args[3])));
|
||||||
|
args.RemoveAt(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
||||||
|
// Remove already handled arguments
|
||||||
|
args.Remove("--console");
|
||||||
|
args.Remove("--msgbox1");
|
||||||
|
args.Remove("--msgbox2");
|
||||||
|
args.Remove("--msgbox3");
|
||||||
|
args.Remove("--etw");
|
||||||
|
args.Remove("--veh");
|
||||||
|
args.Remove("--veh-full");
|
||||||
|
args.Remove("--no-plugin");
|
||||||
|
args.Remove("--no-3rd-plugin");
|
||||||
|
args.Remove("--crash-handler-console");
|
||||||
|
args.Remove("--no-exception-handlers");
|
||||||
|
|
||||||
|
var mainCommand = args[1].ToLowerInvariant();
|
||||||
|
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
|
||||||
|
{
|
||||||
|
return ProcessInjectCommand(args, startInfo);
|
||||||
|
}
|
||||||
|
else if (mainCommand.Length > 0 && mainCommand.Length <= 6 &&
|
||||||
|
"launch"[..mainCommand.Length] == mainCommand)
|
||||||
|
{
|
||||||
|
return ProcessLaunchCommand(args, startInfo);
|
||||||
|
}
|
||||||
|
else if (mainCommand.Length > 0 && mainCommand.Length <= 4 &&
|
||||||
|
"help"[..mainCommand.Length] == mainCommand)
|
||||||
|
{
|
||||||
|
return ProcessHelpCommand(args, args.Count >= 3 ? args[2] : null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new CommandLineException($"\"{mainCommand}\" is not a valid command.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
|
||||||
// Remove already handled arguments
|
|
||||||
args.Remove("--console");
|
|
||||||
args.Remove("--msgbox1");
|
|
||||||
args.Remove("--msgbox2");
|
|
||||||
args.Remove("--msgbox3");
|
|
||||||
args.Remove("--etw");
|
|
||||||
args.Remove("--veh");
|
|
||||||
args.Remove("--veh-full");
|
|
||||||
args.Remove("--no-plugin");
|
|
||||||
args.Remove("--no-3rd-plugin");
|
|
||||||
args.Remove("--crash-handler-console");
|
|
||||||
|
|
||||||
var mainCommand = args[1].ToLowerInvariant();
|
|
||||||
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
|
|
||||||
{
|
{
|
||||||
Environment.Exit(ProcessInjectCommand(args, startInfo));
|
Log.Error(e, "Operation failed.");
|
||||||
}
|
return e.HResult;
|
||||||
else if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "launch"[..mainCommand.Length] == mainCommand)
|
|
||||||
{
|
|
||||||
Environment.Exit(ProcessLaunchCommand(args, startInfo));
|
|
||||||
}
|
|
||||||
else if (mainCommand.Length > 0 && mainCommand.Length <= 4 && "help"[..mainCommand.Length] == mainCommand)
|
|
||||||
{
|
|
||||||
Environment.Exit(ProcessHelpCommand(args, args.Count >= 3 ? args[2] : null));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new CommandLineException($"\"{mainCommand}\" is not a valid command.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,6 +200,7 @@ namespace Dalamud.Injector
|
||||||
CullLogFile(logPath, 1 * 1024 * 1024);
|
CullLogFile(logPath, 1 * 1024 * 1024);
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.WriteTo.Console(standardErrorFromLevel: LogEventLevel.Debug)
|
||||||
.WriteTo.File(logPath, fileSizeLimitBytes: null)
|
.WriteTo.File(logPath, fileSizeLimitBytes: null)
|
||||||
.MinimumLevel.ControlledBy(levelSwitch)
|
.MinimumLevel.ControlledBy(levelSwitch)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
@ -375,12 +389,22 @@ namespace Dalamud.Injector
|
||||||
#else
|
#else
|
||||||
startInfo.LogPath ??= xivlauncherDir;
|
startInfo.LogPath ??= xivlauncherDir;
|
||||||
#endif
|
#endif
|
||||||
|
startInfo.LogName ??= string.Empty;
|
||||||
|
|
||||||
// Set boot defaults
|
// Set boot defaults
|
||||||
startInfo.BootShowConsole = args.Contains("--console");
|
startInfo.BootShowConsole = args.Contains("--console");
|
||||||
startInfo.BootEnableEtw = args.Contains("--etw");
|
startInfo.BootEnableEtw = args.Contains("--etw");
|
||||||
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
|
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
|
||||||
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "prevent_icmphandle_crashes" };
|
startInfo.BootEnabledGameFixes = new()
|
||||||
|
{
|
||||||
|
// See: xivfixes.h, xivfixes.cpp
|
||||||
|
"prevent_devicechange_crashes",
|
||||||
|
"disable_game_openprocess_access_check",
|
||||||
|
"redirect_openprocess",
|
||||||
|
"backup_userdata_save",
|
||||||
|
"prevent_icmphandle_crashes",
|
||||||
|
"symbol_load_patches",
|
||||||
|
};
|
||||||
startInfo.BootDotnetOpenProcessHookMode = 0;
|
startInfo.BootDotnetOpenProcessHookMode = 0;
|
||||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
||||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0;
|
startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0;
|
||||||
|
|
@ -392,6 +416,7 @@ namespace Dalamud.Injector
|
||||||
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin");
|
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin");
|
||||||
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
|
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
|
||||||
startInfo.CrashHandlerShow = args.Contains("--crash-handler-console");
|
startInfo.CrashHandlerShow = args.Contains("--crash-handler-console");
|
||||||
|
startInfo.NoExceptionHandlers = args.Contains("--no-exception-handlers");
|
||||||
|
|
||||||
return startInfo;
|
return startInfo;
|
||||||
}
|
}
|
||||||
|
|
@ -433,7 +458,7 @@ namespace Dalamud.Injector
|
||||||
Console.WriteLine("Verbose logging:\t[-v]");
|
Console.WriteLine("Verbose logging:\t[-v]");
|
||||||
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
|
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
|
||||||
Console.WriteLine("Enable ETW:\t[--etw]");
|
Console.WriteLine("Enable ETW:\t[--etw]");
|
||||||
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full]");
|
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--no-exception-handlers]");
|
||||||
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
|
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
|
||||||
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
|
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
|
||||||
Console.WriteLine("Logging:\t[--logname=<logfile suffix>] [--logpath=<log base directory>]");
|
Console.WriteLine("Logging:\t[--logname=<logfile suffix>] [--logpath=<log base directory>]");
|
||||||
|
|
@ -677,11 +702,11 @@ namespace Dalamud.Injector
|
||||||
mode = mode == null ? "entrypoint" : mode.ToLowerInvariant();
|
mode = mode == null ? "entrypoint" : mode.ToLowerInvariant();
|
||||||
if (mode.Length > 0 && mode.Length <= 10 && "entrypoint"[0..mode.Length] == mode)
|
if (mode.Length > 0 && mode.Length <= 10 && "entrypoint"[0..mode.Length] == mode)
|
||||||
{
|
{
|
||||||
mode = "entrypoint";
|
dalamudStartInfo.LoadMethod = LoadMethod.Entrypoint;
|
||||||
}
|
}
|
||||||
else if (mode.Length > 0 && mode.Length <= 6 && "inject"[0..mode.Length] == mode)
|
else if (mode.Length > 0 && mode.Length <= 6 && "inject"[0..mode.Length] == mode)
|
||||||
{
|
{
|
||||||
mode = "inject";
|
dalamudStartInfo.LoadMethod = LoadMethod.DllInject;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -793,16 +818,12 @@ namespace Dalamud.Injector
|
||||||
noFixAcl,
|
noFixAcl,
|
||||||
p =>
|
p =>
|
||||||
{
|
{
|
||||||
if (!withoutDalamud && mode == "entrypoint")
|
if (!withoutDalamud && dalamudStartInfo.LoadMethod == LoadMethod.Entrypoint)
|
||||||
{
|
{
|
||||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||||
if (RewriteRemoteEntryPointW(p.Handle, gamePath, JsonConvert.SerializeObject(startInfo)) != 0)
|
Marshal.ThrowExceptionForHR(
|
||||||
{
|
RewriteRemoteEntryPointW(p.Handle, gamePath, JsonConvert.SerializeObject(startInfo)));
|
||||||
Log.Error("[HOOKS] RewriteRemoteEntryPointW failed");
|
|
||||||
throw new Exception("RewriteRemoteEntryPointW failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Verbose("RewriteRemoteEntryPointW called!");
|
Log.Verbose("RewriteRemoteEntryPointW called!");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -810,7 +831,7 @@ namespace Dalamud.Injector
|
||||||
|
|
||||||
Log.Verbose("Game process started with PID {0}", process.Id);
|
Log.Verbose("Game process started with PID {0}", process.Id);
|
||||||
|
|
||||||
if (!withoutDalamud && mode == "inject")
|
if (!withoutDalamud && dalamudStartInfo.LoadMethod == LoadMethod.DllInject)
|
||||||
{
|
{
|
||||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||||
|
|
@ -888,7 +909,7 @@ namespace Dalamud.Injector
|
||||||
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
|
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
|
||||||
var gameVer = GameVersion.Parse(gameVerStr);
|
var gameVer = GameVersion.Parse(gameVerStr);
|
||||||
|
|
||||||
return new DalamudStartInfo(startInfo)
|
return startInfo with
|
||||||
{
|
{
|
||||||
GameVersion = gameVer,
|
GameVersion = gameVer,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface;
|
|
||||||
|
|
||||||
internal static class ArrayExtensions
|
|
||||||
{
|
|
||||||
/// <summary> Iterate over enumerables with additional index. </summary>
|
|
||||||
public static IEnumerable<(T Value, int Index)> WithIndex<T>(this IEnumerable<T> list)
|
|
||||||
=> list.Select((x, i) => (x, i));
|
|
||||||
|
|
||||||
/// <summary> Remove an added index from an indexed enumerable. </summary>
|
|
||||||
public static IEnumerable<T> WithoutIndex<T>(this IEnumerable<(T Value, int Index)> list)
|
|
||||||
=> list.Select(x => x.Value);
|
|
||||||
|
|
||||||
/// <summary> Remove the value and only keep the index from an indexed enumerable. </summary>
|
|
||||||
public static IEnumerable<int> WithoutValue<T>(this IEnumerable<(T Value, int Index)> list)
|
|
||||||
=> list.Select(x => x.Index);
|
|
||||||
|
|
||||||
|
|
||||||
// Find the index of the first object fulfilling predicate's criteria in the given list.
|
|
||||||
// Returns -1 if no such object is found.
|
|
||||||
public static int IndexOf<T>(this IEnumerable<T> array, Predicate<T> predicate)
|
|
||||||
{
|
|
||||||
var i = 0;
|
|
||||||
foreach (var obj in array)
|
|
||||||
{
|
|
||||||
if (predicate(obj))
|
|
||||||
return i;
|
|
||||||
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the index of the first occurrence of needle in the given list.
|
|
||||||
// Returns -1 if needle is not contained in the list.
|
|
||||||
public static int IndexOf<T>(this IEnumerable<T> array, T needle) where T : notnull
|
|
||||||
{
|
|
||||||
var i = 0;
|
|
||||||
foreach (var obj in array)
|
|
||||||
{
|
|
||||||
if (needle.Equals(obj))
|
|
||||||
return i;
|
|
||||||
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the first object fulfilling predicate's criteria in the given list, if one exists.
|
|
||||||
// Returns true if an object is found, false otherwise.
|
|
||||||
public static bool FindFirst<T>(this IEnumerable<T> array, Predicate<T> predicate, [NotNullWhen(true)] out T? result)
|
|
||||||
{
|
|
||||||
foreach (var obj in array)
|
|
||||||
{
|
|
||||||
if (predicate(obj))
|
|
||||||
{
|
|
||||||
result = obj!;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the first occurrence of needle in the given list and return the value contained in the list in result.
|
|
||||||
// Returns true if an object is found, false otherwise.
|
|
||||||
public static bool FindFirst<T>(this IEnumerable<T> array, T needle, [NotNullWhen(true)] out T? result) where T : notnull
|
|
||||||
{
|
|
||||||
foreach (var obj in array)
|
|
||||||
{
|
|
||||||
if (obj.Equals(needle))
|
|
||||||
{
|
|
||||||
result = obj;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net7.0-windows</TargetFramework>
|
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
|
||||||
<Platforms>x64;AnyCPU</Platforms>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
<RootNamespace>Dalamud.Interface</RootNamespace>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
using Dalamud.Interface.Raii;
|
|
||||||
using ImGuiNET;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface;
|
|
||||||
|
|
||||||
public static class ImGuiTable
|
|
||||||
{
|
|
||||||
// Draw a simple table with the given data using the drawRow action.
|
|
||||||
// Headers and thus columns and column count are defined by columnTitles.
|
|
||||||
public static void DrawTable<T>(string label, IEnumerable<T> data, Action<T> drawRow, ImGuiTableFlags flags = ImGuiTableFlags.None,
|
|
||||||
params string[] columnTitles)
|
|
||||||
{
|
|
||||||
if (columnTitles.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
using var table = ImRaii.Table(label, columnTitles.Length, flags);
|
|
||||||
if (!table)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var title in columnTitles)
|
|
||||||
{
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TableHeader(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var datum in data)
|
|
||||||
{
|
|
||||||
ImGui.TableNextRow();
|
|
||||||
drawRow(datum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a simple table with the given data using the drawRow action inside a collapsing header.
|
|
||||||
// Headers and thus columns and column count are defined by columnTitles.
|
|
||||||
public static void DrawTabbedTable<T>(string label, IEnumerable<T> data, Action<T> drawRow, ImGuiTableFlags flags = ImGuiTableFlags.None,
|
|
||||||
params string[] columnTitles)
|
|
||||||
{
|
|
||||||
if (ImGui.CollapsingHeader(label))
|
|
||||||
DrawTable($"{label}##Table", data, drawRow, flags, columnTitles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Dalamud.Interface;
|
|
||||||
|
|
||||||
public static class InterfaceHelpers
|
|
||||||
{
|
|
||||||
public static float GlobalScale = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Dalamud.Game;
|
using Dalamud.Common.Game;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Dalamud.Test.Game
|
namespace Dalamud.Test.Game
|
||||||
|
|
|
||||||
126
Dalamud.sln
126
Dalamud.sln
|
|
@ -6,8 +6,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
targets\Dalamud.Plugin.targets = targets\Dalamud.Plugin.targets
|
|
||||||
targets\Dalamud.Plugin.Bootstrap.targets = targets\Dalamud.Plugin.Bootstrap.targets
|
targets\Dalamud.Plugin.Bootstrap.targets = targets\Dalamud.Plugin.Bootstrap.targets
|
||||||
|
targets\Dalamud.Plugin.targets = targets\Dalamud.Plugin.targets
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "build", "build\build.csproj", "{94E5B016-02B1-459B-97D9-E783F28764B2}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "build", "build\build.csproj", "{94E5B016-02B1-459B-97D9-E783F28764B2}"
|
||||||
|
|
@ -38,184 +38,70 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.InteropS
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "DalamudCrashHandler\DalamudCrashHandler.vcxproj", "{317A264C-920B-44A1-8A34-F3A6827B0705}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "DalamudCrashHandler\DalamudCrashHandler.vcxproj", "{317A264C-920B-44A1-8A34-F3A6827B0705}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Interface", "Dalamud.Interface\Dalamud.Interface.csproj", "{757C997D-AA58-4241-8299-243C56514917}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Common", "Dalamud.Common\Dalamud.Common.csproj", "{F21B13D2-D7D0-4456-B70F-3F8D695064E2}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Debug|x64 = Debug|x64
|
|
||||||
Debug|x86 = Debug|x86
|
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
Release|x64 = Release|x64
|
|
||||||
Release|x86 = Release|x86
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64
|
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64
|
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.Build.0 = Release|x64
|
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x64.Build.0 = Release|x64
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.Build.0 = Debug|x64
|
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x86.ActiveCfg = Debug|x64
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x86.Build.0 = Debug|x64
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.ActiveCfg = Release|x64
|
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.Build.0 = Release|x64
|
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x64.Build.0 = Release|x64
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x86.ActiveCfg = Release|x64
|
|
||||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x86.Build.0 = Release|x64
|
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{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}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{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
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.Build.0 = Release|x64
|
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|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}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x86.ActiveCfg = Debug|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x86.Build.0 = Debug|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
|
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
|
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.Build.0 = Release|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x86.ActiveCfg = Release|x64
|
|
||||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x86.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}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
|
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
|
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.Build.0 = Release|x64
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
|
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.ActiveCfg = Debug|x64
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.Build.0 = Debug|x64
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
|
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
|
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.Build.0 = Release|x64
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.ActiveCfg = Release|x64
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.Build.0 = Release|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
|
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.ActiveCfg = Debug|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.Build.0 = Debug|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
|
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
|
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.Build.0 = Release|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.ActiveCfg = Release|x64
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.Build.0 = Release|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
|
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x86.ActiveCfg = Debug|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x86.Build.0 = Debug|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
|
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
|
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.Build.0 = Release|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x86.ActiveCfg = Release|x64
|
|
||||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x86.Build.0 = Release|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64
|
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x86.ActiveCfg = Debug|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x86.Build.0 = Debug|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64
|
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.Build.0 = Release|x64
|
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.Build.0 = Release|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x86.ActiveCfg = Release|x64
|
|
||||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x86.Build.0 = Release|x64
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64
|
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|x64
|
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|x64
|
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64
|
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64
|
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64
|
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64
|
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|x64
|
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|x64
|
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{757C997D-AA58-4241-8299-243C56514917}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
namespace Dalamud;
|
namespace Dalamud;
|
||||||
|
|
||||||
|
// TODO(v10): Delete this, and use Dalamud.Common.ClientLanguage instead for everything.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum describing the language the game loads in.
|
/// Enum describing the language the game loads in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
|
using Dalamud.Interface.FontIdentifier;
|
||||||
|
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||||
using Dalamud.Interface.Style;
|
using Dalamud.Interface.Style;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Internal.Profiles;
|
using Dalamud.Plugin.Internal.Profiles;
|
||||||
|
using Dalamud.Storage;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -18,7 +22,11 @@ namespace Dalamud.Configuration.Internal;
|
||||||
/// Class containing Dalamud settings.
|
/// Class containing Dalamud settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
internal sealed class DalamudConfiguration : IServiceType
|
[ServiceManager.ProvidedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[InherentDependency<ReliableFileStorage>] // We must still have this when unloading
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerSettings SerializerSettings = new()
|
private static readonly JsonSerializerSettings SerializerSettings = new()
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +36,7 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
};
|
};
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private string configPath;
|
private string? configPath;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private bool isSaveQueued;
|
private bool isSaveQueued;
|
||||||
|
|
@ -42,12 +50,12 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that occurs when dalamud configuration is saved.
|
/// Event that occurs when dalamud configuration is saved.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
|
public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of muted works.
|
/// Gets or sets a list of muted works.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> BadWords { get; set; }
|
public List<string>? BadWords { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
||||||
|
|
@ -62,12 +70,12 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the language code to load Dalamud localization with.
|
/// Gets or sets the language code to load Dalamud localization with.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LanguageOverride { get; set; } = null;
|
public string? LanguageOverride { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the last loaded Dalamud version.
|
/// Gets or sets the last loaded Dalamud version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LastVersion { get; set; } = null;
|
public string? LastVersion { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating the last seen FTUE version.
|
/// Gets or sets a value indicating the last seen FTUE version.
|
||||||
|
|
@ -78,7 +86,7 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the last loaded Dalamud version.
|
/// Gets or sets the last loaded Dalamud version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LastChangelogMajorMinor { get; set; } = null;
|
public string? LastChangelogMajorMinor { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the chat type used by default for plugin messages.
|
/// Gets or sets the chat type used by default for plugin messages.
|
||||||
|
|
@ -100,6 +108,11 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not a disclaimer regarding third-party repos has been dismissed.
|
||||||
|
/// </summary>
|
||||||
|
public bool? ThirdRepoSpeedbumpDismissed { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of hidden plugins.
|
/// Gets or sets a list of hidden plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -133,15 +146,18 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to use AXIS fonts from the game.
|
/// Gets or sets a value indicating whether to use AXIS fonts from the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseAxisFontsFromGame { get; set; } = false;
|
[Obsolete($"See {nameof(DefaultFontSpec)}")]
|
||||||
|
public bool UseAxisFontsFromGame { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness.
|
/// Gets or sets the default font spec.
|
||||||
///
|
|
||||||
/// Before gamma is applied...
|
|
||||||
/// * ...TTF fonts loaded with stb or FreeType are in linear space.
|
|
||||||
/// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
public IFontSpec? DefaultFontSpec { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the gamma value to apply for Dalamud fonts. Do not use.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("It happens that nobody touched this setting", true)]
|
||||||
public float FontGammaLevel { get; set; } = 1.4f;
|
public float FontGammaLevel { get; set; } = 1.4f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -199,6 +215,11 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LogOpenAtStartup { get; set; }
|
public bool LogOpenAtStartup { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of lines to keep for the Dalamud Console window.
|
||||||
|
/// </summary>
|
||||||
|
public int LogLinesLimit { get; set; } = 10000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not the dev bar should open at startup.
|
/// Gets or sets a value indicating whether or not the dev bar should open at startup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -218,8 +239,16 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects.
|
/// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects.
|
||||||
/// This setting is effected by the in-game "System Sounds" option and volume.
|
/// This setting is effected by the in-game "System Sounds" option and volume.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "ABI")]
|
||||||
public bool EnablePluginUISoundEffects { get; set; }
|
public bool EnablePluginUISoundEffects { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not an additional button allowing pinning and clickthrough options should be shown
|
||||||
|
/// on plugin title bars when using the Window System.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("EnablePluginUiAdditionalOptionsExperimental")]
|
||||||
|
public bool EnablePluginUiAdditionalOptions { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether viewports should always be disabled.
|
/// Gets or sets a value indicating whether viewports should always be disabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -248,7 +277,7 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DalamudBetaKind { get; set; }
|
public string? DalamudBetaKind { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
|
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
|
||||||
|
|
@ -414,26 +443,45 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double UiBuilderHitch { get; set; } = 100;
|
public double UiBuilderHitch { get; set; } = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the page of the plugin installer that is shown by default when opened.
|
||||||
|
/// </summary>
|
||||||
|
public PluginInstallerWindow.PluginInstallerOpenKind PluginInstallerOpen { get; set; } = PluginInstallerWindow.PluginInstallerOpenKind.AllPlugins;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load a configuration from the provided path.
|
/// Load a configuration from the provided path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path to load the configuration file from.</param>
|
/// <param name="path">Path to read from.</param>
|
||||||
|
/// <param name="fs">File storage.</param>
|
||||||
/// <returns>The deserialized configuration file.</returns>
|
/// <returns>The deserialized configuration file.</returns>
|
||||||
public static DalamudConfiguration Load(string path)
|
public static DalamudConfiguration Load(string path, ReliableFileStorage fs)
|
||||||
{
|
{
|
||||||
DalamudConfiguration deserialized = null;
|
DalamudConfiguration deserialized = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
deserialized = JsonConvert.DeserializeObject<DalamudConfiguration>(File.ReadAllText(path), SerializerSettings);
|
fs.ReadAllText(path, text =>
|
||||||
|
{
|
||||||
|
deserialized =
|
||||||
|
JsonConvert.DeserializeObject<DalamudConfiguration>(text, SerializerSettings);
|
||||||
|
|
||||||
|
// If this reads as null, the file was empty, that's no good
|
||||||
|
if (deserialized == null)
|
||||||
|
throw new Exception("Read config was null.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path);
|
// ignored
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not load DalamudConfiguration at {Path}, creating new", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialized ??= new DalamudConfiguration();
|
deserialized ??= new DalamudConfiguration();
|
||||||
deserialized.configPath = path;
|
deserialized.configPath = path;
|
||||||
|
|
||||||
return deserialized;
|
return deserialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -452,6 +500,13 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
{
|
{
|
||||||
this.Save();
|
this.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
// Make sure that we save, if a save is queued while we are shutting down
|
||||||
|
this.Update();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save the file, if needed. Only needs to be done once a frame.
|
/// Save the file, if needed. Only needs to be done once a frame.
|
||||||
|
|
@ -470,8 +525,11 @@ internal sealed class DalamudConfiguration : IServiceType
|
||||||
private void Save()
|
private void Save()
|
||||||
{
|
{
|
||||||
ThreadSafety.AssertMainThread();
|
ThreadSafety.AssertMainThread();
|
||||||
|
if (this.configPath is null)
|
||||||
|
throw new InvalidOperationException("configPath is not set.");
|
||||||
|
|
||||||
Util.WriteAllTextSafe(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
Service<ReliableFileStorage>.Get().WriteAllText(
|
||||||
|
this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
||||||
this.DalamudConfigurationSaved?.Invoke(this);
|
this.DalamudConfigurationSaved?.Invoke(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Configuration.Internal;
|
namespace Dalamud.Configuration.Internal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -14,4 +16,9 @@ internal sealed class DevPluginSettings
|
||||||
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutomaticReloading { get; set; } = false;
|
public bool AutomaticReloading { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an ID uniquely identifying this specific instance of a devPlugin.
|
||||||
|
/// </summary>
|
||||||
|
public Guid WorkingPluginId { get; set; } = Guid.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Dalamud.Utility;
|
using Dalamud.Storage;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Configuration;
|
namespace Dalamud.Configuration;
|
||||||
|
|
@ -31,24 +31,39 @@ public sealed class PluginConfigurations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config">Plugin configuration.</param>
|
/// <param name="config">Plugin configuration.</param>
|
||||||
/// <param name="pluginName">Plugin name.</param>
|
/// <param name="pluginName">Plugin name.</param>
|
||||||
public void Save(IPluginConfiguration config, string pluginName)
|
/// <param name="workingPluginId">WorkingPluginId of the plugin.</param>
|
||||||
|
public void Save(IPluginConfiguration config, string pluginName, Guid workingPluginId)
|
||||||
{
|
{
|
||||||
Util.WriteAllTextSafe(this.GetConfigFile(pluginName).FullName, SerializeConfig(config));
|
Service<ReliableFileStorage>.Get()
|
||||||
|
.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config), workingPluginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load plugin configuration.
|
/// Load plugin configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginName">Plugin name.</param>
|
/// <param name="pluginName">Plugin name.</param>
|
||||||
|
/// <param name="workingPluginId">WorkingPluginID of the plugin.</param>
|
||||||
/// <returns>Plugin configuration.</returns>
|
/// <returns>Plugin configuration.</returns>
|
||||||
public IPluginConfiguration? Load(string pluginName)
|
public IPluginConfiguration? Load(string pluginName, Guid workingPluginId)
|
||||||
{
|
{
|
||||||
var path = this.GetConfigFile(pluginName);
|
var path = this.GetConfigFile(pluginName);
|
||||||
|
|
||||||
if (!path.Exists)
|
IPluginConfiguration? config = null;
|
||||||
return null;
|
try
|
||||||
|
{
|
||||||
|
Service<ReliableFileStorage>.Get().ReadAllText(path.FullName, text =>
|
||||||
|
{
|
||||||
|
config = DeserializeConfig(text);
|
||||||
|
if (config == null)
|
||||||
|
throw new Exception("Read config was null.");
|
||||||
|
}, workingPluginId);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
return DeserializeConfig(File.ReadAllText(path.FullName));
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -7,12 +6,13 @@ using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Common;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Gui.Internal;
|
|
||||||
using Dalamud.Interface.Internal;
|
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Storage;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using Dalamud.Utility.Timing;
|
||||||
using PInvoke;
|
using PInvoke;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -28,6 +28,7 @@ namespace Dalamud;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main Dalamud class containing all subsystems.
|
/// The main Dalamud class containing all subsystems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[ServiceManager.ProvidedService]
|
||||||
internal sealed class Dalamud : IServiceType
|
internal sealed class Dalamud : IServiceType
|
||||||
{
|
{
|
||||||
#region Internals
|
#region Internals
|
||||||
|
|
@ -40,26 +41,48 @@ internal sealed class Dalamud : IServiceType
|
||||||
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info">DalamudStartInfo instance.</param>
|
/// <param name="info">DalamudStartInfo instance.</param>
|
||||||
|
/// <param name="fs">ReliableFileStorage instance.</param>
|
||||||
/// <param name="configuration">The Dalamud configuration.</param>
|
/// <param name="configuration">The Dalamud configuration.</param>
|
||||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||||
public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
public Dalamud(DalamudStartInfo info, ReliableFileStorage fs, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
||||||
{
|
{
|
||||||
|
this.StartInfo = info;
|
||||||
|
|
||||||
this.unloadSignal = new ManualResetEvent(false);
|
this.unloadSignal = new ManualResetEvent(false);
|
||||||
this.unloadSignal.Reset();
|
this.unloadSignal.Reset();
|
||||||
|
|
||||||
|
// Directory resolved signatures(CS, our own) will be cached in
|
||||||
|
var cacheDir = new DirectoryInfo(Path.Combine(this.StartInfo.WorkingDirectory!, "cachedSigs"));
|
||||||
|
if (!cacheDir.Exists)
|
||||||
|
cacheDir.Create();
|
||||||
|
|
||||||
|
// Set up the SigScanner for our target module
|
||||||
|
TargetSigScanner scanner;
|
||||||
|
using (Timings.Start("SigScanner Init"))
|
||||||
|
{
|
||||||
|
scanner = new TargetSigScanner(
|
||||||
|
true, new FileInfo(Path.Combine(cacheDir.FullName, $"{this.StartInfo.GameVersion}.json")));
|
||||||
|
}
|
||||||
|
|
||||||
ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
|
ServiceManager.InitializeProvidedServices(this, fs, configuration, scanner);
|
||||||
|
|
||||||
|
// Set up FFXIVClientStructs
|
||||||
|
this.SetupClientStructsResolver(cacheDir);
|
||||||
|
|
||||||
if (!configuration.IsResumeGameAfterPluginLoad)
|
if (!configuration.IsResumeGameAfterPluginLoad)
|
||||||
{
|
{
|
||||||
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
NativeFunctions.SetEvent(mainThreadContinueEvent);
|
||||||
try
|
ServiceManager.InitializeEarlyLoadableServices()
|
||||||
{
|
.ContinueWith(t =>
|
||||||
_ = ServiceManager.InitializeEarlyLoadableServices();
|
{
|
||||||
}
|
if (t.IsCompletedSuccessfully)
|
||||||
catch (Exception e)
|
return;
|
||||||
{
|
|
||||||
Log.Error(e, "Service initialization failure");
|
Log.Error(t.Exception!, "Service initialization failure");
|
||||||
}
|
Util.Fatal(
|
||||||
|
"Dalamud failed to load all necessary services.\n\nThe game will continue, but you may not be able to use plugins.",
|
||||||
|
"Dalamud", false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -93,13 +116,36 @@ internal sealed class Dalamud : IServiceType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.DefaultExceptionFilter = NativeFunctions.SetUnhandledExceptionFilter(nint.Zero);
|
||||||
|
NativeFunctions.SetUnhandledExceptionFilter(this.DefaultExceptionFilter);
|
||||||
|
Log.Debug($"SE default exception filter at {this.DefaultExceptionFilter.ToInt64():X}");
|
||||||
|
|
||||||
|
var debugSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??";
|
||||||
|
this.DebugExceptionFilter = Service<TargetSigScanner>.Get().ScanText(debugSig);
|
||||||
|
Log.Debug($"SE debug exception filter at {this.DebugExceptionFilter.ToInt64():X}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the start information for this Dalamud instance.
|
||||||
|
/// </summary>
|
||||||
|
internal DalamudStartInfo StartInfo { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets location of stored assets.
|
/// Gets location of stored assets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory!);
|
internal DirectoryInfo AssetDirectory => new(this.StartInfo.AssetDirectory!);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the in-game default exception filter.
|
||||||
|
/// </summary>
|
||||||
|
private nint DefaultExceptionFilter { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the in-game debug exception filter.
|
||||||
|
/// </summary>
|
||||||
|
private nint DebugExceptionFilter { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signal to the crash handler process that we should restart the game.
|
/// Signal to the crash handler process that we should restart the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -141,36 +187,38 @@ internal sealed class Dalamud : IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose subsystems related to plugin handling.
|
/// Replace the current exception handler with the default one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisposePlugins()
|
internal void UseDefaultExceptionHandler() =>
|
||||||
{
|
this.SetExceptionHandler(this.DefaultExceptionFilter);
|
||||||
// this must be done before unloading interface manager, in order to do rebuild
|
|
||||||
// the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
|
|
||||||
// will not receive any windows messages
|
|
||||||
Service<DalamudIME>.GetNullable()?.Dispose();
|
|
||||||
|
|
||||||
// this must be done before unloading plugins, or it can cause a race condition
|
|
||||||
// due to rendering happening on another thread, where a plugin might receive
|
|
||||||
// a render call after it has been disposed, which can crash if it attempts to
|
|
||||||
// use any resources that it freed in its own Dispose method
|
|
||||||
Service<InterfaceManager>.GetNullable()?.Dispose();
|
|
||||||
|
|
||||||
Service<DalamudInterface>.GetNullable()?.Dispose();
|
|
||||||
|
|
||||||
Service<PluginManager>.GetNullable()?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replace the built-in exception handler with a debug one.
|
/// Replace the current exception handler with a debug one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void ReplaceExceptionHandler()
|
internal void UseDebugExceptionHandler() =>
|
||||||
{
|
this.SetExceptionHandler(this.DebugExceptionFilter);
|
||||||
var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??";
|
|
||||||
var releaseFilter = Service<SigScanner>.Get().ScanText(releaseSig);
|
|
||||||
Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}");
|
|
||||||
|
|
||||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
/// <summary>
|
||||||
Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
|
/// Disable the current exception handler.
|
||||||
|
/// </summary>
|
||||||
|
internal void UseNoExceptionHandler() =>
|
||||||
|
this.SetExceptionHandler(nint.Zero);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper function to set the exception handler.
|
||||||
|
/// </summary>
|
||||||
|
private void SetExceptionHandler(nint newFilter)
|
||||||
|
{
|
||||||
|
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(newFilter);
|
||||||
|
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, oldFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupClientStructsResolver(DirectoryInfo cacheDir)
|
||||||
|
{
|
||||||
|
using (Timings.Start("CS Resolver Init"))
|
||||||
|
{
|
||||||
|
FFXIVClientStructs.Interop.Resolver.GetInstance.SetupSearchSpace(Service<TargetSigScanner>.Get().SearchBase, new FileInfo(Path.Combine(cacheDir.FullName, $"{this.StartInfo.GameVersion}_cs.json")));
|
||||||
|
FFXIVClientStructs.Interop.Resolver.GetInstance.Resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Feature">
|
<PropertyGroup Label="Feature">
|
||||||
<DalamudVersion>7.10.1.0</DalamudVersion>
|
<DalamudVersion>9.0.0.21</DalamudVersion>
|
||||||
<Description>XIV Launcher addon framework</Description>
|
<Description>XIV Launcher addon framework</Description>
|
||||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||||
<Version>$(DalamudVersion)</Version>
|
<Version>$(DalamudVersion)</Version>
|
||||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||||
|
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Output">
|
<PropertyGroup Label="Output">
|
||||||
|
|
@ -67,8 +68,12 @@
|
||||||
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
||||||
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
|
||||||
<PackageReference Include="Lumina" Version="3.10.2" />
|
<PackageReference Include="Lumina" Version="3.16.0" />
|
||||||
<PackageReference Include="Lumina.Excel" Version="6.4.0" />
|
<PackageReference Include="Lumina.Excel" Version="6.5.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="MinSharp" Version="1.0.4" />
|
<PackageReference Include="MinSharp" Version="1.0.4" />
|
||||||
<PackageReference Include="MonoModReorg.RuntimeDetour" Version="23.1.2-prerelease.1" />
|
<PackageReference Include="MonoModReorg.RuntimeDetour" Version="23.1.2-prerelease.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
|
@ -76,6 +81,7 @@
|
||||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
|
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|
@ -85,9 +91,10 @@
|
||||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" />
|
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" />
|
||||||
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />
|
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />
|
||||||
|
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Dalamud.Interface\Dalamud.Interface.csproj" />
|
<ProjectReference Include="..\Dalamud.Common\Dalamud.Common.csproj" />
|
||||||
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
|
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
|
||||||
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
|
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
|
||||||
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
|
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
|
||||||
|
|
|
||||||
153
Dalamud/DalamudAsset.cs
Normal file
153
Dalamud/DalamudAsset.cs
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
namespace Dalamud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies an asset that has been shipped as Dalamud Asset.<br />
|
||||||
|
/// <strong>Any asset can cease to exist at any point, even if the enum value exists.</strong><br />
|
||||||
|
/// Either ship your own assets, or be prepared for errors.
|
||||||
|
/// </summary>
|
||||||
|
public enum DalamudAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Nothing.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Empty, data: new byte[0])]
|
||||||
|
Unspecified = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromRaw"/>: The fallback empty texture.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromRaw, data: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 })]
|
||||||
|
[DalamudAssetRawTexture(4, 8, 4, SharpDX.DXGI.Format.BC1_UNorm)]
|
||||||
|
Empty4X4 = 1000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The Dalamud logo.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "logo.png")]
|
||||||
|
Logo = 1001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The Dalamud logo, but smaller.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "tsmLogo.png")]
|
||||||
|
LogoSmall = 1002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The default plugin icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "defaultIcon.png")]
|
||||||
|
DefaultIcon = 1003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The disabled plugin icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "disabledIcon.png")]
|
||||||
|
DisabledIcon = 1004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The outdated installable plugin icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "outdatedInstallableIcon.png")]
|
||||||
|
OutdatedInstallableIcon = 1005,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "troubleIcon.png")]
|
||||||
|
TroubleIcon = 1006,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "devPluginIcon.png")]
|
||||||
|
DevPluginIcon = 1007,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin update icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "updateIcon.png")]
|
||||||
|
UpdateIcon = 1008,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin installed icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "installedIcon.png")]
|
||||||
|
InstalledIcon = 1009,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The third party plugin icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "thirdIcon.png")]
|
||||||
|
ThirdIcon = 1010,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The installed third party plugin icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "thirdInstalledIcon.png")]
|
||||||
|
ThirdInstalledIcon = 1011,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The API bump explainer icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "changelogApiBump.png")]
|
||||||
|
ChangelogApiBumpIcon = 1012,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The background shade for
|
||||||
|
/// <see cref="Interface.Internal.Windows.TitleScreenMenuWindow"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "tsmShade.png")]
|
||||||
|
TitleScreenMenuShade = 1013,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansCJKjp-Regular.otf")]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansCJKjp-Medium.otf")]
|
||||||
|
NotoSansJpMedium = 2000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK KR Regular.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansCJKkr-Regular.otf")]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansKR-Regular.otf")]
|
||||||
|
NotoSansKrRegular = 2001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Inconsolata Regular.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "Inconsolata-Regular.ttf")]
|
||||||
|
InconsolataRegular = 2002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
|
||||||
|
FontAwesomeFreeSolid = 2003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Game symbol fonts being used as webfonts at Lodestone.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font, required: false)]
|
||||||
|
// [DalamudAssetOnlineSource("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")]
|
||||||
|
LodestoneGameSymbol = 2004,
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,20 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using Dalamud.Interface.Internal;
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Timing;
|
using Dalamud.Utility.Timing;
|
||||||
using ImGuiScene;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Lumina;
|
using Lumina;
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Lumina.Data.Files;
|
|
||||||
using Lumina.Data.Parsing.Tex.Buffers;
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SharpDX.DXGI;
|
|
||||||
|
|
||||||
namespace Dalamud.Data;
|
namespace Dalamud.Data;
|
||||||
|
|
||||||
|
|
@ -34,39 +27,20 @@ namespace Dalamud.Data;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IDataManager>]
|
[ResolveVia<IDataManager>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
internal sealed class DataManager : IInternalDisposableService, IDataManager
|
||||||
{
|
{
|
||||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
|
||||||
private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.tex";
|
|
||||||
|
|
||||||
private readonly Thread luminaResourceThread;
|
private readonly Thread luminaResourceThread;
|
||||||
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud)
|
private DataManager(Dalamud dalamud)
|
||||||
{
|
{
|
||||||
this.Language = dalamudStartInfo.Language;
|
this.Language = (ClientLanguage)dalamud.StartInfo.Language;
|
||||||
|
|
||||||
// Set up default values so plugins do not null-reference when data is being loaded.
|
|
||||||
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
|
|
||||||
|
|
||||||
var baseDir = dalamud.AssetDirectory.FullName;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Log.Verbose("Starting data load...");
|
Log.Verbose("Starting data load...");
|
||||||
|
|
||||||
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
|
||||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")))!;
|
|
||||||
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
|
|
||||||
|
|
||||||
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
|
|
||||||
|
|
||||||
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
|
||||||
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")))!;
|
|
||||||
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
|
|
||||||
|
|
||||||
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
|
|
||||||
|
|
||||||
using (Timings.Start("Lumina Init"))
|
using (Timings.Start("Lumina Init"))
|
||||||
{
|
{
|
||||||
var luminaOptions = new LuminaOptions
|
var luminaOptions = new LuminaOptions
|
||||||
|
|
@ -93,17 +67,20 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
||||||
|
|
||||||
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
||||||
|
|
||||||
try
|
if (!dalamud.StartInfo.TroubleshootingPackData.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
var tsInfo =
|
try
|
||||||
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
{
|
||||||
dalamudStartInfo.TroubleshootingPackData);
|
var tsInfo =
|
||||||
this.HasModifiedGameDataFiles =
|
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
|
||||||
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed or LauncherTroubleshootingInfo.IndexIntegrityResult.Exception;
|
dalamud.StartInfo.TroubleshootingPackData);
|
||||||
}
|
this.HasModifiedGameDataFiles =
|
||||||
catch
|
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed or LauncherTroubleshootingInfo.IndexIntegrityResult.Exception;
|
||||||
{
|
}
|
||||||
// ignored
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,25 +114,20 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ClientLanguage Language { get; private set; }
|
public ClientLanguage Language { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameData GameData { get; private set; }
|
public GameData GameData { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ExcelModule Excel => this.GameData.Excel;
|
public ExcelModule Excel => this.GameData.Excel;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool IsDataReady { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool HasModifiedGameDataFiles { get; private set; }
|
public bool HasModifiedGameDataFiles { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether Game Data is ready to be read.
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsDataReady { get; private set; }
|
||||||
|
|
||||||
#region Lumina Wrappers
|
#region Lumina Wrappers
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -183,162 +155,10 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
||||||
public bool FileExists(string path)
|
public bool FileExists(string path)
|
||||||
=> this.GameData.FileExists(path);
|
=> this.GameData.FileExists(path);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetIcon(uint iconId)
|
|
||||||
=> this.GetIcon(this.Language, iconId, false);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetIcon(uint iconId, bool highResolution)
|
|
||||||
=> this.GetIcon(this.Language, iconId, highResolution);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetIcon(bool isHq, uint iconId)
|
|
||||||
{
|
|
||||||
var type = isHq ? "hq/" : string.Empty;
|
|
||||||
return this.GetIcon(type, iconId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given language.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconLanguage">The requested language.</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
|
|
||||||
=> this.GetIcon(iconLanguage, iconId, false);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId, bool highResolution)
|
|
||||||
{
|
|
||||||
var type = iconLanguage switch
|
|
||||||
{
|
|
||||||
ClientLanguage.Japanese => "ja/",
|
|
||||||
ClientLanguage.English => "en/",
|
|
||||||
ClientLanguage.German => "de/",
|
|
||||||
ClientLanguage.French => "fr/",
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.GetIcon(type, iconId, highResolution);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetIcon(string? type, uint iconId)
|
|
||||||
=> this.GetIcon(type, iconId, false);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetIcon(string? type, uint iconId, bool highResolution)
|
|
||||||
{
|
|
||||||
var format = highResolution ? HighResolutionIconFileFormat : IconFileFormat;
|
|
||||||
|
|
||||||
type ??= string.Empty;
|
|
||||||
if (type.Length > 0 && !type.EndsWith("/"))
|
|
||||||
type += "/";
|
|
||||||
|
|
||||||
var filePath = string.Format(format, iconId / 1000, type, iconId);
|
|
||||||
var file = this.GetFile<TexFile>(filePath);
|
|
||||||
|
|
||||||
if (type == string.Empty || file != default)
|
|
||||||
return file;
|
|
||||||
|
|
||||||
// Couldn't get specific type, try for generic version.
|
|
||||||
filePath = string.Format(format, iconId / 1000, string.Empty, iconId);
|
|
||||||
file = this.GetFile<TexFile>(filePath);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TexFile? GetHqIcon(uint iconId)
|
|
||||||
=> this.GetIcon(true, iconId);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
[return: NotNullIfNotNull(nameof(tex))]
|
|
||||||
public TextureWrap? GetImGuiTexture(TexFile? tex)
|
|
||||||
{
|
|
||||||
if (tex is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var im = Service<InterfaceManager>.Get();
|
|
||||||
var buffer = tex.TextureBuffer;
|
|
||||||
var bpp = 1 << (((int)tex.Header.Format & (int)TexFile.TextureFormat.BppMask) >>
|
|
||||||
(int)TexFile.TextureFormat.BppShift);
|
|
||||||
|
|
||||||
var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(tex.Header.Format, false);
|
|
||||||
if (conversion != TexFile.DxgiFormatConversion.NoConversion || !im.SupportsDxgiFormat((Format)dxgiFormat))
|
|
||||||
{
|
|
||||||
dxgiFormat = (int)Format.B8G8R8A8_UNorm;
|
|
||||||
buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8);
|
|
||||||
bpp = 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pitch = buffer is BlockCompressionTextureBuffer
|
|
||||||
? Math.Max(1, (buffer.Width + 3) / 4) * 2 * bpp
|
|
||||||
: ((buffer.Width * bpp) + 7) / 8;
|
|
||||||
return im.LoadImageFromDxgiFormat(buffer.RawData, pitch, buffer.Width, buffer.Height, (Format)dxgiFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TextureWrap? GetImGuiTexture(string path)
|
|
||||||
=> this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iconId">The icon ID.</param>
|
|
||||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
|
||||||
/// TODO(v9): remove in api9 in favor of GetImGuiTextureIcon(uint iconId, bool highResolution)
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(iconId, false));
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(uint iconId, bool highResolution)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(iconId, highResolution));
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(isHq, iconId));
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TextureWrap? GetImGuiTextureIcon(string type, uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[Obsolete("Use ITextureProvider instead")]
|
|
||||||
public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
|
|
||||||
=> this.GetImGuiTexture(this.GetHqIcon(iconId));
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IDisposable.Dispose()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.luminaCancellationTokenSource.Cancel();
|
this.luminaCancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
@ -6,10 +5,12 @@ using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Common;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Logging.Retention;
|
using Dalamud.Logging.Retention;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Storage;
|
||||||
using Dalamud.Support;
|
using Dalamud.Support;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -162,7 +163,10 @@ public static class EntryPoint
|
||||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||||
|
|
||||||
// Load configuration first to get some early persistent state, like log level
|
// Load configuration first to get some early persistent state, like log level
|
||||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!);
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
var fs = new ReliableFileStorage(Path.GetDirectoryName(info.ConfigurationPath)!);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs);
|
||||||
|
|
||||||
// Set the appropriate logging level from the configuration
|
// Set the appropriate logging level from the configuration
|
||||||
if (!configuration.LogSynchronously)
|
if (!configuration.LogSynchronously)
|
||||||
|
|
@ -170,7 +174,8 @@ public static class EntryPoint
|
||||||
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
|
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
|
||||||
|
|
||||||
// Log any unhandled exception.
|
// Log any unhandled exception.
|
||||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
if (!info.NoExceptionHandlers)
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||||
|
|
||||||
var unloadFailed = false;
|
var unloadFailed = false;
|
||||||
|
|
@ -186,15 +191,18 @@ public static class EntryPoint
|
||||||
Log.Information(new string('-', 80));
|
Log.Information(new string('-', 80));
|
||||||
Log.Information("Initializing a session..");
|
Log.Information("Initializing a session..");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(info.WorkingDirectory))
|
||||||
|
throw new Exception("Working directory was invalid");
|
||||||
|
|
||||||
Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory);
|
Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory);
|
||||||
|
|
||||||
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
||||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
|
||||||
|
|
||||||
if (!Util.IsLinux())
|
if (!Util.IsWine())
|
||||||
InitSymbolHandler(info);
|
InitSymbolHandler(info);
|
||||||
|
|
||||||
var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent);
|
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
|
||||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]", Util.GetGitHash(), Util.GetGitHashClientStructs(), FFXIVClientStructs.Interop.Resolver.Version);
|
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]", Util.GetGitHash(), Util.GetGitHashClientStructs(), FFXIVClientStructs.Interop.Resolver.Version);
|
||||||
|
|
||||||
dalamud.WaitForUnload();
|
dalamud.WaitForUnload();
|
||||||
|
|
@ -216,7 +224,8 @@ public static class EntryPoint
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
|
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
|
||||||
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
|
if (!info.NoExceptionHandlers)
|
||||||
|
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
|
||||||
|
|
||||||
Log.Information("Session has ended.");
|
Log.Information("Session has ended.");
|
||||||
Log.CloseAndFlush();
|
Log.CloseAndFlush();
|
||||||
|
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
Dalamud/Game/Addon/Events/AddonCursorType.cs
Normal file
97
Dalamud/Game/Addon/Events/AddonCursorType.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reimplementation of CursorType.
|
||||||
|
/// </summary>
|
||||||
|
public enum AddonCursorType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Arrow.
|
||||||
|
/// </summary>
|
||||||
|
Arrow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Boot.
|
||||||
|
/// </summary>
|
||||||
|
Boot,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search.
|
||||||
|
/// </summary>
|
||||||
|
Search,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat Pointer.
|
||||||
|
/// </summary>
|
||||||
|
ChatPointer,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interact.
|
||||||
|
/// </summary>
|
||||||
|
Interact,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attack.
|
||||||
|
/// </summary>
|
||||||
|
Attack,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hand.
|
||||||
|
/// </summary>
|
||||||
|
Hand,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resizeable Left-Right.
|
||||||
|
/// </summary>
|
||||||
|
ResizeWE,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resizeable Up-Down.
|
||||||
|
/// </summary>
|
||||||
|
ResizeNS,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resizeable.
|
||||||
|
/// </summary>
|
||||||
|
ResizeNWSR,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resizeable 4-way.
|
||||||
|
/// </summary>
|
||||||
|
ResizeNESW,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clickable.
|
||||||
|
/// </summary>
|
||||||
|
Clickable,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text Input.
|
||||||
|
/// </summary>
|
||||||
|
TextInput,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text Click.
|
||||||
|
/// </summary>
|
||||||
|
TextClick,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grab.
|
||||||
|
/// </summary>
|
||||||
|
Grab,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat Bubble.
|
||||||
|
/// </summary>
|
||||||
|
ChatBubble,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No Access.
|
||||||
|
/// </summary>
|
||||||
|
NoAccess,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hidden.
|
||||||
|
/// </summary>
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
59
Dalamud/Game/Addon/Events/AddonEventEntry.cs
Normal file
59
Dalamud/Game/Addon/Events/AddonEventEntry.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents a registered event that a plugin registers with a native ui node.
|
||||||
|
/// Contains all necessary information to track and clean up events automatically.
|
||||||
|
/// </summary>
|
||||||
|
internal unsafe class AddonEventEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of an invalid addon.
|
||||||
|
/// </summary>
|
||||||
|
public const string InvalidAddonName = "NullAddon";
|
||||||
|
|
||||||
|
private string? addonName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the pointer to the addons AtkUnitBase.
|
||||||
|
/// </summary>
|
||||||
|
required public nint Addon { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the addon this args referrers to.
|
||||||
|
/// </summary>
|
||||||
|
public string AddonName => this.Addon == nint.Zero ? InvalidAddonName : this.addonName ??= MemoryHelper.ReadString((nint)((AtkUnitBase*)this.Addon)->Name, 0x20);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the pointer to the event source.
|
||||||
|
/// </summary>
|
||||||
|
required public nint Node { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the handler that gets called when this event is triggered.
|
||||||
|
/// </summary>
|
||||||
|
required public IAddonEventManager.AddonEventHandler Handler { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique id for this event.
|
||||||
|
/// </summary>
|
||||||
|
required public uint ParamKey { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event type for this event.
|
||||||
|
/// </summary>
|
||||||
|
required public AddonEventType EventType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event handle for this event.
|
||||||
|
/// </summary>
|
||||||
|
required internal IAddonEventHandle Handle { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the formatted log string for this AddonEventEntry.
|
||||||
|
/// </summary>
|
||||||
|
internal string LogString => $"ParamKey: {this.ParamKey}, Addon: {this.AddonName}, Event: {this.EventType}, GUID: {this.Handle.EventGuid}";
|
||||||
|
}
|
||||||
19
Dalamud/Game/Addon/Events/AddonEventHandle.cs
Normal file
19
Dalamud/Game/Addon/Events/AddonEventHandle.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class that represents a addon event handle.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonEventHandle : IAddonEventHandle
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint ParamKey { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string AddonName { get; init; } = "NullAddon";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public AddonEventType EventType { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Guid EventGuid { get; init; }
|
||||||
|
}
|
||||||
97
Dalamud/Game/Addon/Events/AddonEventListener.cs
Normal file
97
Dalamud/Game/Addon/Events/AddonEventListener.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event listener class for managing custom events.
|
||||||
|
/// </summary>
|
||||||
|
// Custom event handler tech provided by Pohky, implemented by MidoriKami
|
||||||
|
internal unsafe class AddonEventListener : IDisposable
|
||||||
|
{
|
||||||
|
private ReceiveEventDelegate? receiveEventDelegate;
|
||||||
|
|
||||||
|
private AtkEventListener* eventListener;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonEventListener"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventHandler">The managed handler to send events to.</param>
|
||||||
|
public AddonEventListener(ReceiveEventDelegate eventHandler)
|
||||||
|
{
|
||||||
|
this.receiveEventDelegate = eventHandler;
|
||||||
|
|
||||||
|
this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener));
|
||||||
|
this.eventListener->vtbl = (void*)Marshal.AllocHGlobal(sizeof(void*) * 3);
|
||||||
|
this.eventListener->vfunc[0] = (delegate* unmanaged<void>)&NullSub;
|
||||||
|
this.eventListener->vfunc[1] = (delegate* unmanaged<void>)&NullSub;
|
||||||
|
this.eventListener->vfunc[2] = (void*)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for receiving custom events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="self">Pointer to the event listener.</param>
|
||||||
|
/// <param name="eventType">Event type.</param>
|
||||||
|
/// <param name="eventParam">Unique Id for this event.</param>
|
||||||
|
/// <param name="eventData">Event Data.</param>
|
||||||
|
/// <param name="unknown">Unknown Parameter.</param>
|
||||||
|
public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, nint unknown);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of this listener.
|
||||||
|
/// </summary>
|
||||||
|
public nint Address => (nint)this.eventListener;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this.eventListener is null) return;
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal((nint)this.eventListener->vtbl);
|
||||||
|
Marshal.FreeHGlobal((nint)this.eventListener);
|
||||||
|
|
||||||
|
this.eventListener = null;
|
||||||
|
this.receiveEventDelegate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register an event to this event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addon">Addon that triggers this event.</param>
|
||||||
|
/// <param name="node">Node to attach event to.</param>
|
||||||
|
/// <param name="eventType">Event type to trigger this event.</param>
|
||||||
|
/// <param name="param">Unique id for this event.</param>
|
||||||
|
public void RegisterEvent(AtkUnitBase* addon, AtkResNode* node, AtkEventType eventType, uint param)
|
||||||
|
{
|
||||||
|
if (node is null) return;
|
||||||
|
|
||||||
|
Service<Framework>.Get().RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
node->AddEvent(eventType, param, this.eventListener, (AtkResNode*)addon, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister an event from this event handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to remove the event from.</param>
|
||||||
|
/// <param name="eventType">Event type that this event is for.</param>
|
||||||
|
/// <param name="param">Unique id for this event.</param>
|
||||||
|
public void UnregisterEvent(AtkResNode* node, AtkEventType eventType, uint param)
|
||||||
|
{
|
||||||
|
if (node is null) return;
|
||||||
|
|
||||||
|
Service<Framework>.Get().RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
node->RemoveEvent(eventType, param, this.eventListener, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
private static void NullSub()
|
||||||
|
{
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
262
Dalamud/Game/Addon/Events/AddonEventManager.cs
Normal file
262
Dalamud/Game/Addon/Events/AddonEventManager.cs
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service provider for addon event management.
|
||||||
|
/// </summary>
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// PluginName for Dalamud Internal use.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Guid DalamudInternalKey = Guid.NewGuid();
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||||
|
|
||||||
|
private readonly AddonLifecycleEventListener finalizeEventListener;
|
||||||
|
|
||||||
|
private readonly AddonEventManagerAddressResolver address;
|
||||||
|
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
|
||||||
|
|
||||||
|
private AddonCursorType? cursorOverride;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private AddonEventManager(TargetSigScanner sigScanner)
|
||||||
|
{
|
||||||
|
this.address = new AddonEventManagerAddressResolver();
|
||||||
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
|
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
|
||||||
|
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
|
||||||
|
|
||||||
|
this.cursorOverride = null;
|
||||||
|
|
||||||
|
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
|
||||||
|
|
||||||
|
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
|
||||||
|
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
|
||||||
|
|
||||||
|
this.onUpdateCursor.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.onUpdateCursor.Dispose();
|
||||||
|
|
||||||
|
foreach (var (_, pluginEventController) in this.pluginEventControllers)
|
||||||
|
{
|
||||||
|
pluginEventController.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addonLifecycle.UnregisterListener(this.finalizeEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers an event handler for the specified addon, node, and type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||||
|
/// <param name="atkUnitBase">The parent addon for this event.</param>
|
||||||
|
/// <param name="atkResNode">The node that will trigger this event.</param>
|
||||||
|
/// <param name="eventType">The event type for this event.</param>
|
||||||
|
/// <param name="eventHandler">The handler to call when event is triggered.</param>
|
||||||
|
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
||||||
|
internal IAddonEventHandle? AddEvent(Guid pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||||
|
{
|
||||||
|
if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
|
||||||
|
{
|
||||||
|
return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters an event handler with the specified event id and event type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||||
|
/// <param name="eventHandle">The Unique Id for this event.</param>
|
||||||
|
internal void RemoveEvent(Guid pluginId, IAddonEventHandle eventHandle)
|
||||||
|
{
|
||||||
|
if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
|
||||||
|
{
|
||||||
|
controller.RemoveEvent(eventHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Verbose($"Unable to locate controller for {pluginId}. No event was removed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Force the game cursor to be the specified cursor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cursor">Which cursor to use.</param>
|
||||||
|
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Un-forces the game cursor.
|
||||||
|
/// </summary>
|
||||||
|
internal void ResetCursor() => this.cursorOverride = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new managed event controller if one doesn't already exist for this pluginId.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||||
|
internal void AddPluginEventController(Guid pluginId)
|
||||||
|
{
|
||||||
|
this.pluginEventControllers.GetOrAdd(
|
||||||
|
pluginId,
|
||||||
|
key =>
|
||||||
|
{
|
||||||
|
Log.Verbose($"Creating new PluginEventController for: {key}");
|
||||||
|
return new PluginEventController();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes an existing managed event controller for the specified plugin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginId">Unique ID for this plugin.</param>
|
||||||
|
internal void RemovePluginEventController(Guid pluginId)
|
||||||
|
{
|
||||||
|
if (this.pluginEventControllers.TryRemove(pluginId, out var controller))
|
||||||
|
{
|
||||||
|
Log.Verbose($"Removing PluginEventController for: {pluginId}");
|
||||||
|
controller.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When an addon finalizes, check it for any registered events, and unregister them.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event type that triggered this call.</param>
|
||||||
|
/// <param name="addonInfo">Addon that triggered this call.</param>
|
||||||
|
private void OnAddonFinalize(AddonEvent eventType, AddonArgs addonInfo)
|
||||||
|
{
|
||||||
|
// It shouldn't be possible for this event to be anything other than PreFinalize.
|
||||||
|
if (eventType != AddonEvent.PreFinalize) return;
|
||||||
|
|
||||||
|
foreach (var pluginList in this.pluginEventControllers)
|
||||||
|
{
|
||||||
|
pluginList.Value.RemoveForAddon(addonInfo.AddonName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private nint UpdateCursorDetour(RaptureAtkModule* module)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var atkStage = AtkStage.GetSingleton();
|
||||||
|
|
||||||
|
if (this.cursorOverride is not null && atkStage is not null)
|
||||||
|
{
|
||||||
|
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
|
||||||
|
if (cursor != this.cursorOverride)
|
||||||
|
{
|
||||||
|
AtkStage.GetSingleton()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nint.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in UpdateCursorDetour.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.onUpdateCursor!.Original(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a AddonEventManager service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IAddonEventManager>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddonEventManager
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly AddonEventManager eventManagerService = Service<AddonEventManager>.Get();
|
||||||
|
|
||||||
|
private readonly LocalPlugin plugin;
|
||||||
|
|
||||||
|
private bool isForcingCursor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonEventManagerPluginScoped"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">Plugin info for the plugin that requested this service.</param>
|
||||||
|
public AddonEventManagerPluginScoped(LocalPlugin plugin)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
this.eventManagerService.AddPluginEventController(plugin.Manifest.WorkingPluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
|
||||||
|
if (this.isForcingCursor)
|
||||||
|
{
|
||||||
|
this.eventManagerService.ResetCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventManagerService.RemovePluginEventController(this.plugin.Manifest.WorkingPluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
|
||||||
|
=> this.eventManagerService.AddEvent(this.plugin.Manifest.WorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RemoveEvent(IAddonEventHandle eventHandle)
|
||||||
|
=> this.eventManagerService.RemoveEvent(this.plugin.Manifest.WorkingPluginId, eventHandle);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void SetCursor(AddonCursorType cursor)
|
||||||
|
{
|
||||||
|
this.isForcingCursor = true;
|
||||||
|
|
||||||
|
this.eventManagerService.SetCursor(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ResetCursor()
|
||||||
|
{
|
||||||
|
this.isForcingCursor = false;
|
||||||
|
|
||||||
|
this.eventManagerService.ResetCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AddonEventManager memory address resolver.
|
||||||
|
/// </summary>
|
||||||
|
internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the AtkModule UpdateCursor method.
|
||||||
|
/// </summary>
|
||||||
|
public nint UpdateCursor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scan for and setup any configured address pointers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
||||||
|
protected override void Setup64Bit(ISigScanner scanner)
|
||||||
|
{
|
||||||
|
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE");
|
||||||
|
}
|
||||||
|
}
|
||||||
158
Dalamud/Game/Addon/Events/AddonEventType.cs
Normal file
158
Dalamud/Game/Addon/Events/AddonEventType.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reimplementation of AtkEventType.
|
||||||
|
/// </summary>
|
||||||
|
public enum AddonEventType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mouse Down.
|
||||||
|
/// </summary>
|
||||||
|
MouseDown = 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mouse Up.
|
||||||
|
/// </summary>
|
||||||
|
MouseUp = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mouse Move.
|
||||||
|
/// </summary>
|
||||||
|
MouseMove = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mouse Over.
|
||||||
|
/// </summary>
|
||||||
|
MouseOver = 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mouse Out.
|
||||||
|
/// </summary>
|
||||||
|
MouseOut = 7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mouse Click.
|
||||||
|
/// </summary>
|
||||||
|
MouseClick = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Input Received.
|
||||||
|
/// </summary>
|
||||||
|
InputReceived = 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Focus Start.
|
||||||
|
/// </summary>
|
||||||
|
FocusStart = 18,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Focus Stop.
|
||||||
|
/// </summary>
|
||||||
|
FocusStop = 19,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Button Press, sent on MouseDown on Button.
|
||||||
|
/// </summary>
|
||||||
|
ButtonPress = 23,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Button Release, sent on MouseUp and MouseOut.
|
||||||
|
/// </summary>
|
||||||
|
ButtonRelease = 24,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Button Click, sent on MouseUp and MouseClick on button.
|
||||||
|
/// </summary>
|
||||||
|
ButtonClick = 25,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List Item RollOver.
|
||||||
|
/// </summary>
|
||||||
|
ListItemRollOver = 33,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List Item Roll Out.
|
||||||
|
/// </summary>
|
||||||
|
ListItemRollOut = 34,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List Item Toggle.
|
||||||
|
/// </summary>
|
||||||
|
ListItemToggle = 35,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Begin.
|
||||||
|
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
|
||||||
|
/// </summary>
|
||||||
|
DragDropBegin = 47,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Insert.
|
||||||
|
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
|
||||||
|
/// </summary>
|
||||||
|
DragDropInsert = 50,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Roll Over.
|
||||||
|
/// </summary>
|
||||||
|
DragDropRollOver = 52,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Roll Out.
|
||||||
|
/// </summary>
|
||||||
|
DragDropRollOut = 53,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Discard.
|
||||||
|
/// Sent when dropping an icon into empty screenspace, eg to remove an action from a hotBar.
|
||||||
|
/// </summary>
|
||||||
|
DragDropDiscard = 54,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Unknown.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use DragDropDiscard")]
|
||||||
|
DragDropUnk54 = 54,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Cancel.
|
||||||
|
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||||
|
/// </summary>
|
||||||
|
DragDropCancel = 55,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drag Drop Unknown.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use DragDropCancel")]
|
||||||
|
DragDropUnk55 = 55,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Icon Text Roll Over.
|
||||||
|
/// </summary>
|
||||||
|
IconTextRollOver = 56,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Icon Text Roll Out.
|
||||||
|
/// </summary>
|
||||||
|
IconTextRollOut = 57,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Icon Text Click.
|
||||||
|
/// </summary>
|
||||||
|
IconTextClick = 58,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Window Roll Over.
|
||||||
|
/// </summary>
|
||||||
|
WindowRollOver = 67,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Window Roll Out.
|
||||||
|
/// </summary>
|
||||||
|
WindowRollOut = 68,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Window Change Scale.
|
||||||
|
/// </summary>
|
||||||
|
WindowChangeScale = 69,
|
||||||
|
}
|
||||||
27
Dalamud/Game/Addon/Events/IAddonEventHandle.cs
Normal file
27
Dalamud/Game/Addon/Events/IAddonEventHandle.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface representing the data used for managing AddonEvents.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAddonEventHandle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the param key associated with this event.
|
||||||
|
/// </summary>
|
||||||
|
public uint ParamKey { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the addon that this event was attached to.
|
||||||
|
/// </summary>
|
||||||
|
public string AddonName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event type associated with this handle.
|
||||||
|
/// </summary>
|
||||||
|
public AddonEventType EventType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique ID for this handle.
|
||||||
|
/// </summary>
|
||||||
|
public Guid EventGuid { get; init; }
|
||||||
|
}
|
||||||
201
Dalamud/Game/Addon/Events/PluginEventController.cs
Normal file
201
Dalamud/Game/Addon/Events/PluginEventController.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class to manage creating and cleaning up events per-plugin.
|
||||||
|
/// </summary>
|
||||||
|
internal unsafe class PluginEventController : IDisposable
|
||||||
|
{
|
||||||
|
private static readonly ModuleLog Log = new("AddonEventManager");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public PluginEventController()
|
||||||
|
{
|
||||||
|
this.EventListener = new AddonEventListener(this.PluginEventListHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddonEventListener EventListener { get; init; }
|
||||||
|
|
||||||
|
private List<AddonEventEntry> Events { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a tracked event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="atkUnitBase">The Parent addon for the event.</param>
|
||||||
|
/// <param name="atkResNode">The Node for the event.</param>
|
||||||
|
/// <param name="atkEventType">The Event Type.</param>
|
||||||
|
/// <param name="handler">The delegate to call when invoking this event.</param>
|
||||||
|
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
||||||
|
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
|
||||||
|
{
|
||||||
|
var node = (AtkResNode*)atkResNode;
|
||||||
|
var addon = (AtkUnitBase*)atkUnitBase;
|
||||||
|
var eventType = (AtkEventType)atkEventType;
|
||||||
|
var eventId = this.GetNextParamKey();
|
||||||
|
var eventGuid = Guid.NewGuid();
|
||||||
|
|
||||||
|
var eventHandle = new AddonEventHandle
|
||||||
|
{
|
||||||
|
AddonName = MemoryHelper.ReadStringNullTerminated((nint)addon->Name),
|
||||||
|
ParamKey = eventId,
|
||||||
|
EventType = atkEventType,
|
||||||
|
EventGuid = eventGuid,
|
||||||
|
};
|
||||||
|
|
||||||
|
var eventEntry = new AddonEventEntry
|
||||||
|
{
|
||||||
|
Addon = atkUnitBase,
|
||||||
|
Handler = handler,
|
||||||
|
Node = atkResNode,
|
||||||
|
EventType = atkEventType,
|
||||||
|
ParamKey = eventId,
|
||||||
|
Handle = eventHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.Verbose($"Adding Event. {eventEntry.LogString}");
|
||||||
|
this.EventListener.RegisterEvent(addon, node, eventType, eventId);
|
||||||
|
this.Events.Add(eventEntry);
|
||||||
|
|
||||||
|
return eventHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a tracked event, also attempts to un-attach the event from native.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">Unique ID of the event to remove.</param>
|
||||||
|
public void RemoveEvent(IAddonEventHandle handle)
|
||||||
|
{
|
||||||
|
if (this.Events.FirstOrDefault(registeredEvent => registeredEvent.Handle == handle) is not { } targetEvent) return;
|
||||||
|
|
||||||
|
Log.Verbose($"Removing Event. {targetEvent.LogString}");
|
||||||
|
this.TryRemoveEventFromNative(targetEvent);
|
||||||
|
this.Events.Remove(targetEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all events attached to the specified addon.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addonName">Addon name to remove events from.</param>
|
||||||
|
public void RemoveForAddon(string addonName)
|
||||||
|
{
|
||||||
|
if (this.Events.Where(entry => entry.AddonName == addonName).ToList() is { Count: not 0 } events)
|
||||||
|
{
|
||||||
|
Log.Verbose($"Addon: {addonName} is Finalizing, removing {events.Count} events.");
|
||||||
|
|
||||||
|
foreach (var registeredEvent in events)
|
||||||
|
{
|
||||||
|
this.RemoveEvent(registeredEvent.Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var registeredEvent in this.Events.ToList())
|
||||||
|
{
|
||||||
|
this.RemoveEvent(registeredEvent.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.EventListener.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint GetNextParamKey()
|
||||||
|
{
|
||||||
|
for (var i = 0u; i < uint.MaxValue; ++i)
|
||||||
|
{
|
||||||
|
if (this.Events.All(registeredEvent => registeredEvent.ParamKey != i)) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new OverflowException($"uint.MaxValue number of ParamKeys used for this event controller.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to remove a tracked event from native UI.
|
||||||
|
/// This method performs several safety checks to only remove events from a still active addon.
|
||||||
|
/// If any of these checks fail, it likely means the native UI already cleaned up the event, and we don't have to worry about them.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventEntry">Event entry to remove.</param>
|
||||||
|
private void TryRemoveEventFromNative(AddonEventEntry eventEntry)
|
||||||
|
{
|
||||||
|
// Is the eventEntry addon valid?
|
||||||
|
if (eventEntry.AddonName is AddonEventEntry.InvalidAddonName) return;
|
||||||
|
|
||||||
|
// Is an addon with the same name active?
|
||||||
|
var currentAddonPointer = Service<GameGui>.Get().GetAddonByName(eventEntry.AddonName);
|
||||||
|
if (currentAddonPointer == nint.Zero) return;
|
||||||
|
|
||||||
|
// Is our stored addon pointer the same as the active addon pointer?
|
||||||
|
if (currentAddonPointer != eventEntry.Addon) return;
|
||||||
|
|
||||||
|
// Does this addon contain the node this event is for? (by address)
|
||||||
|
var atkUnitBase = (AtkUnitBase*)currentAddonPointer;
|
||||||
|
var nodeFound = false;
|
||||||
|
foreach (var index in Enumerable.Range(0, atkUnitBase->UldManager.NodeListCount))
|
||||||
|
{
|
||||||
|
var node = atkUnitBase->UldManager.NodeList[index];
|
||||||
|
|
||||||
|
// If this node matches our node, then we know our node is still valid.
|
||||||
|
if (node is not null && (nint)node == eventEntry.Node)
|
||||||
|
{
|
||||||
|
nodeFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't find the node, we can't remove the event.
|
||||||
|
if (!nodeFound) return;
|
||||||
|
|
||||||
|
// Does the node have a registered event matching the parameters we have?
|
||||||
|
var atkResNode = (AtkResNode*)eventEntry.Node;
|
||||||
|
var eventType = (AtkEventType)eventEntry.EventType;
|
||||||
|
var currentEvent = atkResNode->AtkEventManager.Event;
|
||||||
|
var eventFound = false;
|
||||||
|
while (currentEvent is not null)
|
||||||
|
{
|
||||||
|
var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey;
|
||||||
|
var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address;
|
||||||
|
var eventTypeMatches = currentEvent->Type == eventType;
|
||||||
|
|
||||||
|
if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches)
|
||||||
|
{
|
||||||
|
eventFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event.
|
||||||
|
currentEvent = currentEvent->NextEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't find the event, we can't remove the event.
|
||||||
|
if (!eventFound) return;
|
||||||
|
|
||||||
|
// We have a valid addon, valid node, valid event, and valid key.
|
||||||
|
this.EventListener.UnregisterEvent(atkResNode, eventType, eventEntry.ParamKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventData, IntPtr unknown)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (eventData is null) return;
|
||||||
|
if (this.Events.FirstOrDefault(handler => handler.ParamKey == eventParam) is not { } eventInfo) return;
|
||||||
|
|
||||||
|
// We stored the AtkUnitBase* in EventData->Node, and EventData->Target contains the node that triggered the event.
|
||||||
|
eventInfo.Handler.Invoke((AddonEventType)eventType, (nint)eventData->Node, (nint)eventData->Target);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Log.Error(exception, "Exception in PluginEventList custom event invoke.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
Normal file
85
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
using Dalamud.Memory;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for AddonLifecycle AddonArgTypes.
|
||||||
|
/// </summary>
|
||||||
|
public abstract unsafe class AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constant string representing the name of an addon that is invalid.
|
||||||
|
/// </summary>
|
||||||
|
public const string InvalidAddon = "NullAddon";
|
||||||
|
|
||||||
|
private string? addonName;
|
||||||
|
private IntPtr addon;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the addon this args referrers to.
|
||||||
|
/// </summary>
|
||||||
|
public string AddonName => this.GetAddonName();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the pointer to the addons AtkUnitBase.
|
||||||
|
/// </summary>
|
||||||
|
public nint Addon
|
||||||
|
{
|
||||||
|
get => this.AddonInternal;
|
||||||
|
init => this.AddonInternal = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of these args.
|
||||||
|
/// </summary>
|
||||||
|
public abstract AddonArgsType Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the pointer to the addons AtkUnitBase.
|
||||||
|
/// </summary>
|
||||||
|
internal nint AddonInternal
|
||||||
|
{
|
||||||
|
get => this.addon;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.addon = value;
|
||||||
|
|
||||||
|
// Note: always clear addonName on updating the addon being pointed.
|
||||||
|
// Same address may point to a different addon.
|
||||||
|
this.addonName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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(ReadOnlySpan<char> name)
|
||||||
|
{
|
||||||
|
if (this.Addon == nint.Zero) return false;
|
||||||
|
if (name.Length is 0 or > 0x20)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var addonPointer = (AtkUnitBase*)this.Addon;
|
||||||
|
if (addonPointer->Name is null) return false;
|
||||||
|
|
||||||
|
return MemoryHelper.EqualsZeroTerminatedString(name, (nint)addonPointer->Name, null, 0x20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 == nint.Zero) return InvalidAddon;
|
||||||
|
|
||||||
|
var addonPointer = (AtkUnitBase*)this.Addon;
|
||||||
|
if (addonPointer->Name is null) return InvalidAddon;
|
||||||
|
|
||||||
|
return this.addonName ??= MemoryHelper.ReadString((nint)addonPointer->Name, 0x20);
|
||||||
|
}
|
||||||
|
}
|
||||||
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for ReceiveEvent events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonReceiveEventArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.ReceiveEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the AtkEventType for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public byte AtkEventType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the event id for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public int EventParam { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the pointer to an AtkEvent for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public nint AtkEvent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the pointer to a block of data for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public nint Data { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||||
|
public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Clone"/>
|
||||||
|
object ICloneable.Clone() => this.Clone();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Refresh events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonRefreshArgs : AddonArgs, ICloneable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonRefreshArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Refresh;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of AtkValues.
|
||||||
|
/// </summary>
|
||||||
|
public uint AtkValueCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the address of the AtkValue array.
|
||||||
|
/// </summary>
|
||||||
|
public nint AtkValues { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the AtkValues in the form of a span.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||||
|
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Clone"/>
|
||||||
|
object ICloneable.Clone() => this.Clone();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for OnRequestedUpdate events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonRequestedUpdateArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.RequestedUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the NumberArrayData** for this event.
|
||||||
|
/// </summary>
|
||||||
|
public nint NumberArrayData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the StringArrayData** for this event.
|
||||||
|
/// </summary>
|
||||||
|
public nint StringArrayData { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||||
|
public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Clone"/>
|
||||||
|
object ICloneable.Clone() => this.Clone();
|
||||||
|
}
|
||||||
41
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
Normal file
41
Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for Setup events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonSetupArgs : AddonArgs, ICloneable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Not intended for public construction.", false)]
|
||||||
|
public AddonSetupArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.Setup;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of AtkValues.
|
||||||
|
/// </summary>
|
||||||
|
public uint AtkValueCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the address of the AtkValue array.
|
||||||
|
/// </summary>
|
||||||
|
public nint AtkValues { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the AtkValues in the form of a span.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||||
|
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Clone"/>
|
||||||
|
object ICloneable.Clone() => this.Clone();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
42
Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs
Normal file
42
Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for available AddonLifecycle arg data.
|
||||||
|
/// </summary>
|
||||||
|
public enum AddonArgsType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Setup.
|
||||||
|
/// </summary>
|
||||||
|
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>
|
||||||
|
/// Contains argument data for RequestedUpdate.
|
||||||
|
/// </summary>
|
||||||
|
RequestedUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for Refresh.
|
||||||
|
/// </summary>
|
||||||
|
Refresh,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for ReceiveEvent.
|
||||||
|
/// </summary>
|
||||||
|
ReceiveEvent,
|
||||||
|
}
|
||||||
72
Dalamud/Game/Addon/Lifecycle/AddonEvent.cs
Normal file
72
Dalamud/Game/Addon/Lifecycle/AddonEvent.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for available AddonLifecycle events.
|
||||||
|
/// </summary>
|
||||||
|
public enum AddonEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired before an addon begins it's setup process.
|
||||||
|
/// </summary>
|
||||||
|
PreSetup,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired after an addon has completed it's setup process.
|
||||||
|
/// </summary>
|
||||||
|
PostSetup,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired before an addon begins update.
|
||||||
|
/// </summary>
|
||||||
|
PreUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired after an addon has completed update.
|
||||||
|
/// </summary>
|
||||||
|
PostUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired before an addon begins draw.
|
||||||
|
/// </summary>
|
||||||
|
PreDraw,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired after an addon has completed draw.
|
||||||
|
/// </summary>
|
||||||
|
PostDraw,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired before an addon is finalized.
|
||||||
|
/// </summary>
|
||||||
|
PreFinalize,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired before an addon begins a requested update.
|
||||||
|
/// </summary>
|
||||||
|
PreRequestedUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired after an addon finishes a requested update.
|
||||||
|
/// </summary>
|
||||||
|
PostRequestedUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired before an addon begins a refresh.
|
||||||
|
/// </summary>
|
||||||
|
PreRefresh,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired after an addon has finished a refresh.
|
||||||
|
/// </summary>
|
||||||
|
PostRefresh,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired before an addon begins processing an event.
|
||||||
|
/// </summary>
|
||||||
|
PreReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is fired after an addon has processed an event.
|
||||||
|
/// </summary>
|
||||||
|
PostReceiveEvent,
|
||||||
|
}
|
||||||
468
Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
Normal file
468
Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
Normal file
|
|
@ -0,0 +1,468 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Hooking.Internal;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides events for in-game addon lifecycles.
|
||||||
|
/// </summary>
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
|
{
|
||||||
|
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||||
|
|
||||||
|
[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 CallHook<AddonSetupDelegate> onAddonSetupHook;
|
||||||
|
private readonly CallHook<AddonSetupDelegate> onAddonSetup2Hook;
|
||||||
|
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
|
||||||
|
private readonly CallHook<AddonDrawDelegate> onAddonDrawHook;
|
||||||
|
private readonly CallHook<AddonUpdateDelegate> onAddonUpdateHook;
|
||||||
|
private readonly Hook<AddonOnRefreshDelegate> onAddonRefreshHook;
|
||||||
|
private readonly CallHook<AddonOnRequestedUpdateDelegate> onAddonRequestedUpdateHook;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private AddonLifecycle(TargetSigScanner sigScanner)
|
||||||
|
{
|
||||||
|
this.address = new AddonLifecycleAddressResolver();
|
||||||
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
|
// We want value of the function pointer at vFunc[2]
|
||||||
|
this.disallowedReceiveEventAddress = ((nint*)this.address.AtkEventListener)![2];
|
||||||
|
|
||||||
|
this.onAddonSetupHook = new CallHook<AddonSetupDelegate>(this.address.AddonSetup, this.OnAddonSetup);
|
||||||
|
this.onAddonSetup2Hook = new CallHook<AddonSetupDelegate>(this.address.AddonSetup2, this.OnAddonSetup);
|
||||||
|
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
|
||||||
|
this.onAddonDrawHook = new CallHook<AddonDrawDelegate>(this.address.AddonDraw, this.OnAddonDraw);
|
||||||
|
this.onAddonUpdateHook = new CallHook<AddonUpdateDelegate>(this.address.AddonUpdate, this.OnAddonUpdate);
|
||||||
|
this.onAddonRefreshHook = Hook<AddonOnRefreshDelegate>.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh);
|
||||||
|
this.onAddonRequestedUpdateHook = new CallHook<AddonOnRequestedUpdateDelegate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
|
||||||
|
|
||||||
|
this.onAddonSetupHook.Enable();
|
||||||
|
this.onAddonSetup2Hook.Enable();
|
||||||
|
this.onAddonFinalizeHook.Enable();
|
||||||
|
this.onAddonDrawHook.Enable();
|
||||||
|
this.onAddonUpdateHook.Enable();
|
||||||
|
this.onAddonRefreshHook.Enable();
|
||||||
|
this.onAddonRequestedUpdateHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate void AddonSetupDelegate(AtkUnitBase* addon, uint valueCount, AtkValue* values);
|
||||||
|
|
||||||
|
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
||||||
|
|
||||||
|
private delegate void AddonDrawDelegate(AtkUnitBase* addon);
|
||||||
|
|
||||||
|
private delegate void AddonUpdateDelegate(AtkUnitBase* addon, float delta);
|
||||||
|
|
||||||
|
private delegate void AddonOnRequestedUpdateDelegate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData);
|
||||||
|
|
||||||
|
private delegate byte AddonOnRefreshDelegate(AtkUnitManager* unitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
|
||||||
|
/// </summary>
|
||||||
|
internal List<AddonLifecycleReceiveEventListener> ReceiveEventListeners { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all AddonLifecycle Event Listeners.
|
||||||
|
/// </summary>
|
||||||
|
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.onAddonSetupHook.Dispose();
|
||||||
|
this.onAddonSetup2Hook.Dispose();
|
||||||
|
this.onAddonFinalizeHook.Dispose();
|
||||||
|
this.onAddonDrawHook.Dispose();
|
||||||
|
this.onAddonUpdateHook.Dispose();
|
||||||
|
this.onAddonRefreshHook.Dispose();
|
||||||
|
this.onAddonRequestedUpdateHook.Dispose();
|
||||||
|
|
||||||
|
foreach (var receiveEventListener in this.ReceiveEventListeners)
|
||||||
|
{
|
||||||
|
receiveEventListener.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a listener for the target event and addon.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The listener to register.</param>
|
||||||
|
internal void RegisterListener(AddonLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
this.framework.RunOnTick(() =>
|
||||||
|
{
|
||||||
|
this.EventListeners.Add(listener);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
|
||||||
|
{
|
||||||
|
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
|
||||||
|
{
|
||||||
|
receiveEventListener.Hook?.Enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters the listener from events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The listener to unregister.</param>
|
||||||
|
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
this.framework.RunOnTick(() =>
|
||||||
|
{
|
||||||
|
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 })
|
||||||
|
{
|
||||||
|
// 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.Hook?.Disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke listeners for the specified event type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event Type.</param>
|
||||||
|
/// <param name="args">AddonArgs.</param>
|
||||||
|
/// <param name="blame">What to blame on errors.</param>
|
||||||
|
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
||||||
|
{
|
||||||
|
// Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
|
||||||
|
foreach (var listener in this.EventListeners)
|
||||||
|
{
|
||||||
|
if (listener.EventType != eventType)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Match on string.empty for listeners that want events for all addons.
|
||||||
|
if (!string.IsNullOrWhiteSpace(listener.AddonName) && !args.IsAddon(listener.AddonName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterReceiveEventHook(AtkUnitBase* addon)
|
||||||
|
{
|
||||||
|
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
|
||||||
|
// Disallows hooking the core internal event handler.
|
||||||
|
var addonName = MemoryHelper.ReadStringNullTerminated((nint)addon->Name);
|
||||||
|
var receiveEventAddress = (nint)addon->VTable->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.HookAddress == receiveEventAddress) is { } existingListener)
|
||||||
|
{
|
||||||
|
if (!existingListener.AddonNames.Contains(addonName))
|
||||||
|
{
|
||||||
|
existingListener.AddonNames.Add(addonName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, we have an addon that we don't have the ReceiveEvent for yet, make it.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.ReceiveEventListeners.Add(new AddonLifecycleReceiveEventListener(this, addonName, receiveEventAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an active listener for this addon already, we need to activate this hook.
|
||||||
|
if (this.EventListeners.Any(listener => (listener.EventType is AddonEvent.PostReceiveEvent or AddonEvent.PreReceiveEvent) && listener.AddonName == addonName))
|
||||||
|
{
|
||||||
|
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } receiveEventListener)
|
||||||
|
{
|
||||||
|
receiveEventListener.Hook?.Enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
this.RegisterReceiveEventHook(addon);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var returner = this.argsPool.Rent(out AddonSetupArgs arg);
|
||||||
|
arg.AddonInternal = (nint)addon;
|
||||||
|
arg.AtkValueCount = valueCount;
|
||||||
|
arg.AtkValues = (nint)values;
|
||||||
|
this.InvokeListenersSafely(AddonEvent.PreSetup, arg);
|
||||||
|
valueCount = arg.AtkValueCount;
|
||||||
|
values = (AtkValue*)arg.AtkValues;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
addon->OnSetup(valueCount, values);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original AddonSetup. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.InvokeListenersSafely(AddonEvent.PostSetup, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var addonName = MemoryHelper.ReadStringNullTerminated((nint)atkUnitBase[0]->Name);
|
||||||
|
this.UnregisterReceiveEventHook(addonName);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg);
|
||||||
|
arg.AddonInternal = (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.AddonInternal = (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.AddonInternal = (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 byte OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||||
|
{
|
||||||
|
byte result = 0;
|
||||||
|
|
||||||
|
using var returner = this.argsPool.Rent(out AddonRefreshArgs arg);
|
||||||
|
arg.AddonInternal = (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(atkUnitManager, 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.AddonInternal = (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->OnUpdate(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a AddonLifecycle service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IAddonLifecycle>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLifecycle
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
|
||||||
|
|
||||||
|
private readonly List<AddonLifecycleEventListener> eventListeners = new();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
foreach (var listener in this.eventListeners)
|
||||||
|
{
|
||||||
|
this.addonLifecycleService.UnregisterListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AddonEvent eventType, IEnumerable<string> addonNames, IAddonLifecycle.AddonEventDelegate handler)
|
||||||
|
{
|
||||||
|
foreach (var addonName in addonNames)
|
||||||
|
{
|
||||||
|
this.RegisterListener(eventType, addonName, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate handler)
|
||||||
|
{
|
||||||
|
var listener = new AddonLifecycleEventListener(eventType, addonName, handler);
|
||||||
|
this.eventListeners.Add(listener);
|
||||||
|
this.addonLifecycleService.RegisterListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate handler)
|
||||||
|
{
|
||||||
|
this.RegisterListener(eventType, string.Empty, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AddonEvent eventType, IEnumerable<string> addonNames, IAddonLifecycle.AddonEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
foreach (var addonName in addonNames)
|
||||||
|
{
|
||||||
|
this.UnregisterListener(eventType, addonName, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
this.eventListeners.RemoveAll(entry =>
|
||||||
|
{
|
||||||
|
if (entry.EventType != eventType) return false;
|
||||||
|
if (entry.AddonName != addonName) return false;
|
||||||
|
if (handler is not null && entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
|
this.addonLifecycleService.UnregisterListener(entry);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
this.UnregisterListener(eventType, string.Empty, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(params IAddonLifecycle.AddonEventDelegate[] handlers)
|
||||||
|
{
|
||||||
|
foreach (var handler in handlers)
|
||||||
|
{
|
||||||
|
this.eventListeners.RemoveAll(entry =>
|
||||||
|
{
|
||||||
|
if (entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
|
this.addonLifecycleService.UnregisterListener(entry);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AddonLifecycleService memory address resolver.
|
||||||
|
/// </summary>
|
||||||
|
internal 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>
|
||||||
|
/// Gets the address of AtkUnitManager_vf10 which triggers addon onRefresh.
|
||||||
|
/// </summary>
|
||||||
|
public nint AddonOnRefresh { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of AtkEventListener base vTable.
|
||||||
|
/// This is used to ensure that we do not hook ReceiveEvents that resolve back to the internal handler.
|
||||||
|
/// </summary>
|
||||||
|
public nint AtkEventListener { 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("FF 90 ?? ?? ?? ?? 48 8B 93 ?? ?? ?? ?? 80 8B");
|
||||||
|
this.AddonSetup2 = sig.ScanText("FF 90 ?? ?? ?? ?? 48 8B 03 48 8B CB 80 8B");
|
||||||
|
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 7C 24 ?? 41 8B C6");
|
||||||
|
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C1");
|
||||||
|
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF");
|
||||||
|
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 98 01 00 00 48 8B 5C 24 30 48 83 C4 20");
|
||||||
|
this.AddonOnRefresh = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 41 8B F8 48 8B DA");
|
||||||
|
this.AtkEventListener = sig.GetStaticAddressFromSig("4C 8D 3D ?? ?? ?? ?? 49 8D 8E");
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Dalamud/Game/Addon/Lifecycle/AddonLifecycleEventListener.cs
Normal file
38
Dalamud/Game/Addon/Lifecycle/AddonLifecycleEventListener.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a helper for tracking and invoking listener delegates.
|
||||||
|
/// </summary>
|
||||||
|
internal class AddonLifecycleEventListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonLifecycleEventListener"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event type to listen for.</param>
|
||||||
|
/// <param name="addonName">Addon name to listen for.</param>
|
||||||
|
/// <param name="functionDelegate">Delegate to invoke.</param>
|
||||||
|
internal AddonLifecycleEventListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate functionDelegate)
|
||||||
|
{
|
||||||
|
this.EventType = eventType;
|
||||||
|
this.AddonName = addonName;
|
||||||
|
this.FunctionDelegate = functionDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the addon this listener is looking for.
|
||||||
|
/// string.Empty if it wants to be called for any addon.
|
||||||
|
/// </summary>
|
||||||
|
public string AddonName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event type this listener is looking for.
|
||||||
|
/// </summary>
|
||||||
|
public AddonEvent EventType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the delegate this listener invokes.
|
||||||
|
/// </summary>
|
||||||
|
public IAddonLifecycle.AddonEventDelegate FunctionDelegate { get; init; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
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 = new List<string> { addonName };
|
||||||
|
this.Hook = Hook<AddonReceiveEventDelegate>.FromAddress(receiveEventAddress, this.OnReceiveEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon Receive Event Function delegate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addon">Addon Pointer.</param>
|
||||||
|
/// <param name="eventType">Event Type.</param>
|
||||||
|
/// <param name="eventParam">Unique Event ID.</param>
|
||||||
|
/// <param name="atkEvent">Event Data.</param>
|
||||||
|
/// <param name="a5">Unknown.</param>
|
||||||
|
public delegate void AddonReceiveEventDelegate(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, nint a5);
|
||||||
|
|
||||||
|
/// <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 registered hook.
|
||||||
|
/// </summary>
|
||||||
|
public nint HookAddress => this.Hook?.Address ?? nint.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the contained hook for these addons.
|
||||||
|
/// </summary>
|
||||||
|
public Hook<AddonReceiveEventDelegate>? Hook { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Reference to AddonLifecycle service instance.
|
||||||
|
/// </summary>
|
||||||
|
private AddonLifecycle AddonLifecycle { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Hook?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, nint data)
|
||||||
|
{
|
||||||
|
// Check that we didn't get here through a call to another addons handler.
|
||||||
|
var addonName = MemoryHelper.ReadString((nint)addon->Name, 0x20);
|
||||||
|
if (!this.AddonNames.Contains(addonName))
|
||||||
|
{
|
||||||
|
this.Hook!.Original(addon, eventType, eventParam, atkEvent, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg);
|
||||||
|
arg.AddonInternal = (nint)addon;
|
||||||
|
arg.AtkEventType = (byte)eventType;
|
||||||
|
arg.EventParam = eventParam;
|
||||||
|
arg.AtkEvent = (IntPtr)atkEvent;
|
||||||
|
arg.Data = data;
|
||||||
|
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg);
|
||||||
|
eventType = (AtkEventType)arg.AtkEventType;
|
||||||
|
eventParam = arg.EventParam;
|
||||||
|
atkEvent = (AtkEvent*)arg.AtkEvent;
|
||||||
|
data = arg.Data;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.Hook!.Original(addon, eventType, eventParam, atkEvent, data);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,24 +18,15 @@ public abstract class BaseAddressResolver
|
||||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(ISigScanner)"/> or <see cref="Setup64Bit(ISigScanner)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool IsResolved { get; set; }
|
protected bool IsResolved { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setup the resolver, calling the appropriate method based on the process architecture,
|
|
||||||
/// using the default SigScanner.
|
|
||||||
///
|
|
||||||
/// For plugins. Not intended to be called from Dalamud Service{T} constructors.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public void Setup() => this.Setup(Service<SigScanner>.Get());
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Setup the resolver, calling the appropriate method based on the process architecture.
|
/// Setup the resolver, calling the appropriate method based on the process architecture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
public void Setup(SigScanner scanner)
|
public void Setup(ISigScanner scanner)
|
||||||
{
|
{
|
||||||
// Because C# don't allow to call virtual function while in ctor
|
// Because C# don't allow to call virtual function while in ctor
|
||||||
// we have to do this shit :\
|
// we have to do this shit :\
|
||||||
|
|
@ -92,7 +83,7 @@ public abstract class BaseAddressResolver
|
||||||
/// Setup the resolver by finding any necessary memory addresses.
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
protected virtual void Setup32Bit(SigScanner scanner)
|
protected virtual void Setup32Bit(ISigScanner scanner)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("32 bit version is not supported.");
|
throw new NotSupportedException("32 bit version is not supported.");
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +92,7 @@ public abstract class BaseAddressResolver
|
||||||
/// Setup the resolver by finding any necessary memory addresses.
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
protected virtual void Setup64Bit(SigScanner scanner)
|
protected virtual void Setup64Bit(ISigScanner scanner)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("64 bit version is not supported.");
|
throw new NotSupportedException("64 bit version is not supported.");
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +101,7 @@ public abstract class BaseAddressResolver
|
||||||
/// Setup the resolver by finding any necessary memory addresses.
|
/// Setup the resolver by finding any necessary memory addresses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scanner">The SigScanner instance.</param>
|
/// <param name="scanner">The SigScanner instance.</param>
|
||||||
protected virtual void SetupInternal(SigScanner scanner)
|
protected virtual void SetupInternal(ISigScanner scanner)
|
||||||
{
|
{
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
@ -11,24 +11,22 @@ using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Interface.Internal.Windows;
|
using Dalamud.Interface.Internal.Windows;
|
||||||
using Dalamud.IoC;
|
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Chat events and public helper functions.
|
/// Chat events and public helper functions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
public class ChatHandlers : IServiceType
|
internal class ChatHandlers : IServiceType
|
||||||
{
|
{
|
||||||
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||||
// {
|
// {
|
||||||
|
|
@ -64,6 +62,8 @@ public class ChatHandlers : IServiceType
|
||||||
// { XivChatType.Echo, Color.Gray },
|
// { XivChatType.Echo, Color.Gray },
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("CHATHANDLER");
|
||||||
|
|
||||||
private readonly Regex rmtRegex = new(
|
private readonly Regex rmtRegex = new(
|
||||||
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
|
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
@ -106,11 +106,15 @@ public class ChatHandlers : IServiceType
|
||||||
|
|
||||||
private readonly DalamudLinkPayload openInstallerWindowLink;
|
private readonly DalamudLinkPayload openInstallerWindowLink;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Dalamud dalamud = Service<Dalamud>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
private bool hasSeenLoadingMsg;
|
private bool hasSeenLoadingMsg;
|
||||||
private bool startedAutoUpdatingPlugins;
|
private bool startedAutoUpdatingPlugins;
|
||||||
|
private CancellationTokenSource deferredAutoUpdateCts = new();
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private ChatHandlers(ChatGui chatGui)
|
private ChatHandlers(ChatGui chatGui)
|
||||||
|
|
@ -120,7 +124,7 @@ public class ChatHandlers : IServiceType
|
||||||
|
|
||||||
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||||
{
|
{
|
||||||
Service<DalamudInterface>.GetNullable()?.OpenPluginInstaller();
|
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(PluginInstallerWindow.PluginInstallerOpenKind.InstalledPlugins);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,22 +138,6 @@ public class ChatHandlers : IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsAutoUpdateComplete { get; private set; }
|
public bool IsAutoUpdateComplete { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">Text to convert.</param>
|
|
||||||
/// <returns>SeString payload of italicized text.</returns>
|
|
||||||
public static SeString MakeItalics(string text)
|
|
||||||
=> MakeItalics(new TextPayload(text));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a TextPayload to SeString and wrap in italics payloads.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">Text to convert.</param>
|
|
||||||
/// <returns>SeString payload of italicized text.</returns>
|
|
||||||
public static SeString MakeItalics(TextPayload text)
|
|
||||||
=> new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff);
|
|
||||||
|
|
||||||
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
{
|
{
|
||||||
var textVal = message.TextValue;
|
var textVal = message.TextValue;
|
||||||
|
|
@ -178,21 +166,23 @@ public class ChatHandlers : IServiceType
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
{
|
{
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
|
||||||
var clientState = Service<ClientState.ClientState>.GetNullable();
|
var clientState = Service<ClientState.ClientState>.GetNullable();
|
||||||
if (clientState == null)
|
if (clientState == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
if (type == XivChatType.Notice)
|
||||||
this.PrintWelcomeMessage();
|
{
|
||||||
|
if (!this.hasSeenLoadingMsg)
|
||||||
|
this.PrintWelcomeMessage();
|
||||||
|
|
||||||
|
if (!this.startedAutoUpdatingPlugins)
|
||||||
|
this.AutoUpdatePluginsWithRetry();
|
||||||
|
}
|
||||||
|
|
||||||
// For injections while logged in
|
// For injections while logged in
|
||||||
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
|
||||||
this.PrintWelcomeMessage();
|
this.PrintWelcomeMessage();
|
||||||
|
|
||||||
if (!this.startedAutoUpdatingPlugins)
|
|
||||||
this.AutoUpdatePlugins();
|
|
||||||
|
|
||||||
#if !DEBUG && false
|
#if !DEBUG && false
|
||||||
if (!this.hasSeenLoadingMsg)
|
if (!this.hasSeenLoadingMsg)
|
||||||
return;
|
return;
|
||||||
|
|
@ -200,7 +190,7 @@ public class ChatHandlers : IServiceType
|
||||||
|
|
||||||
if (type == XivChatType.RetainerSale)
|
if (type == XivChatType.RetainerSale)
|
||||||
{
|
{
|
||||||
foreach (var regex in this.retainerSaleRegexes[startInfo.Language])
|
foreach (var regex in this.retainerSaleRegexes[(ClientLanguage)this.dalamud.StartInfo.Language])
|
||||||
{
|
{
|
||||||
var matchInfo = regex.Match(message.TextValue);
|
var matchInfo = regex.Match(message.TextValue);
|
||||||
|
|
||||||
|
|
@ -258,22 +248,21 @@ public class ChatHandlers : IServiceType
|
||||||
{
|
{
|
||||||
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
|
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
|
||||||
{
|
{
|
||||||
chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion));
|
chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.EffectiveVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
|
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
|
||||||
{
|
{
|
||||||
chatGui.PrintChat(new XivChatEntry
|
chatGui.Print(new XivChatEntry
|
||||||
{
|
{
|
||||||
Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."),
|
Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."),
|
||||||
Type = XivChatType.Notice,
|
Type = XivChatType.Notice,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
|
if (ChangelogWindow.WarrantsChangelog())
|
||||||
{
|
{
|
||||||
dalamudInterface.OpenChangelogWindow();
|
dalamudInterface.OpenChangelogWindow();
|
||||||
this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.configuration.LastVersion = assemblyVersion;
|
this.configuration.LastVersion = assemblyVersion;
|
||||||
|
|
@ -283,24 +272,42 @@ public class ChatHandlers : IServiceType
|
||||||
this.hasSeenLoadingMsg = true;
|
this.hasSeenLoadingMsg = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AutoUpdatePlugins()
|
private void AutoUpdatePluginsWithRetry()
|
||||||
|
{
|
||||||
|
var firstAttempt = this.AutoUpdatePlugins();
|
||||||
|
if (!firstAttempt)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
Task.Delay(30_000, this.deferredAutoUpdateCts.Token);
|
||||||
|
this.AutoUpdatePlugins();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AutoUpdatePlugins()
|
||||||
{
|
{
|
||||||
var chatGui = Service<ChatGui>.GetNullable();
|
var chatGui = Service<ChatGui>.GetNullable();
|
||||||
var pluginManager = Service<PluginManager>.GetNullable();
|
var pluginManager = Service<PluginManager>.GetNullable();
|
||||||
var notifications = Service<NotificationManager>.GetNullable();
|
var notifications = Service<NotificationManager>.GetNullable();
|
||||||
|
|
||||||
if (chatGui == null || pluginManager == null || notifications == null)
|
if (chatGui == null || pluginManager == null || notifications == null)
|
||||||
return;
|
{
|
||||||
|
Log.Warning("Aborting auto-update because a required service was not loaded.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any())
|
if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any())
|
||||||
{
|
{
|
||||||
// Plugins aren't ready yet.
|
// Plugins aren't ready yet.
|
||||||
// TODO: We should retry. This sucks, because it means we won't ever get here again until another notice.
|
// TODO: We should retry. This sucks, because it means we won't ever get here again until another notice.
|
||||||
return;
|
Log.Warning("Aborting auto-update because plugins weren't loaded or ready.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startedAutoUpdatingPlugins = true;
|
this.startedAutoUpdatingPlugins = true;
|
||||||
|
|
||||||
|
Log.Debug("Beginning plugin auto-update process...");
|
||||||
Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins, true)).ContinueWith(task =>
|
Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins, true)).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
this.IsAutoUpdateComplete = true;
|
this.IsAutoUpdateComplete = true;
|
||||||
|
|
@ -321,7 +328,7 @@ public class ChatHandlers : IServiceType
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chatGui.PrintChat(new XivChatEntry
|
chatGui.Print(new XivChatEntry
|
||||||
{
|
{
|
||||||
Message = new SeString(new List<Payload>()
|
Message = new SeString(new List<Payload>()
|
||||||
{
|
{
|
||||||
|
|
@ -339,5 +346,7 @@ public class ChatHandlers : IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.ClientState.Aetherytes;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IAetheryteList>]
|
[ResolveVia<IAetheryteList>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
||||||
{
|
{
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
|
|
@ -78,7 +78,7 @@ public sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the list of available Aetherytes in the Teleport window.
|
/// This collection represents the list of available Aetherytes in the Teleport window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AetheryteList
|
internal sealed partial class AetheryteList
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int Count => this.Length;
|
public int Count => this.Length;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace Dalamud.Game.ClientState.Buddy;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IBuddyList>]
|
[ResolveVia<IBuddyList>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public sealed partial class BuddyList : IServiceType, IBuddyList
|
internal sealed partial class BuddyList : IServiceType, IBuddyList
|
||||||
{
|
{
|
||||||
private const uint InvalidObjectID = 0xE0000000;
|
private const uint InvalidObjectID = 0xE0000000;
|
||||||
|
|
||||||
|
|
@ -55,18 +55,6 @@ public sealed partial class BuddyList : IServiceType, IBuddyList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the local player's companion is present.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use CompanionBuddy != null", false)]
|
|
||||||
public bool CompanionBuddyPresent => this.CompanionBuddy != null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the local player's pet is present.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use PetBuddy != null", false)]
|
|
||||||
public bool PetBuddyPresent => this.PetBuddy != null;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public BuddyMember? CompanionBuddy
|
public BuddyMember? CompanionBuddy
|
||||||
{
|
{
|
||||||
|
|
@ -147,7 +135,7 @@ public sealed partial class BuddyList : IServiceType, IBuddyList
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the buddies present in your squadron or trust party.
|
/// This collection represents the buddies present in your squadron or trust party.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class BuddyList
|
internal sealed partial class BuddyList
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
|
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
|
@ -9,24 +8,25 @@ using Dalamud.Game.Network.Internal;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Serilog;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
using Action = System.Action;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState;
|
namespace Dalamud.Game.ClientState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents the state of the game client at the time of access.
|
/// This class represents the state of the game client at the time of access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
#pragma warning disable SA1015
|
internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
[ResolveVia<IClientState>]
|
|
||||||
#pragma warning restore SA1015
|
|
||||||
public sealed class ClientState : IDisposable, IServiceType, IClientState
|
|
||||||
{
|
{
|
||||||
|
private static readonly ModuleLog Log = new("ClientState");
|
||||||
|
|
||||||
private readonly GameLifecycle lifecycle;
|
private readonly GameLifecycle lifecycle;
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||||
|
|
@ -38,10 +38,10 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
||||||
|
|
||||||
private bool lastConditionNone = true;
|
private bool lastConditionNone = true;
|
||||||
private bool lastFramePvP = false;
|
private bool lastFramePvP;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo, GameLifecycle lifecycle)
|
private ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle)
|
||||||
{
|
{
|
||||||
this.lifecycle = lifecycle;
|
this.lifecycle = lifecycle;
|
||||||
this.address = new ClientStateAddressResolver();
|
this.address = new ClientStateAddressResolver();
|
||||||
|
|
@ -49,7 +49,7 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
|
|
||||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
Log.Verbose("===== C L I E N T S T A T E =====");
|
||||||
|
|
||||||
this.ClientLanguage = startInfo.Language;
|
this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language;
|
||||||
|
|
||||||
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
||||||
|
|
||||||
|
|
@ -58,28 +58,30 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||||
|
|
||||||
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||||
|
|
||||||
|
this.setupTerritoryTypeHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<ushort> TerritoryChanged;
|
public event Action<ushort>? TerritoryChanged;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler Login;
|
public event Action? Login;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler Logout;
|
public event Action? Logout;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action EnterPvP;
|
public event Action? EnterPvP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event Action LeavePvP;
|
public event Action? LeavePvP;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<Lumina.Excel.GeneratedSheets.ContentFinderCondition> CfPop;
|
public event Action<ContentFinderCondition>? CfPop;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ClientLanguage ClientLanguage { get; }
|
public ClientLanguage ClientLanguage { get; }
|
||||||
|
|
@ -102,6 +104,9 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsPvPExcludingDen { get; private set; }
|
public bool IsPvPExcludingDen { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsGPosing => GameMain.IsInGPose();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets client state address resolver.
|
/// Gets client state address resolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -110,35 +115,29 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose of managed and unmanaged resources.
|
/// Dispose of managed and unmanaged resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void IDisposable.Dispose()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.setupTerritoryTypeHook.Dispose();
|
this.setupTerritoryTypeHook.Dispose();
|
||||||
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
||||||
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction()
|
|
||||||
{
|
|
||||||
this.setupTerritoryTypeHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
|
||||||
{
|
{
|
||||||
this.TerritoryType = terriType;
|
this.TerritoryType = terriType;
|
||||||
this.TerritoryChanged?.InvokeSafely(this, terriType);
|
this.TerritoryChanged?.InvokeSafely(terriType);
|
||||||
|
|
||||||
Log.Debug("TerritoryType changed: {0}", terriType);
|
Log.Debug("TerritoryType changed: {0}", terriType);
|
||||||
|
|
||||||
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
return this.setupTerritoryTypeHook.Original(manager, terriType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e)
|
private void NetworkHandlersOnCfPop(ContentFinderCondition e)
|
||||||
{
|
{
|
||||||
this.CfPop?.InvokeSafely(this, e);
|
this.CfPop?.InvokeSafely(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FrameworkOnOnUpdateEvent(Framework framework1)
|
private void FrameworkOnOnUpdateEvent(IFramework framework1)
|
||||||
{
|
{
|
||||||
var condition = Service<Conditions.Condition>.GetNullable();
|
var condition = Service<Conditions.Condition>.GetNullable();
|
||||||
var gameGui = Service<GameGui>.GetNullable();
|
var gameGui = Service<GameGui>.GetNullable();
|
||||||
|
|
@ -147,12 +146,12 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
if (condition == null || gameGui == null || data == null)
|
if (condition == null || gameGui == null || data == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (condition.Any() && this.lastConditionNone == true && this.LocalPlayer != null)
|
if (condition.Any() && this.lastConditionNone && this.LocalPlayer != null)
|
||||||
{
|
{
|
||||||
Log.Debug("Is login");
|
Log.Debug("Is login");
|
||||||
this.lastConditionNone = false;
|
this.lastConditionNone = false;
|
||||||
this.IsLoggedIn = true;
|
this.IsLoggedIn = true;
|
||||||
this.Login?.InvokeSafely(this, null);
|
this.Login?.InvokeSafely();
|
||||||
gameGui.ResetUiHideState();
|
gameGui.ResetUiHideState();
|
||||||
|
|
||||||
this.lifecycle.ResetLogout();
|
this.lifecycle.ResetLogout();
|
||||||
|
|
@ -163,7 +162,7 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
Log.Debug("Is logout");
|
Log.Debug("Is logout");
|
||||||
this.lastConditionNone = true;
|
this.lastConditionNone = true;
|
||||||
this.IsLoggedIn = false;
|
this.IsLoggedIn = false;
|
||||||
this.Logout?.InvokeSafely(this, null);
|
this.Logout?.InvokeSafely();
|
||||||
gameGui.ResetUiHideState();
|
gameGui.ResetUiHideState();
|
||||||
|
|
||||||
this.lifecycle.SetLogout();
|
this.lifecycle.SetLogout();
|
||||||
|
|
@ -187,3 +186,103 @@ public sealed class ClientState : IDisposable, IServiceType, IClientState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a GameConfig service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IClientState>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class ClientStatePluginScoped : IInternalDisposableService, IClientState
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ClientState clientStateService = Service<ClientState>.Get();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ClientStatePluginScoped"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal ClientStatePluginScoped()
|
||||||
|
{
|
||||||
|
this.clientStateService.TerritoryChanged += this.TerritoryChangedForward;
|
||||||
|
this.clientStateService.Login += this.LoginForward;
|
||||||
|
this.clientStateService.Logout += this.LogoutForward;
|
||||||
|
this.clientStateService.EnterPvP += this.EnterPvPForward;
|
||||||
|
this.clientStateService.LeavePvP += this.ExitPvPForward;
|
||||||
|
this.clientStateService.CfPop += this.ContentFinderPopForward;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action<ushort>? TerritoryChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action? Login;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action? Logout;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action? EnterPvP;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action? LeavePvP;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action<ContentFinderCondition>? CfPop;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ClientLanguage ClientLanguage => this.clientStateService.ClientLanguage;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ushort TerritoryType => this.clientStateService.TerritoryType;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public PlayerCharacter? LocalPlayer => this.clientStateService.LocalPlayer;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong LocalContentId => this.clientStateService.LocalContentId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsLoggedIn => this.clientStateService.IsLoggedIn;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsPvP => this.clientStateService.IsPvP;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsPvPExcludingDen => this.clientStateService.IsPvPExcludingDen;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsGPosing => this.clientStateService.IsGPosing;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward;
|
||||||
|
this.clientStateService.Login -= this.LoginForward;
|
||||||
|
this.clientStateService.Logout -= this.LogoutForward;
|
||||||
|
this.clientStateService.EnterPvP -= this.EnterPvPForward;
|
||||||
|
this.clientStateService.LeavePvP -= this.ExitPvPForward;
|
||||||
|
this.clientStateService.CfPop -= this.ContentFinderPopForward;
|
||||||
|
|
||||||
|
this.TerritoryChanged = null;
|
||||||
|
this.Login = null;
|
||||||
|
this.Logout = null;
|
||||||
|
this.EnterPvP = null;
|
||||||
|
this.LeavePvP = null;
|
||||||
|
this.CfPop = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TerritoryChangedForward(ushort territoryId) => this.TerritoryChanged?.Invoke(territoryId);
|
||||||
|
|
||||||
|
private void LoginForward() => this.Login?.Invoke();
|
||||||
|
|
||||||
|
private void LogoutForward() => this.Logout?.Invoke();
|
||||||
|
|
||||||
|
private void EnterPvPForward() => this.EnterPvP?.Invoke();
|
||||||
|
|
||||||
|
private void ExitPvPForward() => this.LeavePvP?.Invoke();
|
||||||
|
|
||||||
|
private void ContentFinderPopForward(ContentFinderCondition cfc) => this.CfPop?.Invoke(cfc);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace Dalamud.Game.ClientState;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client state memory address resolver.
|
/// Client state memory address resolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ClientStateAddressResolver : BaseAddressResolver
|
internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
// Static offsets
|
// Static offsets
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ public sealed class ClientStateAddressResolver : BaseAddressResolver
|
||||||
/// Scan for and setup any configured address pointers.
|
/// Scan for and setup any configured address pointers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
protected override void Setup64Bit(ISigScanner sig)
|
||||||
{
|
{
|
||||||
this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ??");
|
this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ??");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Conditions;
|
namespace Dalamud.Game.ClientState.Conditions;
|
||||||
|
|
@ -9,47 +8,48 @@ namespace Dalamud.Game.ClientState.Conditions;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
public sealed partial class Condition : IServiceType
|
internal sealed partial class Condition : IInternalDisposableService, ICondition
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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>
|
||||||
public const int MaxConditionEntries = 104;
|
internal const int MaxConditionEntries = 104;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
private readonly bool[] cache = new bool[MaxConditionEntries];
|
private readonly bool[] cache = new bool[MaxConditionEntries];
|
||||||
|
|
||||||
|
private bool isDisposed;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private Condition(ClientState clientState)
|
private Condition(ClientState clientState)
|
||||||
{
|
{
|
||||||
var resolver = clientState.AddressResolver;
|
var resolver = clientState.AddressResolver;
|
||||||
this.Address = resolver.ConditionFlags;
|
this.Address = resolver.ConditionFlags;
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
for (var i = 0; i < MaxConditionEntries; i++)
|
||||||
|
this.cache[i] = this[i];
|
||||||
|
|
||||||
|
this.framework.Update += this.FrameworkUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Finalizes an instance of the <see cref="Condition" /> class.</summary>
|
||||||
|
~Condition() => this.Dispose(false);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// A delegate type used with the <see cref="ConditionChange"/> event.
|
public event ICondition.ConditionChangeDelegate? ConditionChange;
|
||||||
/// </summary>
|
|
||||||
/// <param name="flag">The changed condition.</param>
|
|
||||||
/// <param name="value">The value the condition is set to.</param>
|
|
||||||
public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Event that gets fired when a condition is set.
|
public int MaxEntries => MaxConditionEntries;
|
||||||
/// Should only get fired for actual changes, so the previous value will always be !value.
|
|
||||||
/// </summary>
|
|
||||||
public event ConditionChangeDelegate? ConditionChange;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the condition array base pointer.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Address { get; private set; }
|
public IntPtr Address { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Check the value of a specific condition/state flag.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="flag">The condition flag to check.</param>
|
|
||||||
public unsafe bool this[int flag]
|
public unsafe bool this[int flag]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -61,14 +61,14 @@ public sealed partial class Condition : IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="this[int]"/>
|
/// <inheritdoc/>
|
||||||
public unsafe bool this[ConditionFlag flag]
|
public bool this[ConditionFlag flag]
|
||||||
=> this[(int)flag];
|
=> this[(int)flag];
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Check if any condition flags are set.
|
void IInternalDisposableService.DisposeService() => this.Dispose(true);
|
||||||
/// </summary>
|
|
||||||
/// <returns>Whether any single flag is set.</returns>
|
/// <inheritdoc/>
|
||||||
public bool Any()
|
public bool Any()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
for (var i = 0; i < MaxConditionEntries; i++)
|
||||||
|
|
@ -81,18 +81,36 @@ public sealed partial class Condition : IServiceType
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
/// <inheritdoc/>
|
||||||
private void ContinueConstruction(Framework framework)
|
public bool Any(params ConditionFlag[] flags)
|
||||||
{
|
{
|
||||||
// Initialization
|
foreach (var flag in flags)
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
{
|
||||||
this.cache[i] = this[i];
|
// this[i] performs range checking, so no need to check here
|
||||||
|
if (this[flag])
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
framework.Update += this.FrameworkUpdate;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FrameworkUpdate(Framework framework)
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (this.isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
this.framework.Update -= this.FrameworkUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrameworkUpdate(IFramework unused)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < MaxConditionEntries; i++)
|
for (var i = 0; i < MaxConditionEntries; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -116,39 +134,52 @@ public sealed partial class Condition : IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
|
/// Plugin-scoped version of a Condition service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class Condition : IDisposable
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<ICondition>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class ConditionPluginScoped : IInternalDisposableService, ICondition
|
||||||
{
|
{
|
||||||
private bool isDisposed;
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Condition conditionService = Service<Condition>.Get();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finalizes an instance of the <see cref="Condition" /> class.
|
/// Initializes a new instance of the <see cref="ConditionPluginScoped"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
~Condition()
|
internal ConditionPluginScoped()
|
||||||
{
|
{
|
||||||
this.Dispose(false);
|
this.conditionService.ConditionChange += this.ConditionChangedForward;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event ICondition.ConditionChangeDelegate? ConditionChange;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int MaxEntries => this.conditionService.MaxEntries;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IntPtr Address => this.conditionService.Address;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool this[int flag] => this.conditionService[flag];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.conditionService.ConditionChange -= this.ConditionChangedForward;
|
||||||
|
|
||||||
|
this.ConditionChange = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Disposes this instance, alongside its hooks.
|
public bool Any() => this.conditionService.Any();
|
||||||
/// </summary>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
this.Dispose(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
/// <inheritdoc/>
|
||||||
{
|
public bool Any(params ConditionFlag[] flags) => this.conditionService.Any(flags);
|
||||||
if (this.isDisposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (disposing)
|
private void ConditionChangedForward(ConditionFlag flag, bool value) => this.ConditionChange?.Invoke(flag, value);
|
||||||
{
|
|
||||||
Service<Framework>.Get().Update -= this.FrameworkUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isDisposed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.ClientState.Fates;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IFateTable>]
|
[ResolveVia<IFateTable>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public sealed partial class FateTable : IServiceType, IFateTable
|
internal sealed partial class FateTable : IServiceType, IFateTable
|
||||||
{
|
{
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ public sealed partial class FateTable : IServiceType, IFateTable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the currently available Fate events.
|
/// This collection represents the currently available Fate events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class FateTable
|
internal sealed partial class FateTable
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int IReadOnlyCollection<Fate>.Count => this.Length;
|
int IReadOnlyCollection<Fate>.Count => this.Length;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Game.ClientState.GamePad;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IGamepadState>]
|
[ResolveVia<IGamepadState>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
|
||||||
{
|
{
|
||||||
private readonly Hook<ControllerPoll>? gamepadPoll;
|
private readonly Hook<ControllerPoll>? gamepadPoll;
|
||||||
|
|
||||||
|
|
@ -38,6 +38,7 @@ public unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
||||||
var resolver = clientState.AddressResolver;
|
var resolver = clientState.AddressResolver;
|
||||||
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
||||||
this.gamepadPoll = Hook<ControllerPoll>.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
|
this.gamepadPoll = Hook<ControllerPoll>.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
|
||||||
|
this.gamepadPoll?.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
private delegate int ControllerPoll(IntPtr controllerInput);
|
||||||
|
|
@ -55,54 +56,6 @@ public unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
||||||
public Vector2 RightStick =>
|
public Vector2 RightStick =>
|
||||||
new(this.rightStickX, this.rightStickY);
|
new(this.rightStickX, this.rightStickY);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.LeftStick.X", false)]
|
|
||||||
public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.LeftStick.X", false)]
|
|
||||||
public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.LeftStick.Y", false)]
|
|
||||||
public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.LeftStick.Y", false)]
|
|
||||||
public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.RightStick.X", false)]
|
|
||||||
public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.RightStick.X", false)]
|
|
||||||
public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.RightStick.Y", false)]
|
|
||||||
public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt).
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use IGamepadState.RightStick.Y", false)]
|
|
||||||
public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets buttons pressed bitmask, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
/// Gets buttons pressed bitmask, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
|
||||||
///
|
///
|
||||||
|
|
@ -156,18 +109,12 @@ public unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes this instance, alongside its hooks.
|
/// Disposes this instance, alongside its hooks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void IDisposable.Dispose()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.Dispose(true);
|
this.Dispose(true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction()
|
|
||||||
{
|
|
||||||
this.gamepadPoll?.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GamepadPollDetour(IntPtr gamepadInput)
|
private int GamepadPollDetour(IntPtr gamepadInput)
|
||||||
{
|
{
|
||||||
var original = this.gamepadPoll!.Original(gamepadInput);
|
var original = this.gamepadPoll!.Original(gamepadInput);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.ClientState.JobGauge;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IJobGauges>]
|
[ResolveVia<IJobGauges>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public class JobGauges : IServiceType, IJobGauges
|
internal class JobGauges : IServiceType, IJobGauges
|
||||||
{
|
{
|
||||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Keys;
|
namespace Dalamud.Game.ClientState.Keys;
|
||||||
|
|
@ -23,7 +25,10 @@ namespace Dalamud.Game.ClientState.Keys;
|
||||||
[PluginInterface]
|
[PluginInterface]
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
public class KeyState : IServiceType
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IKeyState>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class KeyState : IServiceType, IKeyState
|
||||||
{
|
{
|
||||||
// The array is accessed in a way that this limit doesn't appear to exist
|
// The array is accessed in a way that this limit doesn't appear to exist
|
||||||
// but there is other state data past this point, and keys beyond here aren't
|
// but there is other state data past this point, and keys beyond here aren't
|
||||||
|
|
@ -31,10 +36,10 @@ public class KeyState : IServiceType
|
||||||
private const int MaxKeyCode = 0xF0;
|
private const int MaxKeyCode = 0xF0;
|
||||||
private readonly IntPtr bufferBase;
|
private readonly IntPtr bufferBase;
|
||||||
private readonly IntPtr indexBase;
|
private readonly IntPtr indexBase;
|
||||||
private VirtualKey[] validVirtualKeyCache = null;
|
private VirtualKey[]? validVirtualKeyCache;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private KeyState(SigScanner sigScanner, ClientState clientState)
|
private KeyState(TargetSigScanner sigScanner, ClientState clientState)
|
||||||
{
|
{
|
||||||
var moduleBaseAddress = sigScanner.Module.BaseAddress;
|
var moduleBaseAddress = sigScanner.Module.BaseAddress;
|
||||||
var addressResolver = clientState.AddressResolver;
|
var addressResolver = clientState.AddressResolver;
|
||||||
|
|
@ -44,46 +49,29 @@ public class KeyState : IServiceType
|
||||||
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
|
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Get or set the key-pressed state for a given vkCode.
|
public bool this[int vkCode]
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">The virtual key to change.</param>
|
|
||||||
/// <returns>Whether the specified key is currently pressed.</returns>
|
|
||||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
|
||||||
public unsafe bool this[int vkCode]
|
|
||||||
{
|
{
|
||||||
get => this.GetRawValue(vkCode) != 0;
|
get => this.GetRawValue(vkCode) != 0;
|
||||||
set => this.SetRawValue(vkCode, value ? 1 : 0);
|
set => this.SetRawValue(vkCode, value ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="this[int]"/>
|
/// <inheritdoc/>
|
||||||
public bool this[VirtualKey vkCode]
|
public bool this[VirtualKey vkCode]
|
||||||
{
|
{
|
||||||
get => this[(int)vkCode];
|
get => this[(int)vkCode];
|
||||||
set => this[(int)vkCode] = value;
|
set => this[(int)vkCode] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the value in the index array.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">The virtual key to change.</param>
|
|
||||||
/// <returns>The raw value stored in the index array.</returns>
|
|
||||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
|
||||||
public int GetRawValue(int vkCode)
|
public int GetRawValue(int vkCode)
|
||||||
=> this.GetRefValue(vkCode);
|
=> this.GetRefValue(vkCode);
|
||||||
|
|
||||||
/// <inheritdoc cref="GetRawValue(int)"/>
|
/// <inheritdoc/>
|
||||||
public int GetRawValue(VirtualKey vkCode)
|
public int GetRawValue(VirtualKey vkCode)
|
||||||
=> this.GetRawValue((int)vkCode);
|
=> this.GetRawValue((int)vkCode);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Sets the value in the index array.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">The virtual key to change.</param>
|
|
||||||
/// <param name="value">The raw value to set in the index array.</param>
|
|
||||||
/// <exception cref="ArgumentException">If the vkCode is not valid. Refer to <see cref="IsVirtualKeyValid(int)"/> or <see cref="GetValidVirtualKeys"/>.</exception>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">If the set value is non-zero.</exception>
|
|
||||||
public void SetRawValue(int vkCode, int value)
|
public void SetRawValue(int vkCode, int value)
|
||||||
{
|
{
|
||||||
if (value != 0)
|
if (value != 0)
|
||||||
|
|
@ -92,32 +80,23 @@ public class KeyState : IServiceType
|
||||||
this.GetRefValue(vkCode) = value;
|
this.GetRefValue(vkCode) = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="SetRawValue(int, int)"/>
|
/// <inheritdoc/>
|
||||||
public void SetRawValue(VirtualKey vkCode, int value)
|
public void SetRawValue(VirtualKey vkCode, int value)
|
||||||
=> this.SetRawValue((int)vkCode, value);
|
=> this.SetRawValue((int)vkCode, value);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="vkCode">Virtual key code.</param>
|
|
||||||
/// <returns>If the code is valid.</returns>
|
|
||||||
public bool IsVirtualKeyValid(int vkCode)
|
public bool IsVirtualKeyValid(int vkCode)
|
||||||
=> this.ConvertVirtualKey(vkCode) != 0;
|
=> this.ConvertVirtualKey(vkCode) != 0;
|
||||||
|
|
||||||
/// <inheritdoc cref="IsVirtualKeyValid(int)"/>
|
/// <inheritdoc/>
|
||||||
public bool IsVirtualKeyValid(VirtualKey vkCode)
|
public bool IsVirtualKeyValid(VirtualKey vkCode)
|
||||||
=> this.IsVirtualKeyValid((int)vkCode);
|
=> this.IsVirtualKeyValid((int)vkCode);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets an array of virtual keys the game considers valid input.
|
public IEnumerable<VirtualKey> GetValidVirtualKeys()
|
||||||
/// </summary>
|
=> this.validVirtualKeyCache ??= Enum.GetValues<VirtualKey>().Where(this.IsVirtualKeyValid).ToArray();
|
||||||
/// <returns>An array of valid virtual keys.</returns>
|
|
||||||
public VirtualKey[] GetValidVirtualKeys()
|
|
||||||
=> this.validVirtualKeyCache ??= Enum.GetValues<VirtualKey>().Where(vk => this.IsVirtualKeyValid(vk)).ToArray();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Clears the pressed state for all keys.
|
|
||||||
/// </summary>
|
|
||||||
public void ClearAll()
|
public void ClearAll()
|
||||||
{
|
{
|
||||||
foreach (var vk in this.GetValidVirtualKeys())
|
foreach (var vk in this.GetValidVirtualKeys())
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ namespace Dalamud.Game.ClientState.Objects;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IObjectTable>]
|
[ResolveVia<IObjectTable>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public sealed partial class ObjectTable : IServiceType, IObjectTable
|
internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||||
{
|
{
|
||||||
private const int ObjectTableLength = 596;
|
private const int ObjectTableLength = 599;
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ public sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the currently spawned FFXIV game objects.
|
/// This collection represents the currently spawned FFXIV game objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class ObjectTable
|
internal sealed partial class ObjectTable
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int IReadOnlyCollection<GameObject>.Count => this.Length;
|
int IReadOnlyCollection<GameObject>.Count => this.Length;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
@ -25,5 +23,5 @@ public unsafe class BattleNpc : BattleChara
|
||||||
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
|
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override ulong TargetObjectId => this.Struct->Character.TargetObjectID;
|
public override ulong TargetObjectId => this.Struct->Character.TargetId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
using Dalamud.Game.ClientState.Resolvers;
|
||||||
|
|
||||||
|
|
@ -33,5 +31,5 @@ public unsafe class PlayerCharacter : BattleChara
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the target actor ID of the PlayerCharacter.
|
/// Gets the target actor ID of the PlayerCharacter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override ulong TargetObjectId => this.Struct->Character.PlayerTargetObjectID;
|
public override ulong TargetObjectId => this.Struct->Character.LookTargetId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.Objects;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<ITargetManager>]
|
[ResolveVia<ITargetManager>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public sealed unsafe class TargetManager : IServiceType, ITargetManager
|
internal sealed unsafe class TargetManager : IServiceType, ITargetManager
|
||||||
{
|
{
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
|
|
@ -39,136 +39,50 @@ public sealed unsafe class TargetManager : IServiceType, ITargetManager
|
||||||
public GameObject? Target
|
public GameObject? Target
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target);
|
||||||
set => this.SetTarget(value);
|
set => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameObject? MouseOverTarget
|
public GameObject? MouseOverTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
||||||
set => this.SetMouseOverTarget(value);
|
set => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameObject? FocusTarget
|
public GameObject? FocusTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
|
||||||
set => this.SetFocusTarget(value);
|
set => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameObject? PreviousTarget
|
public GameObject? PreviousTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
||||||
set => this.SetPreviousTarget(value);
|
set => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameObject? SoftTarget
|
public GameObject? SoftTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->SoftTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->SoftTarget);
|
||||||
set => this.SetSoftTarget(value);
|
set => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public GameObject? GPoseTarget
|
||||||
|
{
|
||||||
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget);
|
||||||
|
set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public GameObject? MouseOverNameplateTarget
|
||||||
|
{
|
||||||
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverNameplateTarget);
|
||||||
|
set => Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address;
|
private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the current target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actor">Actor to target.</param>
|
|
||||||
[Obsolete("Use Target Property", false)]
|
|
||||||
public void SetTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the mouseover target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actor">Actor to target.</param>
|
|
||||||
[Obsolete("Use MouseOverTarget Property", false)]
|
|
||||||
public void SetMouseOverTarget(GameObject? actor) => this.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the focus target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actor">Actor to target.</param>
|
|
||||||
[Obsolete("Use FocusTarget Property", false)]
|
|
||||||
public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(actor?.Address ?? IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the previous target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actor">Actor to target.</param>
|
|
||||||
[Obsolete("Use PreviousTarget Property", false)]
|
|
||||||
public void SetPreviousTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the soft target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actor">Actor to target.</param>
|
|
||||||
[Obsolete("Use SoftTarget Property", false)]
|
|
||||||
public void SetSoftTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the current target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
|
||||||
[Obsolete("Use Target Property", false)]
|
|
||||||
public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the mouseover target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
|
||||||
[Obsolete("Use MouseOverTarget Property", false)]
|
|
||||||
public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the focus target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
|
||||||
[Obsolete("Use FocusTarget Property", false)]
|
|
||||||
public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the previous target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
|
||||||
[Obsolete("Use PreviousTarget Property", false)]
|
|
||||||
public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the soft target.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorAddress">Actor (address) to target.</param>
|
|
||||||
[Obsolete("Use SoftTarget Property", false)]
|
|
||||||
public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the current target.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use Target Property", false)]
|
|
||||||
public void ClearTarget() => this.SetTarget(IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the mouseover target.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use MouseOverTarget Property", false)]
|
|
||||||
public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the focus target.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use FocusTarget Property", false)]
|
|
||||||
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the previous target.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use PreviousTarget Property", false)]
|
|
||||||
public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the soft target.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use SoftTarget Property", false)]
|
|
||||||
public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Statuses;
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
|
@ -57,8 +58,22 @@ public unsafe class BattleChara : Character
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total casting time of the spell being cast by the chara.
|
/// Gets the total casting time of the spell being cast by the chara.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This can only be a portion of the total cast for some actions.
|
||||||
|
/// Use AdjustedTotalCastTime if you always need the total cast time.
|
||||||
|
/// </remarks>
|
||||||
|
[Api10ToDo("Rename so it is not confused with AdjustedTotalCastTime")]
|
||||||
public float TotalCastTime => this.Struct->GetCastInfo->TotalCastTime;
|
public float TotalCastTime => this.Struct->GetCastInfo->TotalCastTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="TotalCastTime"/> plus any adjustments from the game, such as Action offset 2B. Used for display purposes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is the actual total cast time for all actions.
|
||||||
|
/// </remarks>
|
||||||
|
[Api10ToDo("Rename so it is not confused with TotalCastTime")]
|
||||||
|
public float AdjustedTotalCastTime => this.Struct->GetCastInfo->AdjustedTotalCastTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the underlying structure.
|
/// Gets the underlying structure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.ClientState.Resolvers;
|
using Dalamud.Game.ClientState.Resolvers;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
@ -63,6 +61,11 @@ public unsafe class Character : GameObject
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint MaxCp => this.Struct->CharacterData.MaxCraftingPoints;
|
public uint MaxCp => this.Struct->CharacterData.MaxCraftingPoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the shield percentage of this Chara.
|
||||||
|
/// </summary>
|
||||||
|
public byte ShieldPercentage => this.Struct->CharacterData.ShieldValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ClassJob of this Chara.
|
/// Gets the ClassJob of this Chara.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -87,7 +90,7 @@ public unsafe class Character : GameObject
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the target object ID of the character.
|
/// Gets the target object ID of the character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override ulong TargetObjectId => this.Struct->TargetObjectID;
|
public override ulong TargetObjectId => this.Struct->TargetId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name ID of the character.
|
/// Gets the name ID of the character.
|
||||||
|
|
@ -115,5 +118,6 @@ public unsafe class Character : GameObject
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the underlying structure.
|
/// Gets the underlying structure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
protected internal new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct =>
|
||||||
|
(FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Dalamud.Game.ClientState.Party;
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IPartyList>]
|
[ResolveVia<IPartyList>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
public sealed unsafe partial class PartyList : IServiceType, IPartyList
|
internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||||
{
|
{
|
||||||
private const int GroupLength = 8;
|
private const int GroupLength = 8;
|
||||||
private const int AllianceLength = 20;
|
private const int AllianceLength = 20;
|
||||||
|
|
@ -130,7 +130,7 @@ public sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the party members present in your party or alliance.
|
/// This collection represents the party members present in your party or alliance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class PartyList
|
internal sealed partial class PartyList
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
int IReadOnlyCollection<PartyMember>.Count => this.Length;
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ namespace Dalamud.Game.ClientState.Statuses;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed unsafe partial class StatusList
|
public sealed unsafe partial class StatusList
|
||||||
{
|
{
|
||||||
private const int StatusListLength = 30;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
/// Initializes a new instance of the <see cref="StatusList"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -38,7 +36,7 @@ public sealed unsafe partial class StatusList
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of status effect slots the actor has.
|
/// Gets the amount of status effect slots the actor has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Length => StatusListLength;
|
public int Length => Struct->NumValidStatuses;
|
||||||
|
|
||||||
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
|
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
|
||||||
|
|
||||||
|
|
@ -53,7 +51,7 @@ public sealed unsafe partial class StatusList
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (index < 0 || index > StatusListLength)
|
if (index < 0 || index > this.Length)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var addr = this.GetStatusAddress(index);
|
var addr = this.GetStatusAddress(index);
|
||||||
|
|
@ -107,7 +105,7 @@ public sealed unsafe partial class StatusList
|
||||||
/// <returns>The memory address of the party member.</returns>
|
/// <returns>The memory address of the party member.</returns>
|
||||||
public IntPtr GetStatusAddress(int index)
|
public IntPtr GetStatusAddress(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= StatusListLength)
|
if (index < 0 || index >= this.Length)
|
||||||
return IntPtr.Zero;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return (IntPtr)(this.Struct->Status + (index * StatusSize));
|
return (IntPtr)(this.Struct->Status + (index * StatusSize));
|
||||||
|
|
@ -134,7 +132,7 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<Status> GetEnumerator()
|
public IEnumerator<Status> GetEnumerator()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < StatusListLength; i++)
|
for (var i = 0; i < this.Length; i++)
|
||||||
{
|
{
|
||||||
var status = this[i];
|
var status = this[i];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ public sealed class CommandInfo
|
||||||
public CommandInfo(HandlerDelegate handler)
|
public CommandInfo(HandlerDelegate handler)
|
||||||
{
|
{
|
||||||
this.Handler = handler;
|
this.Handler = handler;
|
||||||
this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
@ -9,22 +8,21 @@ using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Command;
|
namespace Dalamud.Game.Command;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class manages registered in-game slash commands.
|
/// This class manages registered in-game slash commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
#pragma warning disable SA1015
|
internal sealed class CommandManager : IInternalDisposableService, ICommandManager
|
||||||
[ResolveVia<ICommandManager>]
|
|
||||||
#pragma warning restore SA1015
|
|
||||||
public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
|
||||||
{
|
{
|
||||||
|
private static readonly ModuleLog Log = new("Command");
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, CommandInfo> commandMap = new();
|
private readonly ConcurrentDictionary<string, CommandInfo> commandMap = new();
|
||||||
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||||
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||||
|
|
@ -37,15 +35,15 @@ public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
||||||
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private CommandManager(DalamudStartInfo startInfo)
|
private CommandManager(Dalamud dalamud)
|
||||||
{
|
{
|
||||||
this.currentLangCommandRegex = startInfo.Language switch
|
this.currentLangCommandRegex = (ClientLanguage)dalamud.StartInfo.Language switch
|
||||||
{
|
{
|
||||||
ClientLanguage.Japanese => this.commandRegexJp,
|
ClientLanguage.Japanese => this.commandRegexJp,
|
||||||
ClientLanguage.English => this.commandRegexEn,
|
ClientLanguage.English => this.commandRegexEn,
|
||||||
ClientLanguage.German => this.commandRegexDe,
|
ClientLanguage.German => this.commandRegexDe,
|
||||||
ClientLanguage.French => this.commandRegexFr,
|
ClientLanguage.French => this.commandRegexFr,
|
||||||
_ => this.currentLangCommandRegex,
|
_ => this.commandRegexEn,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||||
|
|
@ -84,7 +82,7 @@ public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
||||||
// => command: 0-12 (12 chars)
|
// => command: 0-12 (12 chars)
|
||||||
// => argument: 13-17 (4 chars)
|
// => argument: 13-17 (4 chars)
|
||||||
// => content.IndexOf(' ') == 12
|
// => content.IndexOf(' ') == 12
|
||||||
command = content.Substring(0, separatorPosition);
|
command = content[..separatorPosition];
|
||||||
|
|
||||||
var argStart = separatorPosition + 1;
|
var argStart = separatorPosition + 1;
|
||||||
argument = content[argStart..];
|
argument = content[argStart..];
|
||||||
|
|
@ -132,7 +130,7 @@ public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IDisposable.Dispose()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
|
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
|
||||||
}
|
}
|
||||||
|
|
@ -162,3 +160,93 @@ public sealed class CommandManager : IServiceType, IDisposable, ICommandManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a AddonLifecycle service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<ICommandManager>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager
|
||||||
|
{
|
||||||
|
private static readonly ModuleLog Log = new("Command");
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly CommandManager commandManagerService = Service<CommandManager>.Get();
|
||||||
|
|
||||||
|
private readonly List<string> pluginRegisteredCommands = new();
|
||||||
|
private readonly LocalPlugin pluginInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CommandManagerPluginScoped"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="localPlugin">Info for the plugin that requests this service.</param>
|
||||||
|
public CommandManagerPluginScoped(LocalPlugin localPlugin)
|
||||||
|
{
|
||||||
|
this.pluginInfo = localPlugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ReadOnlyDictionary<string, CommandInfo> Commands => this.commandManagerService.Commands;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
foreach (var command in this.pluginRegisteredCommands)
|
||||||
|
{
|
||||||
|
this.commandManagerService.RemoveHandler(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pluginRegisteredCommands.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool ProcessCommand(string content)
|
||||||
|
=> this.commandManagerService.ProcessCommand(content);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void DispatchCommand(string command, string argument, CommandInfo info)
|
||||||
|
=> this.commandManagerService.DispatchCommand(command, argument, info);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool AddHandler(string command, CommandInfo info)
|
||||||
|
{
|
||||||
|
if (!this.pluginRegisteredCommands.Contains(command))
|
||||||
|
{
|
||||||
|
info.LoaderAssemblyName = this.pluginInfo.InternalName;
|
||||||
|
if (this.commandManagerService.AddHandler(command, info))
|
||||||
|
{
|
||||||
|
this.pluginRegisteredCommands.Add(command);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error($"Command {command} is already registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool RemoveHandler(string command)
|
||||||
|
{
|
||||||
|
if (this.pluginRegisteredCommands.Contains(command))
|
||||||
|
{
|
||||||
|
if (this.commandManagerService.RemoveHandler(command))
|
||||||
|
{
|
||||||
|
this.pluginRegisteredCommands.Remove(command);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error($"Command {command} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
|
@ -13,47 +14,86 @@ namespace Dalamud.Game.Config;
|
||||||
/// This class represents the game's configuration.
|
/// This class represents the game's configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[PluginInterface]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
[ServiceManager.EarlyLoadedService]
|
internal sealed class GameConfig : IInternalDisposableService, IGameConfig
|
||||||
#pragma warning disable SA1015
|
|
||||||
[ResolveVia<IGameConfig>]
|
|
||||||
#pragma warning restore SA1015
|
|
||||||
public sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
|
||||||
{
|
{
|
||||||
|
private readonly TaskCompletionSource tcsInitialization = new();
|
||||||
|
private readonly TaskCompletionSource<GameConfigSection> tcsSystem = new();
|
||||||
|
private readonly TaskCompletionSource<GameConfigSection> tcsUiConfig = new();
|
||||||
|
private readonly TaskCompletionSource<GameConfigSection> tcsUiControl = new();
|
||||||
|
|
||||||
private readonly GameConfigAddressResolver address = new();
|
private readonly GameConfigAddressResolver address = new();
|
||||||
private Hook<ConfigChangeDelegate>? configChangeHook;
|
private Hook<ConfigChangeDelegate>? configChangeHook;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private unsafe GameConfig(Framework framework, SigScanner sigScanner)
|
private unsafe GameConfig(Framework framework, TargetSigScanner sigScanner)
|
||||||
{
|
{
|
||||||
framework.RunOnTick(() =>
|
framework.RunOnTick(() =>
|
||||||
{
|
{
|
||||||
Log.Verbose("[GameConfig] Initializing");
|
try
|
||||||
var csFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
|
{
|
||||||
var commonConfig = &csFramework->SystemConfig.CommonSystemConfig;
|
Log.Verbose("[GameConfig] Initializing");
|
||||||
this.System = new GameConfigSection("System", framework, &commonConfig->ConfigBase);
|
var csFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
|
||||||
this.UiConfig = new GameConfigSection("UiConfig", framework, &commonConfig->UiConfig);
|
var commonConfig = &csFramework->SystemConfig.CommonSystemConfig;
|
||||||
this.UiControl = new GameConfigSection("UiControl", framework, () => this.UiConfig.TryGetBool("PadMode", out var padMode) && padMode ? &commonConfig->UiControlGamepadConfig : &commonConfig->UiControlConfig);
|
this.tcsSystem.SetResult(new("System", framework, &commonConfig->ConfigBase));
|
||||||
|
this.tcsUiConfig.SetResult(new("UiConfig", framework, &commonConfig->UiConfig));
|
||||||
this.address.Setup(sigScanner);
|
this.tcsUiControl.SetResult(
|
||||||
this.configChangeHook = Hook<ConfigChangeDelegate>.FromAddress(this.address.ConfigChangeAddress, this.OnConfigChanged);
|
new(
|
||||||
this.configChangeHook?.Enable();
|
"UiControl",
|
||||||
|
framework,
|
||||||
|
() => this.UiConfig.TryGetBool("PadMode", out var padMode) && padMode
|
||||||
|
? &commonConfig->UiControlGamepadConfig
|
||||||
|
: &commonConfig->UiControlConfig));
|
||||||
|
|
||||||
|
this.address.Setup(sigScanner);
|
||||||
|
this.configChangeHook = Hook<ConfigChangeDelegate>.FromAddress(
|
||||||
|
this.address.ConfigChangeAddress,
|
||||||
|
this.OnConfigChanged);
|
||||||
|
this.configChangeHook.Enable();
|
||||||
|
this.tcsInitialization.SetResult();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.tcsInitialization.SetExceptionIfIncomplete(ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe delegate nint ConfigChangeDelegate(ConfigBase* configBase, ConfigEntry* configEntry);
|
private unsafe delegate nint ConfigChangeDelegate(ConfigBase* configBase, ConfigEntry* configEntry);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<ConfigChangeEvent> Changed;
|
public event EventHandler<ConfigChangeEvent>? Changed;
|
||||||
|
|
||||||
|
#pragma warning disable 67
|
||||||
|
/// <summary>
|
||||||
|
/// Unused internally, used as a proxy for System.Changed via GameConfigPluginScoped
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ConfigChangeEvent>? SystemChanged;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
public GameConfigSection System { get; private set; }
|
/// Unused internally, used as a proxy for UiConfig.Changed via GameConfigPluginScoped
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ConfigChangeEvent>? UiConfigChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unused internally, used as a proxy for UiControl.Changed via GameConfigPluginScoped
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ConfigChangeEvent>? UiControlChanged;
|
||||||
|
#pragma warning restore 67
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a task representing the initialization state of this class.
|
||||||
|
/// </summary>
|
||||||
|
public Task InitializationTask => this.tcsInitialization.Task;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameConfigSection UiConfig { get; private set; }
|
public GameConfigSection System => this.tcsSystem.Task.Result;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameConfigSection UiControl { get; private set; }
|
public GameConfigSection UiConfig => this.tcsUiConfig.Task.Result;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public GameConfigSection UiControl => this.tcsUiControl.Task.Result;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool TryGet(SystemConfigOption option, out bool value) => this.System.TryGet(option.GetName(), out value);
|
public bool TryGet(SystemConfigOption option, out bool value) => this.System.TryGet(option.GetName(), out value);
|
||||||
|
|
@ -155,8 +195,13 @@ public sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
||||||
public void Set(UiControlOption option, string value) => this.UiControl.Set(option.GetName(), value);
|
public void Set(UiControlOption option, string value) => this.UiControl.Set(option.GetName(), value);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IDisposable.Dispose()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
|
var ode = new ObjectDisposedException(nameof(GameConfig));
|
||||||
|
this.tcsInitialization.SetExceptionIfIncomplete(ode);
|
||||||
|
this.tcsSystem.SetExceptionIfIncomplete(ode);
|
||||||
|
this.tcsUiConfig.SetExceptionIfIncomplete(ode);
|
||||||
|
this.tcsUiControl.SetExceptionIfIncomplete(ode);
|
||||||
this.configChangeHook?.Disable();
|
this.configChangeHook?.Disable();
|
||||||
this.configChangeHook?.Dispose();
|
this.configChangeHook?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -193,3 +238,219 @@ public sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a GameConfig service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IGameConfig>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class GameConfigPluginScoped : IInternalDisposableService, IGameConfig
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly GameConfig gameConfigService = Service<GameConfig>.Get();
|
||||||
|
|
||||||
|
private readonly Task initializationTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GameConfigPluginScoped"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal GameConfigPluginScoped()
|
||||||
|
{
|
||||||
|
this.gameConfigService.Changed += this.ConfigChangedForward;
|
||||||
|
this.initializationTask = this.gameConfigService.InitializationTask.ContinueWith(
|
||||||
|
r =>
|
||||||
|
{
|
||||||
|
if (!r.IsCompletedSuccessfully)
|
||||||
|
return r;
|
||||||
|
this.gameConfigService.System.Changed += this.SystemConfigChangedForward;
|
||||||
|
this.gameConfigService.UiConfig.Changed += this.UiConfigConfigChangedForward;
|
||||||
|
this.gameConfigService.UiControl.Changed += this.UiControlConfigChangedForward;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ConfigChangeEvent>? Changed;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ConfigChangeEvent>? SystemChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ConfigChangeEvent>? UiConfigChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ConfigChangeEvent>? UiControlChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public GameConfigSection System => this.gameConfigService.System;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public GameConfigSection UiConfig => this.gameConfigService.UiConfig;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public GameConfigSection UiControl => this.gameConfigService.UiControl;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.gameConfigService.Changed -= this.ConfigChangedForward;
|
||||||
|
this.initializationTask.ContinueWith(
|
||||||
|
r =>
|
||||||
|
{
|
||||||
|
if (!r.IsCompletedSuccessfully)
|
||||||
|
return;
|
||||||
|
this.gameConfigService.System.Changed -= this.SystemConfigChangedForward;
|
||||||
|
this.gameConfigService.UiConfig.Changed -= this.UiConfigConfigChangedForward;
|
||||||
|
this.gameConfigService.UiControl.Changed -= this.UiControlConfigChangedForward;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.Changed = null;
|
||||||
|
this.SystemChanged = null;
|
||||||
|
this.UiConfigChanged = null;
|
||||||
|
this.UiControlChanged = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(SystemConfigOption option, out bool value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(SystemConfigOption option, out uint value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(SystemConfigOption option, out float value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(SystemConfigOption option, out string value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(SystemConfigOption option, out UIntConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(SystemConfigOption option, out FloatConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(SystemConfigOption option, out StringConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiConfigOption option, out bool value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiConfigOption option, out uint value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiConfigOption option, out float value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiConfigOption option, out string value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiConfigOption option, out UIntConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiConfigOption option, out FloatConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiConfigOption option, out StringConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiControlOption option, out bool value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiControlOption option, out uint value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiControlOption option, out float value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiControlOption option, out string value)
|
||||||
|
=> this.gameConfigService.TryGet(option, out value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiControlOption option, out UIntConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiControlOption option, out FloatConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGet(UiControlOption option, out StringConfigProperties? properties)
|
||||||
|
=> this.gameConfigService.TryGet(option, out properties);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(SystemConfigOption option, bool value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(SystemConfigOption option, uint value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(SystemConfigOption option, float value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(SystemConfigOption option, string value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiConfigOption option, bool value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiConfigOption option, uint value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiConfigOption option, float value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiConfigOption option, string value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiControlOption option, bool value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiControlOption option, uint value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiControlOption option, float value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Set(UiControlOption option, string value)
|
||||||
|
=> this.gameConfigService.Set(option, value);
|
||||||
|
|
||||||
|
private void ConfigChangedForward(object sender, ConfigChangeEvent data) => this.Changed?.Invoke(sender, data);
|
||||||
|
|
||||||
|
private void SystemConfigChangedForward(object sender, ConfigChangeEvent data) => this.SystemChanged?.Invoke(sender, data);
|
||||||
|
|
||||||
|
private void UiConfigConfigChangedForward(object sender, ConfigChangeEvent data) => this.UiConfigChanged?.Invoke(sender, data);
|
||||||
|
|
||||||
|
private void UiControlConfigChangedForward(object sender, ConfigChangeEvent data) => this.UiControlChanged?.Invoke(sender, data);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Game config system address resolver.
|
/// Game config system address resolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GameConfigAddressResolver : BaseAddressResolver
|
internal sealed class GameConfigAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the method called when any config option is changed.
|
/// Gets the address of the method called when any config option is changed.
|
||||||
|
|
@ -11,7 +11,7 @@ public sealed class GameConfigAddressResolver : BaseAddressResolver
|
||||||
public nint ConfigChangeAddress { get; private set; }
|
public nint ConfigChangeAddress { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void Setup64Bit(SigScanner scanner)
|
protected override void Setup64Bit(ISigScanner scanner)
|
||||||
{
|
{
|
||||||
this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E");
|
this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
@ -18,11 +17,6 @@ public class GameConfigSection
|
||||||
private readonly ConcurrentDictionary<string, uint> indexMap = new();
|
private readonly ConcurrentDictionary<string, uint> indexMap = new();
|
||||||
private readonly ConcurrentDictionary<uint, object> enumMap = new();
|
private readonly ConcurrentDictionary<uint, object> enumMap = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event which is fired when a game config option is changed within the section.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ConfigChangeEvent> Changed;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameConfigSection"/> class.
|
/// Initializes a new instance of the <see cref="GameConfigSection"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -54,6 +48,11 @@ public class GameConfigSection
|
||||||
/// <returns>Pointer to unmanaged ConfigBase.</returns>
|
/// <returns>Pointer to unmanaged ConfigBase.</returns>
|
||||||
internal unsafe delegate ConfigBase* GetConfigBaseDelegate();
|
internal unsafe delegate ConfigBase* GetConfigBaseDelegate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event which is fired when a game config option is changed within the section.
|
||||||
|
/// </summary>
|
||||||
|
internal event EventHandler<ConfigChangeEvent>? Changed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of config entries contained within the section.
|
/// Gets the number of config entries contained within the section.
|
||||||
/// Some entries may be empty with no data.
|
/// Some entries may be empty with no data.
|
||||||
|
|
|
||||||
|
|
@ -3473,4 +3473,67 @@ public enum UiConfigOption
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GameConfigOption("ItemInventryStoreEnd", ConfigType.UInt)]
|
[GameConfigOption("ItemInventryStoreEnd", ConfigType.UInt)]
|
||||||
ItemInventryStoreEnd,
|
ItemInventryStoreEnd,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name HotbarXHBEditEnable.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("HotbarXHBEditEnable", ConfigType.UInt)]
|
||||||
|
HotbarXHBEditEnable,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name NamePlateDispJobIconInPublicParty.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("NamePlateDispJobIconInPublicParty", ConfigType.UInt)]
|
||||||
|
NamePlateDispJobIconInPublicParty,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name NamePlateDispJobIconInPublicOther.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("NamePlateDispJobIconInPublicOther", ConfigType.UInt)]
|
||||||
|
NamePlateDispJobIconInPublicOther,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name NamePlateDispJobIconInInstanceParty.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("NamePlateDispJobIconInInstanceParty", ConfigType.UInt)]
|
||||||
|
NamePlateDispJobIconInInstanceParty,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name NamePlateDispJobIconInInstanceOther.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("NamePlateDispJobIconInInstanceOther", ConfigType.UInt)]
|
||||||
|
NamePlateDispJobIconInInstanceOther,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name CCProgressAllyFixLeftSide.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("CCProgressAllyFixLeftSide", ConfigType.UInt)]
|
||||||
|
CCProgressAllyFixLeftSide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name CCMapAllyFixLeftSide.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("CCMapAllyFixLeftSide", ConfigType.UInt)]
|
||||||
|
CCMapAllyFixLeftSide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name DispCCCountDown.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("DispCCCountDown", ConfigType.UInt)]
|
||||||
|
DispCCCountDown,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System option with the internal name TelepoCategoryType.
|
||||||
|
/// This option is a UInt.
|
||||||
|
/// </summary>
|
||||||
|
[GameConfigOption("TelepoCategoryType", ConfigType.UInt)]
|
||||||
|
TelepoCategoryType,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,19 @@
|
||||||
using System;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.DutyState;
|
namespace Dalamud.Game.DutyState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents the state of the currently occupied duty.
|
/// This class represents the state of the currently occupied duty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
#pragma warning disable SA1015
|
internal unsafe class DutyState : IInternalDisposableService, IDutyState
|
||||||
[ResolveVia<IDutyState>]
|
|
||||||
#pragma warning restore SA1015
|
|
||||||
public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
|
||||||
{
|
{
|
||||||
private readonly DutyStateAddressResolver address;
|
private readonly DutyStateAddressResolver address;
|
||||||
private readonly Hook<SetupContentDirectNetworkMessageDelegate> contentDirectorNetworkMessageHook;
|
private readonly Hook<SetupContentDirectNetworkMessageDelegate> contentDirectorNetworkMessageHook;
|
||||||
|
|
@ -34,7 +28,7 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
private readonly ClientState.ClientState clientState = Service<ClientState.ClientState>.Get();
|
private readonly ClientState.ClientState clientState = Service<ClientState.ClientState>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private DutyState(SigScanner sigScanner)
|
private DutyState(TargetSigScanner sigScanner)
|
||||||
{
|
{
|
||||||
this.address = new DutyStateAddressResolver();
|
this.address = new DutyStateAddressResolver();
|
||||||
this.address.Setup(sigScanner);
|
this.address.Setup(sigScanner);
|
||||||
|
|
@ -43,22 +37,24 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
|
|
||||||
this.framework.Update += this.FrameworkOnUpdateEvent;
|
this.framework.Update += this.FrameworkOnUpdateEvent;
|
||||||
this.clientState.TerritoryChanged += this.TerritoryOnChangedEvent;
|
this.clientState.TerritoryChanged += this.TerritoryOnChangedEvent;
|
||||||
|
|
||||||
|
this.contentDirectorNetworkMessageHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate byte SetupContentDirectNetworkMessageDelegate(IntPtr a1, IntPtr a2, ushort* a3);
|
private delegate byte SetupContentDirectNetworkMessageDelegate(IntPtr a1, IntPtr a2, ushort* a3);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<ushort> DutyStarted;
|
public event EventHandler<ushort>? DutyStarted;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<ushort> DutyWiped;
|
public event EventHandler<ushort>? DutyWiped;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<ushort> DutyRecommenced;
|
public event EventHandler<ushort>? DutyRecommenced;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler<ushort> DutyCompleted;
|
public event EventHandler<ushort>? DutyCompleted;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsDutyStarted { get; private set; }
|
public bool IsDutyStarted { get; private set; }
|
||||||
|
|
@ -66,19 +62,13 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
private bool CompletedThisTerritory { get; set; }
|
private bool CompletedThisTerritory { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IDisposable.Dispose()
|
void IInternalDisposableService.DisposeService()
|
||||||
{
|
{
|
||||||
this.contentDirectorNetworkMessageHook.Dispose();
|
this.contentDirectorNetworkMessageHook.Dispose();
|
||||||
this.framework.Update -= this.FrameworkOnUpdateEvent;
|
this.framework.Update -= this.FrameworkOnUpdateEvent;
|
||||||
this.clientState.TerritoryChanged -= this.TerritoryOnChangedEvent;
|
this.clientState.TerritoryChanged -= this.TerritoryOnChangedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction()
|
|
||||||
{
|
|
||||||
this.contentDirectorNetworkMessageHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte ContentDirectorNetworkMessageDetour(IntPtr a1, IntPtr a2, ushort* a3)
|
private byte ContentDirectorNetworkMessageDetour(IntPtr a1, IntPtr a2, ushort* a3)
|
||||||
{
|
{
|
||||||
var category = *a3;
|
var category = *a3;
|
||||||
|
|
@ -92,33 +82,33 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
// Duty Commenced
|
// Duty Commenced
|
||||||
case 0x4000_0001:
|
case 0x4000_0001:
|
||||||
this.IsDutyStarted = true;
|
this.IsDutyStarted = true;
|
||||||
this.DutyStarted.InvokeSafely(this, this.clientState.TerritoryType);
|
this.DutyStarted?.Invoke(this, this.clientState.TerritoryType);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Party Wipe
|
// Party Wipe
|
||||||
case 0x4000_0005:
|
case 0x4000_0005:
|
||||||
this.IsDutyStarted = false;
|
this.IsDutyStarted = false;
|
||||||
this.DutyWiped.InvokeSafely(this, this.clientState.TerritoryType);
|
this.DutyWiped?.Invoke(this, this.clientState.TerritoryType);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Duty Recommence
|
// Duty Recommence
|
||||||
case 0x4000_0006:
|
case 0x4000_0006:
|
||||||
this.IsDutyStarted = true;
|
this.IsDutyStarted = true;
|
||||||
this.DutyRecommenced.InvokeSafely(this, this.clientState.TerritoryType);
|
this.DutyRecommenced?.Invoke(this, this.clientState.TerritoryType);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Duty Completed Flytext Shown
|
// Duty Completed Flytext Shown
|
||||||
case 0x4000_0002 when !this.CompletedThisTerritory:
|
case 0x4000_0002 when !this.CompletedThisTerritory:
|
||||||
this.IsDutyStarted = false;
|
this.IsDutyStarted = false;
|
||||||
this.CompletedThisTerritory = true;
|
this.CompletedThisTerritory = true;
|
||||||
this.DutyCompleted.InvokeSafely(this, this.clientState.TerritoryType);
|
this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Duty Completed
|
// Duty Completed
|
||||||
case 0x4000_0003 when !this.CompletedThisTerritory:
|
case 0x4000_0003 when !this.CompletedThisTerritory:
|
||||||
this.IsDutyStarted = false;
|
this.IsDutyStarted = false;
|
||||||
this.CompletedThisTerritory = true;
|
this.CompletedThisTerritory = true;
|
||||||
this.DutyCompleted.InvokeSafely(this, this.clientState.TerritoryType);
|
this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +116,7 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
return this.contentDirectorNetworkMessageHook.Original(a1, a2, a3);
|
return this.contentDirectorNetworkMessageHook.Original(a1, a2, a3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TerritoryOnChangedEvent(object? sender, ushort e)
|
private void TerritoryOnChangedEvent(ushort territoryId)
|
||||||
{
|
{
|
||||||
if (this.IsDutyStarted)
|
if (this.IsDutyStarted)
|
||||||
{
|
{
|
||||||
|
|
@ -141,7 +131,7 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
/// Joining a duty in progress, or disconnecting and reconnecting will cause the player to miss the event.
|
/// Joining a duty in progress, or disconnecting and reconnecting will cause the player to miss the event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="framework1">Framework reference.</param>
|
/// <param name="framework1">Framework reference.</param>
|
||||||
private void FrameworkOnUpdateEvent(Framework framework1)
|
private void FrameworkOnUpdateEvent(IFramework framework1)
|
||||||
{
|
{
|
||||||
// If the duty hasn't been started, and has not been completed yet this territory
|
// If the duty hasn't been started, and has not been completed yet this territory
|
||||||
if (!this.IsDutyStarted && !this.CompletedThisTerritory)
|
if (!this.IsDutyStarted && !this.CompletedThisTerritory)
|
||||||
|
|
@ -161,11 +151,73 @@ public unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsBoundByDuty()
|
private bool IsBoundByDuty()
|
||||||
|
=> this.condition.Any(ConditionFlag.BoundByDuty,
|
||||||
|
ConditionFlag.BoundByDuty56,
|
||||||
|
ConditionFlag.BoundByDuty95);
|
||||||
|
|
||||||
|
private bool IsInCombat()
|
||||||
|
=> this.condition.Any(ConditionFlag.InCombat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin scoped version of DutyState.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[InterfaceVersion("1.0")]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IDutyState>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class DutyStatePluginScoped : IInternalDisposableService, IDutyState
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DutyState dutyStateService = Service<DutyState>.Get();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DutyStatePluginScoped"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal DutyStatePluginScoped()
|
||||||
{
|
{
|
||||||
return this.condition[ConditionFlag.BoundByDuty] ||
|
this.dutyStateService.DutyStarted += this.DutyStartedForward;
|
||||||
this.condition[ConditionFlag.BoundByDuty56] ||
|
this.dutyStateService.DutyWiped += this.DutyWipedForward;
|
||||||
this.condition[ConditionFlag.BoundByDuty95];
|
this.dutyStateService.DutyRecommenced += this.DutyRecommencedForward;
|
||||||
|
this.dutyStateService.DutyCompleted += this.DutyCompletedForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsInCombat() => this.condition[ConditionFlag.InCombat];
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ushort>? DutyStarted;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ushort>? DutyWiped;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ushort>? DutyRecommenced;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<ushort>? DutyCompleted;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsDutyStarted => this.dutyStateService.IsDutyStarted;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.dutyStateService.DutyStarted -= this.DutyStartedForward;
|
||||||
|
this.dutyStateService.DutyWiped -= this.DutyWipedForward;
|
||||||
|
this.dutyStateService.DutyRecommenced -= this.DutyRecommencedForward;
|
||||||
|
this.dutyStateService.DutyCompleted -= this.DutyCompletedForward;
|
||||||
|
|
||||||
|
this.DutyStarted = null;
|
||||||
|
this.DutyWiped = null;
|
||||||
|
this.DutyRecommenced = null;
|
||||||
|
this.DutyCompleted = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DutyStartedForward(object sender, ushort territoryId) => this.DutyStarted?.Invoke(sender, territoryId);
|
||||||
|
|
||||||
|
private void DutyWipedForward(object sender, ushort territoryId) => this.DutyWiped?.Invoke(sender, territoryId);
|
||||||
|
|
||||||
|
private void DutyRecommencedForward(object sender, ushort territoryId) => this.DutyRecommenced?.Invoke(sender, territoryId);
|
||||||
|
|
||||||
|
private void DutyCompletedForward(object sender, ushort territoryId) => this.DutyCompleted?.Invoke(sender, territoryId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.DutyState;
|
namespace Dalamud.Game.DutyState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duty state memory address resolver.
|
/// Duty state memory address resolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DutyStateAddressResolver : BaseAddressResolver
|
internal class DutyStateAddressResolver : BaseAddressResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the address of the method which is called when the client receives a content director update.
|
/// Gets the address of the method which is called when the client receives a content director update.
|
||||||
|
|
@ -16,7 +14,7 @@ public class DutyStateAddressResolver : BaseAddressResolver
|
||||||
/// Scan for and setup any configured address pointers.
|
/// Scan for and setup any configured address pointers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
protected override void Setup64Bit(ISigScanner sig)
|
||||||
{
|
{
|
||||||
this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8B D9 49 8B F8 41 0F B7 08");
|
this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8B D9 49 8B F8 41 0F B7 08");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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