Compare commits

...

87 commits

Author SHA1 Message Date
github-actions[bot]
3212435aa9 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-10 18:35:36 +00:00
goaaats
a39763f161 Mark preset dirty when disabling clickthrough for a window
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 2s
2025-12-10 18:33:37 +01:00
goaaats
201c9cfcf2 Use game window to calculate offsets in fallback mouse position code 2025-12-10 18:13:52 +01:00
goat
e8485dee25
Merge pull request #2492 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-12-07 22:22:08 +01:00
github-actions[bot]
0072f49fe8 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-12-07 21:13:12 +00:00
goat
bcb8094c2d
Merge pull request #2497 from Haselnussbomber/update-fontawesome
[API14] Update Font Awesome to 7.1.0
2025-12-07 17:06:43 +01:00
Haselnussbomber
624191d1e0
Update DalamudAssetPath to FontAwesome710FreeSolid.otf 2025-12-07 16:45:40 +01:00
Haselnussbomber
c254c8600e
Update Font Awesome to 7.1.0 2025-12-07 16:31:03 +01:00
goat
61376fe84e
Merge pull request #2496 from Haselnussbomber/remove-targets
[API14] Remove targets
2025-12-07 16:25:32 +01:00
Haselnussbomber
2f5f52b572
Forgot to remove this too 2025-12-07 16:23:13 +01:00
Haselnussbomber
7199bfb0a9
Remove targets 2025-12-07 16:20:55 +01:00
goat
abcddde591
Merge pull request #2494 from Haselnussbomber/remove-obsoleted-sestring-casts
[API14] Remove obsolete casts from Lumina.Text.SeString
2025-12-07 15:59:33 +01:00
goat
2a99108eb1
Merge pull request #2495 from Haselnussbomber/expose-settings-uibuilder
[API14] Add PluginUISoundEffectsEnabled to UiBuilder
2025-12-07 15:58:58 +01:00
Haselnussbomber
8a5f1fd96d
Add PluginUISoundEffectsEnabled to UiBuilder 2025-12-07 15:55:43 +01:00
Haselnussbomber
d4fe523d73
Clean up some warnings 2025-12-07 15:38:10 +01:00
Haselnussbomber
9e5723359a
Remove obsolete casts from Lumina.Text.SeString 2025-12-07 15:35:38 +01:00
goaaats
9fd59f736d Merge from master
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-06 18:48:31 +01:00
goaaats
3d29157391 Revert "Add git status checks to workflow to see what's dirty"
This reverts commit a36e11574b.
2025-12-06 18:38:44 +01:00
goaaats
b2d9480f9f Submit nuke schema 2025-12-06 18:38:44 +01:00
goat
61123ce573
Merge pull request #2485 from Haselnussbomber/update-cswin32
[API14] Update Microsoft.Windows.CsWin32
2025-12-06 18:35:41 +01:00
goat
88fc933e3f
Merge pull request #2491 from Haselnussbomber/drawstate-fontptr
[API14] Use ImFontPtr in SeStringDrawState
2025-12-06 18:33:09 +01:00
Haselnussbomber
1d1db04f04
Use ImFontPtr in SeStringDrawState 2025-12-06 16:09:42 +01:00
goat
a36e11574b
Add git status checks to workflow to see what's dirty
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-06 01:10:00 +01:00
Haselnussbomber
d94cacaac3
Disable SafeHandles 2025-12-05 19:10:31 +01:00
Haselnussbomber
7cf20fe102
Update Microsoft.Windows.CsWin32 2025-12-05 18:58:10 +01:00
goat
98a4c0d4fd
Merge pull request #2479 from goatcorp/api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
[api14] Rollup changes from master
2025-12-05 18:20:01 +01:00
goat
f85ef995e3
Merge pull request #2486 from Haselnussbomber/update-terrafx
[API14] Update TerraFX.Interop.Windows
2025-12-05 18:19:42 +01:00
goat
e7d4786a1f
Oops, wrong version 2025-12-05 18:18:57 +01:00
goat
4d949e4a07
Merge branch 'api14' into update-terrafx 2025-12-05 18:17:52 +01:00
goat
68ca60fa8c
Merge pull request #2484 from Haselnussbomber/sharpdx-removal
[API14] Remove SharpDX
2025-12-05 18:16:00 +01:00
goat
411067219e
Merge pull request #2487 from Haselnussbomber/update-nuke
[API14] Update Nuke
2025-12-05 18:14:22 +01:00
Haselnussbomber
fc983458fa
Update Nuke 2025-12-05 01:44:18 +01:00
Haselnussbomber
ddc3113244
Update TerraFX.Interop.Windows 2025-12-05 01:34:47 +01:00
Haselnussbomber
da7be64fdf
Remove SharpDX 2025-12-04 23:34:11 +01:00
Haselnussbomber
0112e17fdb
Replace internal SharpDX usage with TerraFX 2025-12-04 23:33:48 +01:00
github-actions[bot]
6f8e33a39c Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-04 22:03:13 +00:00
goat
5bb212bfaa
Merge pull request #2424 from Haselnussbomber/fix-service-namespaces
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[API14] Fix services using wrong namespaces
2025-12-04 00:56:10 +01:00
goat
a917ebd856
Merge pull request #2468 from KazWolfe/rpc-unix
feat: Add unix sockets
2025-12-04 00:48:23 +01:00
Kaz Wolfe
874745651b
feat: Add PID, process time, rename ClientIdentifer to ClientState 2025-11-29 21:12:08 -08:00
Kaz Wolfe
ead1c705a4
fix: Route URIs to the specified InternalName 2025-11-29 17:07:51 -08:00
goat
a31dda7865
Merge pull request #2475 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-11-29 19:39:18 +01:00
github-actions[bot]
d7e04ad4ff Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-29 18:23:24 +00:00
Haselnussbomber
c661faea6b
Fix services using wrong namespaces 2025-11-27 09:41:02 +01:00
goat
d4f1636dd2
Merge pull request #2473 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-11-26 22:43:25 +01:00
github-actions[bot]
196a5ef709 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-26 20:47:44 +00:00
goat
5e192ef39b
Merge pull request #2467 from goatcorp/api14-rollup
[api14] Rollup changes from master
2025-11-26 21:15:41 +01:00
github-actions[bot]
947518b3d6 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-26 20:10:02 +00:00
Kaz Wolfe
2cef75bbbe
feat: remove socket cleanup tasks 2025-11-26 11:56:30 -08:00
Kaz Wolfe
8ab7b59ae4
fix: Missing service types causing injection failures
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-25 10:17:12 -08:00
Kaz Wolfe
7b286c427c
chore: remove named pipe transport, use startinfo for pathing 2025-11-25 10:08:24 -08:00
Kaz Wolfe
0d8f577576
feat: add debug link handler as demo 2025-11-18 16:28:03 -08:00
Kaz Wolfe
01d8fc0c7e
fix: log tweaks
- also fix a boot failure
2025-11-18 15:57:37 -08:00
Kaz Wolfe
71927a8bf6
feat: Add unix sockets
- Unix sockets run parallel to Named Pipes
  - Named Pipes will only run on non-Wine
  - If the game crashes, the next run will clean up an orphaned socket.
- Restructure RPC to be a bit tidier
2025-11-18 15:20:22 -08:00
goaaats
6a69a6e197 Fix some warnings
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-11-18 00:58:08 +01:00
goaaats
cc91916574 Fix bad merge 2025-11-18 00:52:30 +01:00
goat
b7dda599fb
Merge pull request #2464 from goatcorp/api14-rollup
[api14] Rollup changes from master
2025-11-17 23:16:58 +01:00
github-actions[bot]
63b7ecf0d7 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-17 22:05:40 +00:00
goat
e4eca842d3
Merge pull request #2461 from goatcorp/api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
[api14] Rollup changes from master
2025-11-17 19:18:36 +01:00
github-actions[bot]
c79fa96505 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-17 17:43:53 +00:00
goat
ba0cf4c990
Merge pull request #2458 from Haselnussbomber/struct-enumerators
[API14] Use struct enumerators/types
2025-11-17 18:24:07 +01:00
goat
9a49a9588b
Merge pull request #2462 from KazWolfe/rpc
feat: Dalamud RPC service
2025-11-17 18:11:04 +01:00
Kaz Wolfe
19a3926051
Better hello message 2025-11-16 21:35:33 -08:00
Kaz Wolfe
4937a2f4bd
CR changes 2025-11-16 18:14:02 -08:00
Kaz Wolfe
78ed4a2b01
feat: Dalamud RPC service
A draft for a simple RPC service for Dalamud. Enables use of Dalamud URIs, to be added later.
2025-11-16 16:08:24 -08:00
goat
62b9c1f2a1
Merge pull request #2460 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-11-15 01:09:51 +01:00
github-actions[bot]
a2e923b051 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-15 00:09:30 +00:00
goat
de396e70f8
Merge pull request #2459 from goatcorp/api14-rollup
[api14] Rollup changes from master
2025-11-15 00:51:49 +01:00
github-actions[bot]
7a8f01f418 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-14 23:49:59 +00:00
Haselnussbomber
9d0879148c
Remove unused StatusEffect struct 2025-11-13 19:06:23 +01:00
Haselnussbomber
778c82fad2
Add struct enumerator to StatusList 2025-11-13 19:06:23 +01:00
Haselnussbomber
7f2ed9adb6
Convert Status to readonly struct and add interface 2025-11-13 19:06:23 +01:00
Haselnussbomber
53b94caeb7
Convert PartyMember to readonly struct 2025-11-13 19:06:18 +01:00
Haselnussbomber
d1dc81318a
Add struct enumerator to PartyList 2025-11-13 19:04:38 +01:00
Haselnussbomber
a48eead85e
Convert Fate to readonly struct 2025-11-13 19:04:35 +01:00
Haselnussbomber
d1bed3ebc5
Add struct enumerator to FateTable 2025-11-13 19:04:12 +01:00
Haselnussbomber
23e7c164d8
Convert BuddyMember to readonly struct 2025-11-13 19:04:11 +01:00
Haselnussbomber
8a9b47c7a4
Add struct enumerator to BuddyList 2025-11-13 19:03:56 +01:00
Haselnussbomber
520e3ea028
Convert AetheryteEntry to readonly struct 2025-11-13 19:03:53 +01:00
Haselnussbomber
dd70c5b8ee
Add struct enumerator to AetheryteList 2025-11-13 18:44:15 +01:00
Haselnussbomber
2b2f628096
Convert ObjectTable enumerator to struct 2025-11-13 18:44:14 +01:00
goaaats
6340afb692 Nuke schema, also remove analyzers from imgui testbed 2025-11-12 21:39:38 +01:00
goaaats
928fbba489 Remove Injector.Boot targets 2025-11-12 21:13:50 +01:00
goaaats
7bc921f543 No analyzers on nuke build 2025-11-12 21:09:21 +01:00
goaaats
a37a13e0ba Use .NET 10 in CI 2025-11-12 21:03:14 +01:00
goaaats
e0eff2fe74 Use standard apphost for Dalamud.Injector 2025-11-12 21:02:07 +01:00
goaaats
7d76d27555 Upgrade packages 2025-11-12 20:31:28 +01:00
goaaats
4e87b4b007 Retarget to .NET 10 2025-11-12 20:15:12 +01:00
97 changed files with 2575 additions and 1530 deletions

View file

@ -24,7 +24,7 @@ jobs:
uses: microsoft/setup-msbuild@v1.0.2
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '9.0.200'
dotnet-version: '10.0.100'
- name: Define VERSION
run: |
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)

View file

@ -1,19 +1,57 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"definitions": {
"build": {
"type": "object",
"Host": {
"type": "string",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"ExecutableTarget": {
"type": "string",
"enum": [
"CI",
"Clean",
"Compile",
"CompileCImGui",
"CompileCImGuizmo",
"CompileCImPlot",
"CompileDalamud",
"CompileDalamudBoot",
"CompileDalamudCrashHandler",
"CompileImGuiNatives",
"CompileInjector",
"Restore",
"SetCILogging",
"Test"
]
},
"Verbosity": {
"type": "string",
"description": "",
"enum": [
"Verbose",
"Normal",
"Minimal",
"Quiet"
]
},
"NukeBuild": {
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
@ -23,29 +61,8 @@
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"IsDocsBuild": {
"type": "boolean",
"description": "Whether we are building for documentation - emits generated files"
"$ref": "#/definitions/Host"
},
"NoLogo": {
"type": "boolean",
@ -74,65 +91,46 @@
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"CI",
"Clean",
"Compile",
"CompileCImGui",
"CompileCImGuizmo",
"CompileCImPlot",
"CompileDalamud",
"CompileDalamudBoot",
"CompileDalamudCrashHandler",
"CompileImGuiNatives",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
"SetCILogging",
"Test"
]
"$ref": "#/definitions/ExecutableTarget"
}
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"CI",
"Clean",
"Compile",
"CompileCImGui",
"CompileCImGuizmo",
"CompileCImPlot",
"CompileDalamud",
"CompileDalamudBoot",
"CompileDalamudCrashHandler",
"CompileImGuiNatives",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
"SetCILogging",
"Test"
]
"$ref": "#/definitions/ExecutableTarget"
}
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
"$ref": "#/definitions/Verbosity"
}
}
}
}
}
},
"allOf": [
{
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"IsDocsBuild": {
"type": "boolean",
"description": "Whether we are building for documentation - emits generated files"
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
}
}
},
{
"$ref": "#/definitions/NukeBuild"
}
]
}

View file

@ -108,6 +108,11 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
config.LogName = json.value("LogName", config.LogName);
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
if (json.contains("TempDirectory") && !json["TempDirectory"].is_null()) {
config.TempDirectory = json.value("TempDirectory", config.TempDirectory);
}
config.Language = json.value("Language", config.Language);
config.Platform = json.value("Platform", config.Platform);
config.GameVersion = json.value("GameVersion", config.GameVersion);

View file

@ -44,6 +44,7 @@ struct DalamudStartInfo {
std::string ConfigurationPath;
std::string LogPath;
std::string LogName;
std::string TempDirectory;
std::string PluginDirectory;
std::string AssetDirectory;
ClientLanguage Language = ClientLanguage::English;

View file

@ -124,6 +124,7 @@ static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstrin
args.emplace_back(L"--logname=\"" + unicode::convert<std::wstring>(g_startInfo.LogName) + L"\"");
args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert<std::wstring>(g_startInfo.PluginDirectory) + L"\"");
args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
args.emplace_back(L"--dalamud-temp-directory=\"" + unicode::convert<std::wstring>(g_startInfo.TempDirectory) + L"\"");
args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast<int>(g_startInfo.Language)));
args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs));
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler

View file

@ -34,6 +34,12 @@ public record DalamudStartInfo
/// </summary>
public string? ConfigurationPath { get; set; }
/// <summary>
/// Gets or sets the directory for temporary files. This directory needs to exist and be writable to the user.
/// It should also be predictable and easy for launchers to find.
/// </summary>
public string? TempDirectory { get; set; }
/// <summary>
/// Gets or sets the path of the log files.
/// </summary>

View file

@ -1,111 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{8874326B-E755-4D13-90B4-59AB263A3E6B}</ProjectGuid>
<RootNamespace>Dalamud_Injector_Boot</RootNamespace>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Platform>x64</Platform>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<TargetName>Dalamud.Injector</TargetName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<LinkIncremental>false</LinkIncremental>
<CharacterSet>Unicode</CharacterSet>
<OutDir>..\bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp23</LanguageStandard>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ProgramDatabaseFile>$(OutDir)$(TargetName).Boot.pdb</ProgramDatabaseFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>false</IntrinsicFunctions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>false</EnableCOMDATFolding>
<OptimizeReferences>false</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ItemGroup>
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
<Link>nethost.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Image Include="dalamud.ico" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\Dalamud.Boot\logging.cpp" />
<ClCompile Include="..\Dalamud.Boot\unicode.cpp" />
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\Dalamud.Boot\logging.h" />
<ClInclude Include="..\Dalamud.Boot\unicode.h" />
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
<Delete Files="$(OutDir)$(TargetName).lib" />
<Delete Files="$(OutDir)$(TargetName).exp" />
</Target>
</Project>

View file

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{4faac519-3a73-4b2b-96e7-fb597f02c0be}</UniqueIdentifier>
<Extensions>ico;rc</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<Image Include="dalamud.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Dalamud.Boot\logging.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Dalamud.Boot\unicode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Dalamud.Boot\logging.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Dalamud.Boot\unicode.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -1,48 +0,0 @@
#define WIN32_LEAN_AND_MEAN
#include <filesystem>
#include <Windows.h>
#include <shellapi.h>
#include "..\Dalamud.Boot\logging.h"
#include "..\lib\CoreCLR\CoreCLR.h"
#include "..\lib\CoreCLR\boot.h"
int wmain(int argc, wchar_t** argv)
{
// Take care: don't redirect stderr/out here, we need to write our pid to stdout for XL to read
//logging::start_file_logging("dalamud.injector.boot.log", false);
logging::I("Dalamud Injector, (c) 2021 XIVLauncher Contributors");
logging::I("Built at : " __DATE__ "@" __TIME__);
wchar_t _module_path[MAX_PATH];
GetModuleFileNameW(NULL, _module_path, sizeof _module_path / 2);
std::filesystem::path fs_module_path(_module_path);
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.runtimeconfig.json").c_str());
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.dll").c_str());
// =========================================================================== //
void* entrypoint_vfn;
const auto result = InitializeClrAndGetEntryPoint(
GetModuleHandleW(nullptr),
false,
runtimeconfig_path,
module_path,
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
L"Main",
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
&entrypoint_vfn);
if (FAILED(result))
return result;
typedef int (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
logging::I("Running Dalamud Injector...");
const auto ret = entrypoint_fn(argc, argv);
logging::I("Done!");
return ret;
}

View file

@ -1 +0,0 @@
#pragma once

View file

@ -1 +0,0 @@
MAINICON ICON "dalamud.ico"

View file

@ -13,12 +13,13 @@
</PropertyGroup>
<PropertyGroup Label="Output">
<OutputType>Library</OutputType>
<OutputType>Exe</OutputType>
<OutputPath>..\bin\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ApplicationIcon>dalamud.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Label="Documentation">

View file

@ -25,34 +25,20 @@ namespace Dalamud.Injector
/// <summary>
/// Entrypoint to the program.
/// </summary>
public sealed class EntryPoint
public sealed class Program
{
/// <summary>
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">char** string arguments.</param>
/// <returns>Return value (HRESULT).</returns>
public delegate int MainDelegate(int argc, IntPtr argvPtr);
/// <summary>
/// Start the Dalamud injector.
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">byte** string arguments.</param>
/// <param name="argsArray">Command line arguments.</param>
/// <returns>Return value (HRESULT).</returns>
public static int Main(int argc, IntPtr argvPtr)
public static int Main(string[] argsArray)
{
try
{
List<string> args = new(argc);
unsafe
{
var argv = (IntPtr*)argvPtr;
for (var i = 0; i < argc; i++)
args.Add(Marshal.PtrToStringUni(argv[i]));
}
// API14 TODO: Refactor
var args = argsArray.ToList();
args.Insert(0, Assembly.GetExecutingAssembly().Location);
Init(args);
args.Remove("-v"); // Remove "verbose" flag
@ -305,6 +291,7 @@ namespace Dalamud.Injector
var configurationPath = startInfo.ConfigurationPath;
var pluginDirectory = startInfo.PluginDirectory;
var assetDirectory = startInfo.AssetDirectory;
var tempDirectory = startInfo.TempDirectory;
var delayInitializeMs = startInfo.DelayInitializeMs;
var logName = startInfo.LogName;
var logPath = startInfo.LogPath;
@ -335,6 +322,10 @@ namespace Dalamud.Injector
{
assetDirectory = args[i][key.Length..];
}
else if (args[i].StartsWith(key = "--dalamud-temp-directory="))
{
tempDirectory = args[i][key.Length..];
}
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
{
delayInitializeMs = int.Parse(args[i][key.Length..]);
@ -447,6 +438,7 @@ namespace Dalamud.Injector
startInfo.ConfigurationPath = configurationPath;
startInfo.PluginDirectory = pluginDirectory;
startInfo.AssetDirectory = assetDirectory;
startInfo.TempDirectory = tempDirectory;
startInfo.Language = clientLanguage;
startInfo.Platform = platform;
startInfo.DelayInitializeMs = delayInitializeMs;

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

@ -0,0 +1,108 @@
using System;
using System.Linq;
using Dalamud.Networking.Rpc.Model;
using Xunit;
namespace Dalamud.Test.Rpc
{
public class DalamudUriTests
{
[Theory]
[InlineData("https://www.google.com/", false)]
[InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", true)]
public void ValidatesScheme(string uri, bool valid)
{
Action act = () => { _ = DalamudUri.FromUri(uri); };
var ex = Record.Exception(act);
if (valid)
{
Assert.Null(ex);
}
else
{
Assert.NotNull(ex);
Assert.IsType<ArgumentOutOfRangeException>(ex);
}
}
[Theory]
[InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", "plugininstaller")]
[InlineData("dalamud://Plugin/Dalamud.FindAnything/OpenWindow", "plugin")]
[InlineData("dalamud://Test", "test")]
public void ExtractsNamespace(string uri, string expectedNamespace)
{
var dalamudUri = DalamudUri.FromUri(uri);
Assert.Equal(expectedNamespace, dalamudUri.Namespace);
}
[Theory]
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/")]
[InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux")]
[InlineData("dalamud://foo/bar/baz", "/bar/baz")]
[InlineData("dalamud://foo/bar", "/bar")]
[InlineData("dalamud://foo/bar/", "/bar/")]
[InlineData("dalamud://foo/", "/")]
public void ExtractsPath(string uri, string expectedPath)
{
var dalamudUri = DalamudUri.FromUri(uri);
Assert.Equal(expectedPath, dalamudUri.Path);
}
[Theory]
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo#frag", "/bar/baz/qux/?cow=moo#frag")]
[InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/?cow=moo")]
[InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux?cow=moo")]
[InlineData("dalamud://foo/bar/baz", "/bar/baz")]
[InlineData("dalamud://foo/bar?cow=moo", "/bar?cow=moo")]
[InlineData("dalamud://foo/bar", "/bar")]
[InlineData("dalamud://foo/bar/?cow=moo", "/bar/?cow=moo")]
[InlineData("dalamud://foo/bar/", "/bar/")]
[InlineData("dalamud://foo/?cow=moo#chicken", "/?cow=moo#chicken")]
[InlineData("dalamud://foo/?cow=moo", "/?cow=moo")]
[InlineData("dalamud://foo/", "/")]
public void ExtractsData(string uri, string expectedData)
{
var dalamudUri = DalamudUri.FromUri(uri);
Assert.Equal(expectedData, dalamudUri.Data);
}
[Theory]
[InlineData("dalamud://foo/bar", 0)]
[InlineData("dalamud://foo/bar?cow=moo", 1)]
[InlineData("dalamud://foo/bar?cow=moo&wolf=awoo", 2)]
[InlineData("dalamud://foo/bar?cow=moo&wolf=awoo&cat", 3)]
public void ExtractsQueryParams(string uri, int queryCount)
{
var dalamudUri = DalamudUri.FromUri(uri);
Assert.Equal(queryCount, dalamudUri.QueryParams.Count);
}
[Theory]
[InlineData("dalamud://foo/bar/baz/qux/meh/?foo=bar", 5, true)]
[InlineData("dalamud://foo/bar/baz/qux/meh/", 5, true)]
[InlineData("dalamud://foo/bar/baz/qux/meh", 5)]
[InlineData("dalamud://foo/bar/baz/qux", 4)]
[InlineData("dalamud://foo/bar/baz", 3)]
[InlineData("dalamud://foo/bar/", 2)]
[InlineData("dalamud://foo/bar", 2)]
public void ExtractsSegments(string uri, int segmentCount, bool finalSegmentEndsWithSlash = false)
{
var dalamudUri = DalamudUri.FromUri(uri);
var segments = dalamudUri.Segments;
// First segment must always be `/`
Assert.Equal("/", segments[0]);
Assert.Equal(segmentCount, segments.Length);
if (finalSegmentEndsWithSlash)
{
Assert.EndsWith("/", segments.Last());
}
}
}
}

View file

@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32319.34
MinimumVisualStudioVersion = 10.0.40219.1
@ -27,8 +27,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Injector.Boot", "Dalamud.Injector.Boot\Dalamud.Injector.Boot.vcxproj", "{8874326B-E755-4D13-90B4-59AB263A3E6B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
@ -49,8 +47,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator", "lib\FFX
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator.Runtime", "lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj", "{A6AA1C3F-9470-4922-9D3F-D4549657AB22}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Injector", "Injector", "{19775C83-7117-4A5F-AA00-18889F46A490}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimgui", "external\cimgui\cimgui.vcxproj", "{8430077C-F736-4246-A052-8EA1CECE844E}"
@ -103,10 +99,6 @@ Global
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
@ -188,8 +180,6 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490}
{8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490}
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}

View file

@ -73,14 +73,13 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MinSharp" />
<PackageReference Include="SharpDX.Direct3D11" />
<PackageReference Include="SharpDX.Mathematics" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.Async" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="sqlite-net-pcl" />
<PackageReference Include="StreamJsonRpc" />
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -122,6 +121,8 @@
<Content Include="licenses.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Remove="Interface\ImGuiBackend\Renderers\gaussian.hlsl" />
<None Remove="Interface\ImGuiBackend\Renderers\fullscreen-quad.hlsl.bytes" />
</ItemGroup>
<ItemGroup>
@ -226,9 +227,4 @@
<!-- writes the attribute to the customAssemblyInfo file -->
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
</Target>
<!-- Copy plugin .targets folder into distrib -->
<Target Name="CopyPluginTargets" AfterTargets="Build">
<Copy SourceFiles="$(ProjectDir)\..\targets\Dalamud.Plugin.targets;$(ProjectDir)\..\targets\Dalamud.Plugin.Bootstrap.targets" DestinationFolder="$(OutDir)\targets" />
</Target>
</Project>

View file

@ -151,7 +151,7 @@ public enum DalamudAsset
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
/// </summary>
[DalamudAsset(DalamudAssetPurpose.Font)]
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
[DalamudAssetPath("UIRes", "FontAwesome710FreeSolid.otf")]
FontAwesomeFreeSolid = 2003,
/// <summary>

View file

@ -263,7 +263,7 @@ public sealed class EntryPoint
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
var searchPath = $".;{symbolPath}";
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess();
// Remove any existing Symbol Handler and Init a new one with our search path added
Windows.Win32.PInvoke.SymCleanup(currentProcess);

View file

@ -1,4 +1,4 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Plugin.Services;
namespace Dalamud.Game.Addon.Lifecycle;

View file

@ -2,6 +2,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Plugin.Services;
namespace Dalamud.Game;
/// <summary>

View file

@ -63,47 +63,37 @@ public interface IAetheryteEntry
}
/// <summary>
/// Class representing an aetheryte entry available to the game.
/// This struct represents an aetheryte entry available to the game.
/// </summary>
internal sealed class AetheryteEntry : IAetheryteEntry
/// <param name="data">Data read from the Aetheryte List.</param>
internal readonly struct AetheryteEntry(TeleportInfo data) : IAetheryteEntry
{
private readonly TeleportInfo data;
/// <summary>
/// Initializes a new instance of the <see cref="AetheryteEntry"/> class.
/// </summary>
/// <param name="data">Data read from the Aetheryte List.</param>
internal AetheryteEntry(TeleportInfo data)
{
this.data = data;
}
/// <inheritdoc />
public uint AetheryteId => data.AetheryteId;
/// <inheritdoc />
public uint AetheryteId => this.data.AetheryteId;
public uint TerritoryId => data.TerritoryId;
/// <inheritdoc />
public uint TerritoryId => this.data.TerritoryId;
public byte SubIndex => data.SubIndex;
/// <inheritdoc />
public byte SubIndex => this.data.SubIndex;
public byte Ward => data.Ward;
/// <inheritdoc />
public byte Ward => this.data.Ward;
public byte Plot => data.Plot;
/// <inheritdoc />
public byte Plot => this.data.Plot;
public uint GilCost => data.GilCost;
/// <inheritdoc />
public uint GilCost => this.data.GilCost;
public bool IsFavourite => data.IsFavourite;
/// <inheritdoc />
public bool IsFavourite => this.data.IsFavourite;
public bool IsSharedHouse => data.IsSharedHouse;
/// <inheritdoc />
public bool IsSharedHouse => this.data.IsSharedHouse;
/// <inheritdoc />
public bool IsApartment => this.data.IsApartment;
public bool IsApartment => data.IsApartment;
/// <inheritdoc />
public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId);

View file

@ -88,10 +88,7 @@ internal sealed partial class AetheryteList
/// <inheritdoc/>
public IEnumerator<IAetheryteEntry> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
yield return this[i];
}
return new Enumerator(this);
}
/// <inheritdoc/>
@ -99,4 +96,30 @@ internal sealed partial class AetheryteList
{
return this.GetEnumerator();
}
private struct Enumerator(AetheryteList aetheryteList) : IEnumerator<IAetheryteEntry>
{
private int index = 0;
public IAetheryteEntry Current { get; private set; }
object IEnumerator.Current => this.Current;
public bool MoveNext()
{
if (this.index == aetheryteList.Length) return false;
this.Current = aetheryteList[this.index];
this.index++;
return true;
}
public void Reset()
{
this.index = 0;
}
public void Dispose()
{
}
}
}

View file

@ -8,6 +8,7 @@ using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
using CSBuddy = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy;
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
using CSUIState = FFXIVClientStructs.FFXIV.Client.Game.UI.UIState;
namespace Dalamud.Game.ClientState.Buddy;
@ -23,7 +24,7 @@ namespace Dalamud.Game.ClientState.Buddy;
#pragma warning restore SA1015
internal sealed partial class BuddyList : IServiceType, IBuddyList
{
private const uint InvalidObjectID = 0xE0000000;
private const uint InvalidEntityId = 0xE0000000;
[ServiceManager.ServiceDependency]
private readonly PlayerState playerState = Service<PlayerState>.Get();
@ -84,37 +85,37 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
}
/// <inheritdoc/>
public unsafe IntPtr GetCompanionBuddyMemberAddress()
public unsafe nint GetCompanionBuddyMemberAddress()
{
return (IntPtr)this.BuddyListStruct->CompanionInfo.Companion;
return (nint)this.BuddyListStruct->CompanionInfo.Companion;
}
/// <inheritdoc/>
public unsafe IntPtr GetPetBuddyMemberAddress()
public unsafe nint GetPetBuddyMemberAddress()
{
return (IntPtr)this.BuddyListStruct->PetInfo.Pet;
return (nint)this.BuddyListStruct->PetInfo.Pet;
}
/// <inheritdoc/>
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
public unsafe nint GetBattleBuddyMemberAddress(int index)
{
if (index < 0 || index >= 3)
return IntPtr.Zero;
return 0;
return (IntPtr)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
return (nint)Unsafe.AsPointer(ref this.BuddyListStruct->BattleBuddies[index]);
}
/// <inheritdoc/>
public IBuddyMember? CreateBuddyMemberReference(IntPtr address)
public unsafe IBuddyMember? CreateBuddyMemberReference(nint address)
{
if (address == IntPtr.Zero)
if (address == 0)
return null;
if (!this.playerState.IsLoaded)
if (this.playerState.ContentId == 0)
return null;
var buddy = new BuddyMember(address);
if (buddy.ObjectId == InvalidObjectID)
var buddy = new BuddyMember((CSBuddyMember*)address);
if (buddy.EntityId == InvalidEntityId)
return null;
return buddy;
@ -132,12 +133,35 @@ internal sealed partial class BuddyList
/// <inheritdoc/>
public IEnumerator<IBuddyMember> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
yield return this[i];
}
return new Enumerator(this);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private struct Enumerator(BuddyList buddyList) : IEnumerator<IBuddyMember>
{
private int index = 0;
public IBuddyMember Current { get; private set; }
object IEnumerator.Current => this.Current;
public bool MoveNext()
{
if (this.index == buddyList.Length) return false;
this.Current = buddyList[this.index];
this.index++;
return true;
}
public void Reset()
{
this.index = 0;
}
public void Dispose()
{
}
}
}

View file

@ -1,20 +1,24 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.Data;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Lumina.Excel;
using CSBuddyMember = FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember;
namespace Dalamud.Game.ClientState.Buddy;
/// <summary>
/// Interface representing represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// </summary>
public interface IBuddyMember
public interface IBuddyMember : IEquatable<IBuddyMember>
{
/// <summary>
/// Gets the address of the buddy in memory.
/// </summary>
IntPtr Address { get; }
nint Address { get; }
/// <summary>
/// Gets the object ID of this buddy.
@ -67,42 +71,34 @@ public interface IBuddyMember
}
/// <summary>
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// This struct represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// </summary>
internal unsafe class BuddyMember : IBuddyMember
/// <param name="ptr">A pointer to the BuddyMember.</param>
internal readonly unsafe struct BuddyMember(CSBuddyMember* ptr) : IBuddyMember
{
[ServiceManager.ServiceDependency]
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
/// <summary>
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
/// </summary>
/// <param name="address">Buddy address.</param>
internal BuddyMember(IntPtr address)
{
this.Address = address;
}
/// <inheritdoc />
public nint Address => (nint)ptr;
/// <inheritdoc />
public IntPtr Address { get; }
public uint ObjectId => this.EntityId;
/// <inheritdoc />
public uint ObjectId => this.Struct->EntityId;
public uint EntityId => ptr->EntityId;
/// <inheritdoc />
public uint EntityId => this.Struct->EntityId;
public IGameObject? GameObject => this.objectTable.SearchById(this.EntityId);
/// <inheritdoc />
public IGameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
public uint CurrentHP => ptr->CurrentHealth;
/// <inheritdoc />
public uint CurrentHP => this.Struct->CurrentHealth;
public uint MaxHP => ptr->MaxHealth;
/// <inheritdoc />
public uint MaxHP => this.Struct->MaxHealth;
/// <inheritdoc />
public uint DataID => this.Struct->DataId;
public uint DataID => ptr->DataId;
/// <inheritdoc />
public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID);
@ -113,5 +109,25 @@ internal unsafe class BuddyMember : IBuddyMember
/// <inheritdoc />
public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(this.DataID);
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
public static bool operator ==(BuddyMember x, BuddyMember y) => x.Equals(y);
public static bool operator !=(BuddyMember x, BuddyMember y) => !(x == y);
/// <inheritdoc/>
public bool Equals(IBuddyMember? other)
{
return this.EntityId == other.EntityId;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is BuddyMember fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.EntityId.GetHashCode();
}
}

View file

@ -1,3 +1,5 @@
using Dalamud.Plugin.Services;
namespace Dalamud.Game.ClientState;
/// <summary>

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Dalamud.Data;
@ -7,10 +8,12 @@ using Dalamud.Memory;
using Lumina.Excel;
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
namespace Dalamud.Game.ClientState.Fates;
/// <summary>
/// Interface representing an fate entry that can be seen in the current area.
/// Interface representing a fate entry that can be seen in the current area.
/// </summary>
public interface IFate : IEquatable<IFate>
{
@ -112,129 +115,96 @@ public interface IFate : IEquatable<IFate>
/// <summary>
/// Gets the address of this Fate in memory.
/// </summary>
IntPtr Address { get; }
nint Address { get; }
}
/// <summary>
/// This class represents an FFXIV Fate.
/// This struct represents a Fate.
/// </summary>
internal unsafe partial class Fate
/// <param name="ptr">A pointer to the FateContext.</param>
internal readonly unsafe struct Fate(CSFateContext* ptr) : IFate
{
/// <summary>
/// Initializes a new instance of the <see cref="Fate"/> class.
/// </summary>
/// <param name="address">The address of this fate in memory.</param>
internal Fate(IntPtr address)
{
this.Address = address;
}
/// <inheritdoc />
public IntPtr Address { get; }
private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address;
public static bool operator ==(Fate fate1, Fate fate2)
{
if (fate1 is null || fate2 is null)
return Equals(fate1, fate2);
return fate1.Equals(fate2);
}
public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2);
/// <summary>
/// Gets a value indicating whether this Fate is still valid in memory.
/// </summary>
/// <param name="fate">The fate to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(Fate fate)
{
if (fate == null)
return false;
var playerState = Service<PlayerState>.Get();
return playerState.IsLoaded == true;
}
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid() => IsValid(this);
public nint Address => (nint)ptr;
/// <inheritdoc/>
bool IEquatable<IFate>.Equals(IFate other) => this.FateId == other?.FateId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<IFate>)this).Equals(obj as IFate);
/// <inheritdoc/>
public override int GetHashCode() => this.FateId.GetHashCode();
}
/// <summary>
/// This class represents an FFXIV Fate.
/// </summary>
internal unsafe partial class Fate : IFate
{
/// <inheritdoc/>
public ushort FateId => this.Struct->FateId;
public ushort FateId => ptr->FateId;
/// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId);
/// <inheritdoc/>
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
public int StartTimeEpoch => ptr->StartTimeEpoch;
/// <inheritdoc/>
public short Duration => this.Struct->Duration;
public short Duration => ptr->Duration;
/// <inheritdoc/>
public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds();
/// <inheritdoc/>
public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name);
public SeString Name => MemoryHelper.ReadSeString(&ptr->Name);
/// <inheritdoc/>
public SeString Description => MemoryHelper.ReadSeString(&this.Struct->Description);
public SeString Description => MemoryHelper.ReadSeString(&ptr->Description);
/// <inheritdoc/>
public SeString Objective => MemoryHelper.ReadSeString(&this.Struct->Objective);
public SeString Objective => MemoryHelper.ReadSeString(&ptr->Objective);
/// <inheritdoc/>
public FateState State => (FateState)this.Struct->State;
public FateState State => (FateState)ptr->State;
/// <inheritdoc/>
public byte HandInCount => this.Struct->HandInCount;
public byte HandInCount => ptr->HandInCount;
/// <inheritdoc/>
public byte Progress => this.Struct->Progress;
public byte Progress => ptr->Progress;
/// <inheritdoc/>
public bool HasBonus => this.Struct->IsBonus;
public bool HasBonus => ptr->IsBonus;
/// <inheritdoc/>
public uint IconId => this.Struct->IconId;
public uint IconId => ptr->IconId;
/// <inheritdoc/>
public byte Level => this.Struct->Level;
public byte Level => ptr->Level;
/// <inheritdoc/>
public byte MaxLevel => this.Struct->MaxLevel;
public byte MaxLevel => ptr->MaxLevel;
/// <inheritdoc/>
public Vector3 Position => this.Struct->Location;
public Vector3 Position => ptr->Location;
/// <inheritdoc/>
public float Radius => this.Struct->Radius;
public float Radius => ptr->Radius;
/// <inheritdoc/>
public uint MapIconId => this.Struct->MapIconId;
public uint MapIconId => ptr->MapIconId;
/// <summary>
/// Gets the territory this <see cref="Fate"/> is located in.
/// </summary>
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->MapMarkers[0].MapMarkerData.TerritoryTypeId);
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->MapMarkers[0].MapMarkerData.TerritoryTypeId);
public static bool operator ==(Fate x, Fate y) => x.Equals(y);
public static bool operator !=(Fate x, Fate y) => !(x == y);
/// <inheritdoc/>
public bool Equals(IFate? other)
{
return this.FateId == other.FateId;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Fate fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.FateId.GetHashCode();
}
}

View file

@ -6,6 +6,7 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
using CSFateContext = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext;
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
namespace Dalamud.Game.ClientState.Fates;
@ -26,7 +27,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable
}
/// <inheritdoc/>
public unsafe IntPtr Address => (nint)CSFateManager.Instance();
public unsafe nint Address => (nint)CSFateManager.Instance();
/// <inheritdoc/>
public unsafe int Length
@ -69,29 +70,29 @@ internal sealed partial class FateTable : IServiceType, IFateTable
}
/// <inheritdoc/>
public unsafe IntPtr GetFateAddress(int index)
public unsafe nint GetFateAddress(int index)
{
if (index >= this.Length)
return IntPtr.Zero;
return 0;
var fateManager = CSFateManager.Instance();
if (fateManager == null)
return IntPtr.Zero;
return 0;
return (IntPtr)fateManager->Fates[index].Value;
return (nint)fateManager->Fates[index].Value;
}
/// <inheritdoc/>
public IFate? CreateFateReference(IntPtr offset)
public unsafe IFate? CreateFateReference(IntPtr address)
{
if (offset == IntPtr.Zero)
if (address == 0)
return null;
var playerState = Service<PlayerState>.Get();
if (!playerState.IsLoaded)
var clientState = Service<ClientState>.Get();
if (clientState.LocalContentId == 0)
return null;
return new Fate(offset);
return new Fate((CSFateContext*)address);
}
}
@ -106,12 +107,35 @@ internal sealed partial class FateTable
/// <inheritdoc/>
public IEnumerator<IFate> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
yield return this[i];
}
return new Enumerator(this);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private struct Enumerator(FateTable fateTable) : IEnumerator<IFate>
{
private int index = 0;
public IFate Current { get; private set; }
object IEnumerator.Current => this.Current;
public bool MoveNext()
{
if (this.index == fateTable.Length) return false;
this.Current = fateTable[this.index];
this.index++;
return true;
}
public void Reset()
{
this.index = 0;
}
public void Dispose()
{
}
}
}

View file

@ -13,8 +13,6 @@ using Dalamud.Utility;
using FFXIVClientStructs.Interop;
using Microsoft.Extensions.ObjectPool;
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager;
@ -37,8 +35,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
private readonly CachedEntry[] cachedObjectTable;
private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4];
[ServiceManager.ServiceConstructor]
private unsafe ObjectTable()
{
@ -48,9 +44,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
this.cachedObjectTable = new CachedEntry[objectTableLength];
for (var i = 0; i < this.cachedObjectTable.Length; i++)
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++)
this.frameworkThreadEnumerators[i] = new(this, i);
}
/// <inheritdoc/>
@ -243,30 +236,14 @@ internal sealed partial class ObjectTable
public IEnumerator<IGameObject> GetEnumerator()
{
ThreadSafety.AssertMainThread();
// If we're on the framework thread, see if there's an already allocated enumerator available for use.
foreach (ref var x in this.frameworkThreadEnumerators.AsSpan())
{
if (x is not null)
{
var t = x;
x = null;
t.Reset();
return t;
}
}
// No reusable enumerator is available; allocate a new temporary one.
return new Enumerator(this, -1);
return new Enumerator(this);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator<IGameObject>, IResettable
private struct Enumerator(ObjectTable owner) : IEnumerator<IGameObject>
{
private ObjectTable? owner = owner;
private int index = -1;
public IGameObject Current { get; private set; } = null!;
@ -278,7 +255,7 @@ internal sealed partial class ObjectTable
if (this.index == objectTableLength)
return false;
var cache = this.owner!.cachedObjectTable.AsSpan();
var cache = owner.cachedObjectTable.AsSpan();
for (this.index++; this.index < objectTableLength; this.index++)
{
if (cache[this.index].Update() is { } ao)
@ -295,17 +272,6 @@ internal sealed partial class ObjectTable
public void Dispose()
{
if (this.owner is not { } o)
return;
if (slotId != -1)
o.frameworkThreadEnumerators[slotId] = this;
}
public bool TryReset()
{
this.Reset();
return true;
}
}
}

View file

@ -1,6 +1,7 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Control;

View file

@ -9,6 +9,7 @@ using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
namespace Dalamud.Game.ClientState.Party;
@ -43,20 +44,20 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
/// <inheritdoc/>
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance();
public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance();
/// <inheritdoc/>
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
public nint GroupListAddress => (nint)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
/// <inheritdoc/>
public IntPtr AllianceListAddress => (IntPtr)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
/// <inheritdoc/>
public long PartyId => this.GroupManagerStruct->MainGroup.PartyId;
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
private static int PartyMemberSize { get; } = Marshal.SizeOf<CSPartyMember>();
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
private CSGroupManager* GroupManagerStruct => (CSGroupManager*)this.GroupManagerAddress;
/// <inheritdoc/>
public IPartyMember? this[int index]
@ -81,39 +82,45 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
}
/// <inheritdoc/>
public IntPtr GetPartyMemberAddress(int index)
public nint GetPartyMemberAddress(int index)
{
if (index < 0 || index >= GroupLength)
return IntPtr.Zero;
return 0;
return this.GroupListAddress + (index * PartyMemberSize);
}
/// <inheritdoc/>
public IPartyMember? CreatePartyMemberReference(IntPtr address)
public IPartyMember? CreatePartyMemberReference(nint address)
{
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
if (this.playerState.ContentId == 0)
return null;
return new PartyMember(address);
if (address == 0)
return null;
return new PartyMember((CSPartyMember*)address);
}
/// <inheritdoc/>
public IntPtr GetAllianceMemberAddress(int index)
public nint GetAllianceMemberAddress(int index)
{
if (index < 0 || index >= AllianceLength)
return IntPtr.Zero;
return 0;
return this.AllianceListAddress + (index * PartyMemberSize);
}
/// <inheritdoc/>
public IPartyMember? CreateAllianceMemberReference(IntPtr address)
public IPartyMember? CreateAllianceMemberReference(nint address)
{
if (address == IntPtr.Zero || !this.playerState.IsLoaded)
if (this.playerState.ContentId == 0)
return null;
return new PartyMember(address);
if (address == 0)
return null;
return new PartyMember((CSPartyMember*)address);
}
}
@ -128,18 +135,44 @@ internal sealed partial class PartyList
/// <inheritdoc/>
public IEnumerator<IPartyMember> GetEnumerator()
{
// Normally using Length results in a recursion crash, however we know the party size via ptr.
for (var i = 0; i < this.Length; i++)
{
var member = this[i];
if (member == null)
break;
yield return member;
}
return new Enumerator(this);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private struct Enumerator(PartyList partyList) : IEnumerator<IPartyMember>
{
private int index = 0;
public IPartyMember Current { get; private set; }
object IEnumerator.Current => this.Current;
public bool MoveNext()
{
if (this.index == partyList.Length) return false;
for (; this.index < partyList.Length; this.index++)
{
var partyMember = partyList[this.index];
if (partyMember != null)
{
this.Current = partyMember;
return true;
}
}
return false;
}
public void Reset()
{
this.index = 0;
}
public void Dispose()
{
}
}
}

View file

@ -1,26 +1,27 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Data;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using Lumina.Excel;
using CSPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
namespace Dalamud.Game.ClientState.Party;
/// <summary>
/// Interface representing a party member.
/// </summary>
public interface IPartyMember
public interface IPartyMember : IEquatable<IPartyMember>
{
/// <summary>
/// Gets the address of this party member in memory.
/// </summary>
IntPtr Address { get; }
nint Address { get; }
/// <summary>
/// Gets a list of buffs or debuffs applied to this party member.
@ -108,69 +109,81 @@ public interface IPartyMember
}
/// <summary>
/// This class represents a party member in the group manager.
/// This struct represents a party member in the group manager.
/// </summary>
internal unsafe class PartyMember : IPartyMember
/// <param name="ptr">A pointer to the PartyMember.</param>
internal unsafe readonly struct PartyMember(CSPartyMember* ptr) : IPartyMember
{
/// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary>
/// <param name="address">Address of the party member.</param>
internal PartyMember(IntPtr address)
{
this.Address = address;
}
/// <inheritdoc/>
public nint Address => (nint)ptr;
/// <inheritdoc/>
public IntPtr Address { get; }
public StatusList Statuses => new(&ptr->StatusManager);
/// <inheritdoc/>
public StatusList Statuses => new(&this.Struct->StatusManager);
public Vector3 Position => ptr->Position;
/// <inheritdoc/>
public Vector3 Position => this.Struct->Position;
public long ContentId => (long)ptr->ContentId;
/// <inheritdoc/>
public long ContentId => (long)this.Struct->ContentId;
public uint ObjectId => ptr->EntityId;
/// <inheritdoc/>
public uint ObjectId => this.Struct->EntityId;
/// <inheritdoc/>
public uint EntityId => this.Struct->EntityId;
public uint EntityId => ptr->EntityId;
/// <inheritdoc/>
public IGameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.EntityId);
/// <inheritdoc/>
public uint CurrentHP => this.Struct->CurrentHP;
public uint CurrentHP => ptr->CurrentHP;
/// <inheritdoc/>
public uint MaxHP => this.Struct->MaxHP;
public uint MaxHP => ptr->MaxHP;
/// <inheritdoc/>
public ushort CurrentMP => this.Struct->CurrentMP;
public ushort CurrentMP => ptr->CurrentMP;
/// <inheritdoc/>
public ushort MaxMP => this.Struct->MaxMP;
public ushort MaxMP => ptr->MaxMP;
/// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryType);
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(ptr->TerritoryType);
/// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(this.Struct->HomeWorld);
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(ptr->HomeWorld);
/// <inheritdoc/>
public SeString Name => SeString.Parse(this.Struct->Name);
public SeString Name => SeString.Parse(ptr->Name);
/// <inheritdoc/>
public byte Sex => this.Struct->Sex;
public byte Sex => ptr->Sex;
/// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(this.Struct->ClassJob);
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(ptr->ClassJob);
/// <inheritdoc/>
public byte Level => this.Struct->Level;
public byte Level => ptr->Level;
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
public static bool operator ==(PartyMember x, PartyMember y) => x.Equals(y);
public static bool operator !=(PartyMember x, PartyMember y) => !(x == y);
/// <inheritdoc/>
public bool Equals(IPartyMember? other)
{
return this.EntityId == other.EntityId;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is PartyMember fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.EntityId.GetHashCode();
}
}

View file

@ -1,61 +1,49 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.Data;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Lumina.Excel;
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
namespace Dalamud.Game.ClientState.Statuses;
/// <summary>
/// This class represents a status effect an actor is afflicted by.
/// Interface representing a status.
/// </summary>
public unsafe class Status
public interface IStatus : IEquatable<IStatus>
{
/// <summary>
/// Initializes a new instance of the <see cref="Status"/> class.
/// </summary>
/// <param name="address">Status address.</param>
internal Status(IntPtr address)
{
this.Address = address;
}
/// <summary>
/// Gets the address of the status in memory.
/// </summary>
public IntPtr Address { get; }
nint Address { get; }
/// <summary>
/// Gets the status ID of this status.
/// </summary>
public uint StatusId => this.Struct->StatusId;
uint StatusId { get; }
/// <summary>
/// Gets the GameData associated with this status.
/// </summary>
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(this.Struct->StatusId);
RowRef<Lumina.Excel.Sheets.Status> GameData { get; }
/// <summary>
/// Gets the parameter value of the status.
/// </summary>
public ushort Param => this.Struct->Param;
/// <summary>
/// Gets the stack count of this status.
/// Only valid if this is a non-food status.
/// </summary>
[Obsolete($"Replaced with {nameof(Param)}", true)]
public byte StackCount => (byte)this.Struct->Param;
ushort Param { get; }
/// <summary>
/// Gets the time remaining of this status.
/// </summary>
public float RemainingTime => this.Struct->RemainingTime;
float RemainingTime { get; }
/// <summary>
/// Gets the source ID of this status.
/// </summary>
public uint SourceId => this.Struct->SourceObject.ObjectId;
uint SourceId { get; }
/// <summary>
/// Gets the source actor associated with this status.
@ -63,7 +51,55 @@ public unsafe class Status
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
IGameObject? SourceObject { get; }
}
/// <summary>
/// This struct represents a status effect an actor is afflicted by.
/// </summary>
/// <param name="ptr">A pointer to the Status.</param>
internal unsafe readonly struct Status(CSStatus* ptr) : IStatus
{
/// <inheritdoc/>
public nint Address => (nint)ptr;
/// <inheritdoc/>
public uint StatusId => ptr->StatusId;
/// <inheritdoc/>
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(ptr->StatusId);
/// <inheritdoc/>
public ushort Param => ptr->Param;
/// <inheritdoc/>
public float RemainingTime => ptr->RemainingTime;
/// <inheritdoc/>
public uint SourceId => ptr->SourceObject.ObjectId;
/// <inheritdoc/>
public IGameObject? SourceObject => Service<ObjectTable>.Get().SearchById(this.SourceId);
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
public static bool operator ==(Status x, Status y) => x.Equals(y);
public static bool operator !=(Status x, Status y) => !(x == y);
/// <inheritdoc/>
public bool Equals(IStatus? other)
{
return this.StatusId == other.StatusId && this.SourceId == other.SourceId && this.Param == other.Param && this.RemainingTime == other.RemainingTime;
}
/// <inheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Status fate && this.Equals(fate);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(this.StatusId, this.SourceId, this.Param, this.RemainingTime);
}
}

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Dalamud.Game.Player;
using CSStatus = FFXIVClientStructs.FFXIV.Client.Game.Status;
namespace Dalamud.Game.ClientState.Statuses;
@ -16,7 +16,7 @@ public sealed unsafe partial class StatusList
/// Initializes a new instance of the <see cref="StatusList"/> class.
/// </summary>
/// <param name="address">Address of the status list.</param>
internal StatusList(IntPtr address)
internal StatusList(nint address)
{
this.Address = address;
}
@ -26,14 +26,14 @@ public sealed unsafe partial class StatusList
/// </summary>
/// <param name="pointer">Pointer to the status list.</param>
internal unsafe StatusList(void* pointer)
: this((IntPtr)pointer)
: this((nint)pointer)
{
}
/// <summary>
/// Gets the address of the status list in memory.
/// </summary>
public IntPtr Address { get; }
public nint Address { get; }
/// <summary>
/// Gets the amount of status effect slots the actor has.
@ -49,7 +49,7 @@ public sealed unsafe partial class StatusList
/// </summary>
/// <param name="index">Status Index.</param>
/// <returns>The status at the specified index.</returns>
public Status? this[int index]
public IStatus? this[int index]
{
get
{
@ -66,7 +66,7 @@ public sealed unsafe partial class StatusList
/// </summary>
/// <param name="address">The address of the status list in memory.</param>
/// <returns>The status object containing the requested data.</returns>
public static StatusList? CreateStatusListReference(IntPtr address)
public static StatusList? CreateStatusListReference(nint address)
{
if (address == IntPtr.Zero)
return null;
@ -74,8 +74,12 @@ public sealed unsafe partial class StatusList
// The use case for CreateStatusListReference and CreateStatusReference to be static is so
// fake status lists can be generated. Since they aren't exposed as services, it's either
// here or somewhere else.
var playerState = Service<PlayerState>.Get();
if (!playerState.IsLoaded)
var clientState = Service<ClientState>.Get();
if (clientState.LocalContentId == 0)
return null;
if (address == 0)
return null;
return new StatusList(address);
@ -86,16 +90,15 @@ public sealed unsafe partial class StatusList
/// </summary>
/// <param name="address">The address of the status effect in memory.</param>
/// <returns>The status object containing the requested data.</returns>
public static Status? CreateStatusReference(IntPtr address)
public static IStatus? CreateStatusReference(nint address)
{
if (address == IntPtr.Zero)
return null;
var playerState = Service<PlayerState>.Get();
if (!playerState.IsLoaded)
if (address == 0)
return null;
return new Status(address);
return new Status((CSStatus*)address);
}
/// <summary>
@ -103,22 +106,22 @@ public sealed unsafe partial class StatusList
/// </summary>
/// <param name="index">The index of the status.</param>
/// <returns>The memory address of the status.</returns>
public IntPtr GetStatusAddress(int index)
public nint GetStatusAddress(int index)
{
if (index < 0 || index >= this.Length)
return IntPtr.Zero;
return 0;
return (IntPtr)Unsafe.AsPointer(ref this.Struct->Status[index]);
return (nint)Unsafe.AsPointer(ref this.Struct->Status[index]);
}
}
/// <summary>
/// This collection represents the status effects an actor is afflicted by.
/// </summary>
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
public sealed partial class StatusList : IReadOnlyCollection<IStatus>, ICollection
{
/// <inheritdoc/>
int IReadOnlyCollection<Status>.Count => this.Length;
int IReadOnlyCollection<IStatus>.Count => this.Length;
/// <inheritdoc/>
int ICollection.Count => this.Length;
@ -130,17 +133,9 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
object ICollection.SyncRoot => this;
/// <inheritdoc/>
public IEnumerator<Status> GetEnumerator()
public IEnumerator<IStatus> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
var status = this[i];
if (status == null || status.StatusId == 0)
continue;
yield return status;
}
return new Enumerator(this);
}
/// <inheritdoc/>
@ -155,4 +150,39 @@ public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollectio
index++;
}
}
private struct Enumerator(StatusList statusList) : IEnumerator<IStatus>
{
private int index = 0;
public IStatus Current { get; private set; }
object IEnumerator.Current => this.Current;
public bool MoveNext()
{
if (this.index == statusList.Length) return false;
for (; this.index < statusList.Length; this.index++)
{
var status = statusList[this.index];
if (status != null && status.StatusId != 0)
{
this.Current = status;
return true;
}
}
return false;
}
public void Reset()
{
this.index = 0;
}
public void Dispose()
{
}
}
}

View file

@ -1,35 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs;
/// <summary>
/// Native memory representation of a FFXIV status effect.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct StatusEffect
{
/// <summary>
/// The effect ID.
/// </summary>
public short EffectId;
/// <summary>
/// How many stacks are present.
/// </summary>
public byte StackCount;
/// <summary>
/// Additional parameters.
/// </summary>
public byte Param;
/// <summary>
/// The duration remaining.
/// </summary>
public float Duration;
/// <summary>
/// The ID of the actor that caused this effect.
/// </summary>
public int OwnerId;
}

View file

@ -1,4 +1,6 @@
namespace Dalamud.Game.Config;
using Dalamud.Plugin.Services;
namespace Dalamud.Game.Config;
/// <summary>
/// Game config system address resolver.

View file

@ -1,3 +1,5 @@
using Dalamud.Plugin.Services;
namespace Dalamud.Game.DutyState;
/// <summary>

View file

@ -1,3 +1,5 @@
using Dalamud.Plugin.Services;
namespace Dalamud.Game.Gui;
/// <summary>

View file

@ -1,3 +1,5 @@
using Dalamud.Plugin.Services;
namespace Dalamud.Game.Gui.NamePlate;
/// <summary>

View file

@ -1,3 +1,5 @@
using Dalamud.Plugin.Services;
namespace Dalamud.Game.Network;
/// <summary>

View file

@ -1,4 +1,6 @@
namespace Dalamud.Game.Network.Internal;
using Dalamud.Plugin.Services;
namespace Dalamud.Game.Network.Internal;
/// <summary>
/// Internal address resolver for the network handlers.

View file

@ -8,6 +8,8 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Dalamud.Plugin.Services;
using Iced.Intel;
using Newtonsoft.Json;
using Serilog;

View file

@ -1,8 +1,9 @@
using System.Diagnostics;
using System.Diagnostics;
using System.IO;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
namespace Dalamud.Game;

View file

@ -3,7 +3,6 @@ using System.Globalization;
using Lumina.Text.ReadOnly;
using DSeString = Dalamud.Game.Text.SeStringHandling.SeString;
using LSeString = Lumina.Text.SeString;
namespace Dalamud.Game.Text.Evaluator;
@ -71,9 +70,6 @@ public readonly struct SeStringParameter
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
[Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData));
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
public static implicit operator SeStringParameter(string value) => new(value);

View file

@ -113,14 +113,6 @@ public class SeString
/// <returns>Equivalent SeString.</returns>
public static implicit operator SeString(string str) => new(new TextPayload(str));
/// <summary>
/// Implicitly convert a string into a SeString containing a <see cref="TextPayload"/>.
/// </summary>
/// <param name="str">string to convert.</param>
/// <returns>Equivalent SeString.</returns>
[Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString();
/// <summary>
/// Parse a binary game message into an SeString.
/// </summary>

View file

@ -21,6 +21,7 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1116:SplitParametersMustStartOnLineAfterDeclaration", Justification = "Reviewed.")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "This would be nice, but a big refactor")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:FileNameMustMatchTypeName", Justification = "I don't like this one so much")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1108:BlockStatementsMustNotContainEmbeddedComments", Justification = "I like having comments in blocks")]
// ImRAII stuff
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "Reviewed.", Scope = "namespaceanddescendants", Target = "Dalamud.Interface.Utility.Raii")]

View file

@ -201,19 +201,19 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
if (EnvironmentConfiguration.DalamudForceMinHook)
useMinHook = true;
using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
if (moduleHandle.IsInvalid)
var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
if (moduleHandle.IsNull)
throw new Exception($"Could not get a handle to module {moduleName}");
var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
if (procAddress == IntPtr.Zero)
var procAddress = Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
if (procAddress.IsNull)
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
procAddress = HookManager.FollowJmp(procAddress);
var address = HookManager.FollowJmp(procAddress.Value);
if (useMinHook)
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
return new MinHookHook<T>(address, detour, Assembly.GetCallingAssembly());
else
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
return new ReloadedHook<T>(address, detour, Assembly.GetCallingAssembly());
}
/// <summary>

File diff suppressed because it is too large Load diff

View file

@ -64,9 +64,9 @@ public interface IObjectWithLocalizableName
var result = new Dictionary<string, string>((int)count);
for (var i = 0u; i < count; i++)
{
fn->GetLocaleName(i, (ushort*)buf, maxStrLen).ThrowOnError();
fn->GetLocaleName(i, buf, maxStrLen).ThrowOnError();
var key = new string(buf);
fn->GetString(i, (ushort*)buf, maxStrLen).ThrowOnError();
fn->GetString(i, buf, maxStrLen).ThrowOnError();
var value = new string(buf);
result[key.ToLowerInvariant()] = value;
}

View file

@ -133,8 +133,8 @@ public sealed class SystemFontFamilyId : IFontFamilyId
var familyIndex = 0u;
BOOL exists = false;
fixed (void* pName = this.EnglishName)
sfc.Get()->FindFamilyName((ushort*)pName, &familyIndex, &exists).ThrowOnError();
fixed (char* pName = this.EnglishName)
sfc.Get()->FindFamilyName(pName, &familyIndex, &exists).ThrowOnError();
if (!exists)
throw new FileNotFoundException($"Font \"{this.EnglishName}\" not found.");

View file

@ -113,8 +113,8 @@ public sealed class SystemFontId : IFontId
var familyIndex = 0u;
BOOL exists = false;
fixed (void* name = this.Family.EnglishName)
sfc.Get()->FindFamilyName((ushort*)name, &familyIndex, &exists).ThrowOnError();
fixed (char* name = this.Family.EnglishName)
sfc.Get()->FindFamilyName(name, &familyIndex, &exists).ThrowOnError();
if (!exists)
throw new FileNotFoundException($"Font \"{this.Family.EnglishName}\" not found.");
@ -151,7 +151,7 @@ public sealed class SystemFontId : IFontId
flocal.Get()->GetFilePathLengthFromKey(refKey, refKeySize, &pathSize).ThrowOnError();
var path = stackalloc char[(int)pathSize + 1];
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, (ushort*)path, pathSize + 1).ThrowOnError();
flocal.Get()->GetFilePathFromKey(refKey, refKeySize, path, pathSize + 1).ThrowOnError();
return (new(path, 0, (int)pathSize), (int)fface.Get()->GetIndex());
}

View file

@ -104,19 +104,19 @@ internal static unsafe class ReShadePeeler
fixed (byte* pfn5 = "glBegin"u8)
fixed (byte* pfn6 = "vkCreateDevice"u8)
{
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0)
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == null)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0)
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == null)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0)
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == null)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0)
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == null)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0)
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == null)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0)
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == null)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0)
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == null)
continue;
}

View file

@ -494,7 +494,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
// (This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same.)
var mousePos = mouseScreenPos;
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0)
ClientToScreen(focusedWindow, &mousePos);
{
// Use game window, otherwise, positions are calculated based on the focused window which might not be the game.
// Leads to offsets.
ClientToScreen(this.hWnd, &mousePos);
}
io.AddMousePosEvent(mousePos.x, mousePos.y);
}
@ -708,7 +713,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
hbrBackground = (HBRUSH)(1 + COLOR.COLOR_BACKGROUND),
lpfnWndProc = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)Marshal
.GetFunctionPointerForDelegate(this.input.wndProcDelegate),
lpszClassName = (ushort*)windowClassNamePtr,
lpszClassName = windowClassNamePtr,
};
if (RegisterClassExW(&wcex) == 0)
@ -737,7 +742,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
fixed (char* windowClassNamePtr = WindowClassName)
{
UnregisterClassW(
(ushort*)windowClassNamePtr,
windowClassNamePtr,
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module));
}
@ -851,8 +856,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
{
data->Hwnd = CreateWindowExW(
(uint)data->DwExStyle,
(ushort*)windowClassNamePtr,
(ushort*)windowClassNamePtr,
windowClassNamePtr,
windowClassNamePtr,
(uint)data->DwStyle,
rect.left,
rect.top,
@ -1066,7 +1071,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
{
var data = (ImGuiViewportDataWin32*)viewport.PlatformUserData;
fixed (char* pwszTitle = MemoryHelper.ReadStringNullTerminated((nint)title))
SetWindowTextW(data->Hwnd, (ushort*)pwszTitle);
SetWindowTextW(data->Hwnd, pwszTitle);
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]

View file

@ -89,7 +89,7 @@ public unsafe ref struct SeStringDrawState
this.splitter = default;
this.GetEntity = ssdp.GetEntity;
this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y));
this.FontSizeScale = this.FontSize / this.Font->FontSize;
this.FontSizeScale = this.FontSize / this.Font.FontSize;
this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight);
this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f;
this.Opacity = ssdp.EffectiveOpacity;
@ -119,7 +119,7 @@ public unsafe ref struct SeStringDrawState
public Vector2 ScreenOffset { get; }
/// <inheritdoc cref="SeStringDrawParams.Font"/>
public ImFont* Font { get; }
public ImFontPtr Font { get; }
/// <inheritdoc cref="SeStringDrawParams.FontSize"/>
public float FontSize { get; }
@ -269,7 +269,7 @@ public unsafe ref struct SeStringDrawState
/// <param name="offset">Offset of the glyph in pixels w.r.t. <see cref="ScreenOffset"/>.</param>
internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset)
{
var texId = this.Font->ContainerAtlas->Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
var texId = this.Font.ContainerAtlas.Textures.Ref<ImFontAtlasTexture>(g.TextureIndex).TexID;
var xy0 = new Vector2(
MathF.Round(g.X0 * this.FontSizeScale),
MathF.Round(g.Y0 * this.FontSizeScale));
@ -326,7 +326,7 @@ public unsafe ref struct SeStringDrawState
offset += this.ScreenOffset;
offset.Y += (this.LinkUnderlineThickness - 1) / 2f;
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale));
offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font.Ascent * this.FontSizeScale));
this.SetCurrentChannel(SeStringDrawChannel.Foreground);
this.DrawList.AddLine(
@ -353,9 +353,9 @@ public unsafe ref struct SeStringDrawState
internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune)
{
var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue
? this.Font->FindGlyph((ushort)rune.Value)
: this.Font->FallbackGlyph;
return ref *(ImGuiHelpers.ImFontGlyphReal*)p;
? (ImFontGlyphPtr)this.Font.FindGlyph((ushort)rune.Value)
: this.Font.FallbackGlyph;
return ref *(ImGuiHelpers.ImFontGlyphReal*)p.Handle;
}
/// <summary>Gets the glyph corresponding to the given codepoint.</summary>
@ -388,7 +388,7 @@ public unsafe ref struct SeStringDrawState
return 0;
return MathF.Round(
this.Font->GetDistanceAdjustmentForPair(
this.Font.GetDistanceAdjustmentForPair(
(ushort)left.Value,
(ushort)right.Value) * this.FontSizeScale);
}

View file

@ -256,7 +256,7 @@ internal partial class InterfaceManager : IInternalDisposableService
var gwh = default(HWND);
fixed (char* pClass = "FFXIVGAME")
{
while ((gwh = FindWindowExW(default, gwh, (ushort*)pClass, default)) != default)
while ((gwh = FindWindowExW(default, gwh, pClass, default)) != default)
{
uint pid;
_ = GetWindowThreadProcessId(gwh, &pid);

View file

@ -63,11 +63,11 @@ internal sealed unsafe partial class ReShadeAddonInterface
return;
bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res)
static bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res)
{
Span<byte> name8 = stackalloc byte[Encoding.UTF8.GetByteCount(name) + 1];
name8[Encoding.UTF8.GetBytes(name, name8)] = 0;
*(nint*)res = GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0]));
*(nint*)res = (nint)GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0]));
return *(nint*)res != 0;
}
}
@ -174,7 +174,7 @@ internal sealed unsafe partial class ReShadeAddonInterface
CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT.CERT_NAME_ISSUER_FLAG,
null,
(ushort*)Unsafe.AsPointer(ref issuerName[0]),
(char*)Unsafe.AsPointer(ref issuerName[0]),
pcb);
if (pcb == 0)
throw new Win32Exception("CertGetNameStringW(2)");

View file

@ -94,7 +94,7 @@ internal static unsafe class ReShadeUnwrapper
static bool HasProcExported(ProcessModule m, ReadOnlySpan<byte> name)
{
fixed (byte* p = name)
return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != 0;
return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != null;
}
}

View file

@ -216,7 +216,7 @@ internal partial class StaThreadService : IInternalDisposableService
lpfnWndProc = &MessageReceiverWndProcStatic,
hInstance = hInstance,
hbrBackground = (HBRUSH)(COLOR.COLOR_BACKGROUND + 1),
lpszClassName = (ushort*)name,
lpszClassName = name,
};
wndClassAtom = RegisterClassExW(&wndClass);
@ -226,8 +226,8 @@ internal partial class StaThreadService : IInternalDisposableService
this.messageReceiverHwndTask.SetResult(
CreateWindowExW(
0,
(ushort*)wndClassAtom,
(ushort*)name,
(char*)wndClassAtom,
name,
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
@ -275,7 +275,7 @@ internal partial class StaThreadService : IInternalDisposableService
_ = OleFlushClipboard();
OleUninitialize();
if (wndClassAtom != 0)
UnregisterClassW((ushort*)wndClassAtom, hInstance);
UnregisterClassW((char*)wndClassAtom, hInstance);
this.messageReceiverHwndTask.TrySetException(e);
}
}

View file

@ -15,7 +15,6 @@ using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
using SharpDX.DXGI;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
@ -749,7 +748,7 @@ internal sealed partial class FontAtlasFactory
new(
width,
height,
(int)(use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm),
(int)(use4 ? DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM : DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM),
width * bpp),
buf,
name);

View file

@ -44,12 +44,12 @@ internal sealed class BitmapCodecInfo : IBitmapCodecInfo
private static unsafe string ReadStringUsing(
IWICBitmapCodecInfo* codecInfo,
delegate* unmanaged<IWICBitmapCodecInfo*, uint, ushort*, uint*, int> readFuncPtr)
delegate* unmanaged[MemberFunction]<IWICBitmapCodecInfo*, uint, char*, uint*, int> readFuncPtr)
{
var cch = 0u;
_ = readFuncPtr(codecInfo, 0, null, &cch);
var buf = stackalloc char[(int)cch + 1];
Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, (ushort*)buf, &cch));
Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, buf, &cch));
return new(buf, 0, (int)cch);
}
}

View file

@ -219,14 +219,14 @@ internal sealed partial class TextureManager
return;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int QueryInterfaceStatic(IUnknown* pThis, Guid* riid, void** ppvObject) =>
ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint AddRefStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0);
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint ReleaseStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0);
}

View file

@ -133,7 +133,7 @@ internal sealed partial class TextureManager
},
},
};
namea.AsSpan().CopyTo(new(fgda.fgd.e0.cFileName, 260));
namea.AsSpan().CopyTo(new(Unsafe.AsPointer(ref fgda.fgd.e0.cFileName[0]), 260));
AddToDataObject(
pdo,
@ -157,7 +157,7 @@ internal sealed partial class TextureManager
},
},
};
preferredFileNameWithoutExtension.AsSpan().CopyTo(new(fgdw.fgd.e0.cFileName, 260));
preferredFileNameWithoutExtension.AsSpan().CopyTo(new(Unsafe.AsPointer(ref fgdw.fgd.e0.cFileName[0]), 260));
AddToDataObject(
pdo,
@ -450,7 +450,7 @@ internal sealed partial class TextureManager
try
{
IStream* pfs;
SHCreateStreamOnFileW((ushort*)pPath, sharedRead, &pfs).ThrowOnError();
SHCreateStreamOnFileW((char*)pPath, sharedRead, &pfs).ThrowOnError();
var stgm2 = new STGMEDIUM
{

View file

@ -13,7 +13,6 @@ using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.Internal;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Plugin;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility;
using Serilog;
@ -151,13 +150,6 @@ public interface IUiBuilder
/// </summary>
public ImFontPtr FontMono { get; }
/// <summary>
/// Gets the game's active Direct3D device.
/// </summary>
// TODO: Remove it on API11/APIXI, and remove SharpDX/PInvoke/etc. dependency from Dalamud.
[Obsolete($"Use {nameof(DeviceHandle)} and wrap it using DirectX wrapper library of your choice.")]
SharpDX.Direct3D11.Device Device { get; }
/// <summary>Gets the game's active Direct3D device.</summary>
/// <value>Pointer to the instance of IUnknown that the game is using and should be containing an ID3D11Device,
/// or 0 if it is not available yet.</value>
@ -227,6 +219,12 @@ public interface IUiBuilder
/// </summary>
bool ShouldUseReducedMotion { get; }
/// <summary>
/// Gets a value indicating whether the user has enabled the "Enable sound effects for plugin windows" setting.<br />
/// This setting is effected by the in-game "System Sounds" option and volume.
/// </summary>
bool PluginUISoundEffectsEnabled { get; }
/// <summary>
/// Loads an ULD file that can load textures containing multiple icons in a single texture.
/// </summary>
@ -303,8 +301,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
private IFontHandle? monoFontHandle;
private IFontHandle? iconFontFixedWidthHandle;
private SharpDX.Direct3D11.Device? sdxDevice;
/// <summary>
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
/// You do not have to call this manually.
@ -494,12 +490,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
this.InterfaceManagerWithScene?.MonoFontHandle
?? throw new InvalidOperationException("Scene is not yet ready.")));
/// <inheritdoc/>
// TODO: Remove it on API11/APIXI, and remove SharpDX/PInvoke/etc. dependency from Dalamud.
[Obsolete($"Use {nameof(DeviceHandle)} and wrap it using DirectX wrapper library of your choice.")]
public SharpDX.Direct3D11.Device Device =>
this.sdxDevice ??= new(this.InterfaceManagerWithScene!.Backend!.DeviceHandle);
/// <inheritdoc/>
public nint DeviceHandle => this.InterfaceManagerWithScene?.Backend?.DeviceHandle ?? 0;
@ -576,6 +566,9 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
/// </summary>
public bool ShouldUseReducedMotion => Service<DalamudConfiguration>.Get().ReduceMotions ?? false;
/// <inheritdoc />
public bool PluginUISoundEffectsEnabled => Service<DalamudConfiguration>.Get().EnablePluginUISoundEffects;
/// <summary>
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
/// </summary>

View file

@ -84,7 +84,7 @@ public abstract class Window
Click = _ =>
{
this.internalIsClickthrough = false;
this.presetDirty = false;
this.presetDirty = true;
ImGui.OpenPopup(AdditionsPopupName);
},
Priority = int.MinValue,
@ -905,7 +905,7 @@ public abstract class Window
private void DrawErrorMessage()
{
// TODO: Once window systems are services, offer to reload the plugin
ImGui.TextColoredWrapped(ImGuiColors.DalamudRed,Loc.Localize("WindowSystemErrorOccurred", "An error occurred while rendering this window. Please contact the developer for details."));
ImGui.TextColoredWrapped(ImGuiColors.DalamudRed, Loc.Localize("WindowSystemErrorOccurred", "An error occurred while rendering this window. Please contact the developer for details."));
ImGuiHelpers.ScaledDummy(5);

View file

@ -1,4 +1,5 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"useSafeHandles": false,
"allowMarshaling": false
}

View file

@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
namespace Dalamud.Networking.Rpc.Model;
/// <summary>
/// A Dalamud Uri, in the format:
/// <code>dalamud://{NAMESPACE}/{ARBITRARY}</code>
/// </summary>
public record DalamudUri
{
private readonly Uri rawUri;
private DalamudUri(Uri uri)
{
if (uri.Scheme != "dalamud")
{
throw new ArgumentOutOfRangeException(nameof(uri), "URI must be of scheme dalamud.");
}
this.rawUri = uri;
}
/// <summary>
/// Gets the namespace that this URI should be routed to. Generally a high level component like "PluginInstaller".
/// </summary>
public string Namespace => this.rawUri.Authority;
/// <summary>
/// Gets the raw (untargeted) path and query params for this URI.
/// </summary>
public string Data =>
this.rawUri.GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.UriEscaped);
/// <summary>
/// Gets the raw (untargeted) path for this URI.
/// </summary>
public string Path => this.rawUri.AbsolutePath;
/// <summary>
/// Gets a list of segments based on the provided Data element.
/// </summary>
public string[] Segments => this.GetDataSegments();
/// <summary>
/// Gets the raw query parameters for this URI, if any.
/// </summary>
public string Query => this.rawUri.Query;
/// <summary>
/// Gets the query params (as a parsed NameValueCollection) in this URI.
/// </summary>
public NameValueCollection QueryParams => HttpUtility.ParseQueryString(this.Query);
/// <summary>
/// Gets the fragment (if one is specified) in this URI.
/// </summary>
public string Fragment => this.rawUri.Fragment;
/// <inheritdoc/>
public override string ToString() => this.rawUri.ToString();
/// <summary>
/// Build a DalamudURI from a given URI.
/// </summary>
/// <param name="uri">The URI to convert to a Dalamud URI.</param>
/// <returns>Returns a DalamudUri.</returns>
public static DalamudUri FromUri(Uri uri)
{
return new DalamudUri(uri);
}
/// <summary>
/// Build a DalamudURI from a URI in string format.
/// </summary>
/// <param name="uri">The URI to convert to a Dalamud URI.</param>
/// <returns>Returns a DalamudUri.</returns>
public static DalamudUri FromUri(string uri) => FromUri(new Uri(uri));
private string[] GetDataSegments()
{
// reimplementation of the System.URI#Segments, under MIT license.
var path = this.Path;
var segments = new List<string>();
var current = 0;
while (current < path.Length)
{
var next = path.IndexOf('/', current);
if (next == -1)
{
next = path.Length - 1;
}
segments.Add(path.Substring(current, (next - current) + 1));
current = next + 1;
}
return segments.ToArray();
}
}

View file

@ -0,0 +1,95 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Networking.Rpc.Service;
using Serilog;
using StreamJsonRpc;
namespace Dalamud.Networking.Rpc;
/// <summary>
/// A single RPC client session connected via a stream (named pipe or Unix socket).
/// </summary>
internal class RpcConnection : IDisposable
{
private readonly Stream stream;
private readonly RpcServiceRegistry registry;
private readonly CancellationTokenSource cts = new();
/// <summary>
/// Initializes a new instance of the <see cref="RpcConnection"/> class.
/// </summary>
/// <param name="stream">The stream that this connection will handle.</param>
/// <param name="registry">A registry of RPC services.</param>
public RpcConnection(Stream stream, RpcServiceRegistry registry)
{
this.Id = Guid.CreateVersion7();
this.stream = stream;
this.registry = registry;
var formatter = new JsonMessageFormatter();
var handler = new HeaderDelimitedMessageHandler(stream, stream, formatter);
this.Rpc = new JsonRpc(handler);
this.Rpc.AllowModificationWhileListening = true;
this.Rpc.Disconnected += this.OnDisconnected;
this.registry.Attach(this.Rpc);
this.Rpc.StartListening();
}
/// <summary>
/// Gets the GUID for this connection.
/// </summary>
public Guid Id { get; }
/// <summary>
/// Gets the JsonRpc instance for this connection.
/// </summary>
public JsonRpc Rpc { get; }
/// <summary>
/// Gets a task that's called on RPC completion.
/// </summary>
public Task Completion => this.Rpc.Completion;
/// <inheritdoc/>
public void Dispose()
{
if (!this.cts.IsCancellationRequested)
{
this.cts.Cancel();
}
try
{
this.Rpc.Dispose();
}
catch (Exception ex)
{
Log.Debug(ex, "Error disposing JsonRpc for client {Id}", this.Id);
}
try
{
this.stream.Dispose();
}
catch (Exception ex)
{
Log.Debug(ex, "Error disposing stream for client {Id}", this.Id);
}
this.cts.Dispose();
GC.SuppressFinalize(this);
}
private void OnDisconnected(object? sender, JsonRpcDisconnectedEventArgs e)
{
Log.Debug("RPC client {Id} disconnected: {Reason}", this.Id, e.Description);
this.registry.Detach(this.Rpc);
this.Dispose();
}
}

View file

@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Dalamud.Logging.Internal;
using Dalamud.Networking.Rpc.Transport;
namespace Dalamud.Networking.Rpc;
/// <summary>
/// The Dalamud service repsonsible for hosting the RPC.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class RpcHostService : IServiceType, IInternalDisposableService
{
private readonly ModuleLog log = new("RPC");
private readonly RpcServiceRegistry registry = new();
private readonly List<IRpcTransport> transports = [];
/// <summary>
/// Initializes a new instance of the <see cref="RpcHostService"/> class.
/// </summary>
[ServiceManager.ServiceConstructor]
public RpcHostService()
{
this.StartUnixTransport();
if (this.transports.Count == 0)
{
this.log.Warning("No RPC hosts could be started on this platform");
}
}
/// <summary>
/// Gets all active RPC transports.
/// </summary>
public IReadOnlyList<IRpcTransport> Transports => this.transports;
/// <summary>
/// Add a new service Object to the RPC host.
/// </summary>
/// <param name="service">The object to add.</param>
public void AddService(object service) => this.registry.AddService(service);
/// <summary>
/// Add a new standalone method to the RPC host.
/// </summary>
/// <param name="name">The method name to add.</param>
/// <param name="handler">The handler to add.</param>
public void AddMethod(string name, Delegate handler) => this.registry.AddMethod(name, handler);
/// <inheritdoc/>
public void DisposeService()
{
foreach (var host in this.transports)
{
host.Dispose();
}
this.transports.Clear();
}
/// <inheritdoc cref="IRpcTransport.InvokeClientAsync"/>
public async Task<T> InvokeClientAsync<T>(Guid clientId, string method, params object[] arguments)
{
var clients = this.transports.SelectMany(t => t.Connections).ToImmutableDictionary();
if (!clients.TryGetValue(clientId, out var session))
throw new KeyNotFoundException($"No client {clientId}");
return await session.Rpc.InvokeAsync<T>(method, arguments).ConfigureAwait(false);
}
/// <inheritdoc cref="IRpcTransport.BroadcastNotifyAsync"/>
public async Task BroadcastNotifyAsync(string method, params object[] arguments)
{
await foreach (var transport in this.transports.ToAsyncEnumerable().ConfigureAwait(false))
{
await transport.BroadcastNotifyAsync(method, arguments).ConfigureAwait(false);
}
}
private void StartUnixTransport()
{
var transport = new UnixRpcTransport(this.registry);
this.transports.Add(transport);
transport.Start();
this.log.Information("RpcHostService listening to UNIX socket: {Socket}", transport.SocketPath);
}
}

View file

@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Threading;
using StreamJsonRpc;
namespace Dalamud.Networking.Rpc;
/// <summary>
/// Thread-safe registry of local RPC target objects that are exposed to every connected JsonRpc session.
/// New sessions get all previously registered targets; newly added targets are attached to all active sessions.
/// </summary>
internal class RpcServiceRegistry
{
private readonly Lock sync = new();
private readonly List<object> targets = [];
private readonly List<(string Name, Delegate Handler)> methods = [];
private readonly List<JsonRpc> activeRpcs = [];
/// <summary>
/// Registers a new local RPC target object. Its public JSON-RPC methods become callable by clients.
/// Adds <paramref name="service"/> to the registry and attaches it to all active RPC sessions.
/// </summary>
/// <param name="service">The service instance containing JSON-RPC callable methods to expose.</param>
public void AddService(object service)
{
lock (this.sync)
{
this.targets.Add(service);
foreach (var rpc in this.activeRpcs)
{
rpc.AddLocalRpcTarget(service);
}
}
}
/// <summary>
/// Registers a new standalone JSON-RPC method.
/// </summary>
/// <param name="name">The name of the method to add.</param>
/// <param name="handler">The handler to add.</param>
public void AddMethod(string name, Delegate handler)
{
lock (this.sync)
{
this.methods.Add((name, handler));
foreach (var rpc in this.activeRpcs)
{
rpc.AddLocalRpcMethod(name, handler);
}
}
}
/// <summary>
/// Attaches a JsonRpc instance <paramref name="rpc"/> to the registry so it receives all existing service targets.
/// </summary>
/// <param name="rpc">The JsonRpc instance to attach and populate with current targets.</param>
internal void Attach(JsonRpc rpc)
{
lock (this.sync)
{
this.activeRpcs.Add(rpc);
foreach (var t in this.targets)
{
rpc.AddLocalRpcTarget(t);
}
foreach (var m in this.methods)
{
rpc.AddLocalRpcMethod(m.Name, m.Handler);
}
}
}
/// <summary>
/// Detaches a JsonRpc instance <paramref name="rpc"/> from the registry (e.g. when a client disconnects).
/// </summary>
/// <param name="rpc">The JsonRpc instance being detached.</param>
internal void Detach(JsonRpc rpc)
{
lock (this.sync)
{
this.activeRpcs.Remove(rpc);
}
}
}

View file

@ -0,0 +1,133 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Utility;
using Lumina.Excel.Sheets;
namespace Dalamud.Networking.Rpc.Service;
/// <summary>
/// A minimal service to respond with information about this client.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal sealed class ClientHelloService : IInternalDisposableService
{
/// <summary>
/// Initializes a new instance of the <see cref="ClientHelloService"/> class.
/// </summary>
/// <param name="rpcHostService">Injected host service.</param>
[ServiceManager.ServiceConstructor]
public ClientHelloService(RpcHostService rpcHostService)
{
rpcHostService.AddMethod("hello", this.HandleHello);
}
/// <summary>
/// Handle a hello request.
/// </summary>
/// <param name="request">.</param>
/// <returns>Respond with information.</returns>
public async Task<ClientHelloResponse> HandleHello(ClientHelloRequest request)
{
var dalamud = await Service<Dalamud>.GetAsync();
return new ClientHelloResponse
{
ApiVersion = "1.0",
DalamudVersion = Util.GetScmVersion(),
GameVersion = dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown",
ProcessId = Environment.ProcessId,
ProcessStartTime = new DateTimeOffset(Process.GetCurrentProcess().StartTime).ToUnixTimeSeconds(),
ClientState = await this.GetClientIdentifier(),
};
}
/// <inheritdoc/>
public void DisposeService()
{
}
private async Task<string> GetClientIdentifier()
{
var framework = await Service<Framework>.GetAsync();
var clientState = await Service<ClientState>.GetAsync();
var dataManager = await Service<DataManager>.GetAsync();
var clientIdentifier = $"FFXIV Process ${Environment.ProcessId}";
await framework.RunOnFrameworkThread(() =>
{
if (clientState.IsLoggedIn)
{
var player = clientState.LocalPlayer;
if (player != null)
{
var world = dataManager.GetExcelSheet<World>().GetRow(player.HomeWorld.RowId);
clientIdentifier = $"Logged in as {player.Name.TextValue} @ {world.Name.ExtractText()}";
}
}
else
{
clientIdentifier = "On login screen";
}
});
return clientIdentifier;
}
}
/// <summary>
/// A request from a client to say hello.
/// </summary>
internal record ClientHelloRequest
{
/// <summary>
/// Gets the API version this client is expecting.
/// </summary>
public string ApiVersion { get; init; } = string.Empty;
/// <summary>
/// Gets the user agent of the client.
/// </summary>
public string UserAgent { get; init; } = string.Empty;
}
/// <summary>
/// A response from Dalamud to a hello request.
/// </summary>
internal record ClientHelloResponse
{
/// <summary>
/// Gets the API version this server has offered.
/// </summary>
public string? ApiVersion { get; init; }
/// <summary>
/// Gets the current Dalamud version.
/// </summary>
public string? DalamudVersion { get; init; }
/// <summary>
/// Gets the current game version.
/// </summary>
public string? GameVersion { get; init; }
/// <summary>
/// Gets the process ID of this client.
/// </summary>
public int? ProcessId { get; init; }
/// <summary>
/// Gets the time this process started.
/// </summary>
public long? ProcessStartTime { get; init; }
/// <summary>
/// Gets a state for this client for user display.
/// </summary>
public string? ClientState { get; init; }
}

View file

@ -0,0 +1,107 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using Dalamud.Logging.Internal;
using Dalamud.Networking.Rpc.Model;
using Dalamud.Utility;
namespace Dalamud.Networking.Rpc.Service;
/// <summary>
/// A service responsible for handling Dalamud URIs and dispatching them accordingly.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class LinkHandlerService : IInternalDisposableService
{
private readonly ModuleLog log = new("LinkHandler");
// key: namespace (e.g. "plugin" or "PluginInstaller") -> list of handlers
private readonly ConcurrentDictionary<string, List<Action<DalamudUri>>> handlers
= new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Initializes a new instance of the <see cref="LinkHandlerService"/> class.
/// </summary>
/// <param name="rpcHostService">The injected RPC host service.</param>
[ServiceManager.ServiceConstructor]
public LinkHandlerService(RpcHostService rpcHostService)
{
rpcHostService.AddMethod("handleLink", this.HandleLinkCall);
}
/// <inheritdoc/>
public void DisposeService()
{
}
/// <summary>
/// Register a handler for a namespace. All URIs with this namespace will be dispatched to the handler.
/// </summary>
/// <param name="ns">The namespace to use for this subscription.</param>
/// <param name="handler">The command handler.</param>
public void Register(string ns, Action<DalamudUri> handler)
{
if (string.IsNullOrWhiteSpace(ns))
throw new ArgumentNullException(nameof(ns));
var list = this.handlers.GetOrAdd(ns, _ => []);
lock (list)
{
list.Add(handler);
}
this.log.Verbose("Registered handler for {Namespace}", ns);
}
/// <summary>
/// Unregister a handler.
/// </summary>
/// <param name="ns">The namespace to use for this subscription.</param>
/// <param name="handler">The command handler.</param>
public void Unregister(string ns, Action<DalamudUri> handler)
{
if (string.IsNullOrWhiteSpace(ns))
return;
if (!this.handlers.TryGetValue(ns, out var list))
return;
list.RemoveAll(x => x == handler);
if (list.Count == 0)
this.handlers.TryRemove(ns, out _);
this.log.Verbose("Unregistered handler for {Namespace}", ns);
}
/// <summary>
/// Dispatch a URI to matching handlers.
/// </summary>
/// <param name="uri">The URI to parse and dispatch.</param>
public void Dispatch(DalamudUri uri)
{
this.log.Information("Received URI: {Uri}", uri.ToString());
var ns = uri.Namespace;
if (!this.handlers.TryGetValue(ns, out var actions))
return;
foreach (var h in actions)
{
h.InvokeSafely(uri);
}
}
/// <summary>
/// The RPC-invokable link handler.
/// </summary>
/// <param name="uri">A plain-text URI to parse.</param>
public void HandleLinkCall(string uri)
{
if (string.IsNullOrWhiteSpace(uri))
return;
var du = DalamudUri.FromUri(uri);
this.Dispatch(du);
}
}

View file

@ -0,0 +1,67 @@
using Dalamud.Game.Gui.Toast;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification.Internal;
using Dalamud.Networking.Rpc.Model;
namespace Dalamud.Networking.Rpc.Service.Links;
#if DEBUG
/// <summary>
/// A debug controller for link handling.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal sealed class DebugLinkHandler : IInternalDisposableService
{
private readonly LinkHandlerService linkHandlerService;
/// <summary>
/// Initializes a new instance of the <see cref="DebugLinkHandler"/> class.
/// </summary>
/// <param name="linkHandler">Injected LinkHandler.</param>
[ServiceManager.ServiceConstructor]
public DebugLinkHandler(LinkHandlerService linkHandler)
{
this.linkHandlerService = linkHandler;
this.linkHandlerService.Register("debug", this.HandleLink);
}
/// <inheritdoc/>
public void DisposeService()
{
this.linkHandlerService.Unregister("debug", this.HandleLink);
}
private void HandleLink(DalamudUri uri)
{
var action = uri.Path.Split("/").GetValue(1)?.ToString();
switch (action)
{
case "toast":
this.ShowToast(uri);
break;
case "notification":
this.ShowNotification(uri);
break;
}
}
private void ShowToast(DalamudUri uri)
{
var message = uri.QueryParams.Get("message") ?? "Hello, world!";
Service<ToastGui>.Get().ShowNormal(message);
}
private void ShowNotification(DalamudUri uri)
{
Service<NotificationManager>.Get().AddNotification(
new Notification
{
Title = uri.QueryParams.Get("title"),
Content = uri.QueryParams.Get("content") ?? "Hello, world!",
});
}
}
#endif

View file

@ -0,0 +1,57 @@
using System.Linq;
using Dalamud.Console;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Networking.Rpc.Model;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
#pragma warning disable DAL_RPC
namespace Dalamud.Networking.Rpc.Service.Links;
/// <inheritdoc cref="IPluginLinkHandler" />
[PluginInterface]
[ServiceManager.ScopedService]
[ResolveVia<IPluginLinkHandler>]
public class PluginLinkHandler : IInternalDisposableService, IPluginLinkHandler
{
private readonly LinkHandlerService linkHandler;
private readonly LocalPlugin localPlugin;
/// <summary>
/// Initializes a new instance of the <see cref="PluginLinkHandler"/> class.
/// </summary>
/// <param name="localPlugin">The plugin to bind this service to.</param>
/// <param name="linkHandler">The central link handler.</param>
internal PluginLinkHandler(LocalPlugin localPlugin, LinkHandlerService linkHandler)
{
this.linkHandler = linkHandler;
this.localPlugin = localPlugin;
this.linkHandler.Register("plugin", this.HandleUri);
}
/// <inheritdoc/>
public event IPluginLinkHandler.PluginUriReceived? OnUriReceived;
/// <inheritdoc/>
public void DisposeService()
{
this.OnUriReceived = null;
this.linkHandler.Unregister("plugin", this.HandleUri);
}
private void HandleUri(DalamudUri uri)
{
var target = uri.Path.Split("/").ElementAtOrDefault(1);
var thisPlugin = ConsoleManagerPluginUtil.GetSanitizedNamespaceName(this.localPlugin.InternalName);
if (target == null || !string.Equals(target, thisPlugin, StringComparison.OrdinalIgnoreCase))
{
return;
}
this.OnUriReceived?.Invoke(uri);
}
}

View file

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Dalamud.Networking.Rpc.Transport;
/// <summary>
/// Interface for RPC host implementations (named pipes or Unix sockets).
/// </summary>
internal interface IRpcTransport : IDisposable
{
/// <summary>
/// Gets a list of active RPC connections.
/// </summary>
IReadOnlyDictionary<Guid, RpcConnection> Connections { get; }
/// <summary>Starts accepting client connections.</summary>
void Start();
/// <summary>Invoke an RPC request on a specific client expecting a result.</summary>
/// <param name="clientId">The client ID to invoke.</param>
/// <param name="method">The method to invoke.</param>
/// <param name="arguments">Any arguments to invoke.</param>
/// <returns>An optional return based on the specified RPC.</returns>
/// <typeparam name="T">The expected response type.</typeparam>
Task<T> InvokeClientAsync<T>(Guid clientId, string method, params object[] arguments);
/// <summary>Send a notification to all connected clients (no response expected).</summary>
/// <param name="method">The method name to broadcast.</param>
/// <param name="arguments">The arguments to broadcast.</param>
/// <returns>Returns a Task when completed.</returns>
Task BroadcastNotifyAsync(string method, params object[] arguments);
}

View file

@ -0,0 +1,207 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Logging.Internal;
using Dalamud.Utility;
namespace Dalamud.Networking.Rpc.Transport;
/// <summary>
/// Simple multi-client JSON-RPC Unix socket host using StreamJsonRpc.
/// </summary>
internal class UnixRpcTransport : IRpcTransport
{
private readonly ModuleLog log = new("RPC/Transport/UnixSocket");
private readonly RpcServiceRegistry registry;
private readonly CancellationTokenSource cts = new();
private readonly ConcurrentDictionary<Guid, RpcConnection> sessions = new();
private readonly string? cleanupSocketDirectory;
private Task? acceptLoopTask;
private Socket? listenSocket;
/// <summary>
/// Initializes a new instance of the <see cref="UnixRpcTransport"/> class.
/// </summary>
/// <param name="registry">The RPC service registry to use.</param>
/// <param name="socketDirectory">The Unix socket directory to use. If null, defaults to Dalamud home directory.</param>
/// <param name="socketName">The name of the socket to create.</param>
public UnixRpcTransport(RpcServiceRegistry registry, string? socketDirectory = null, string? socketName = null)
{
this.registry = registry;
socketName ??= $"DalamudRPC.{Environment.ProcessId}.sock";
if (!socketDirectory.IsNullOrEmpty())
{
this.SocketPath = Path.Combine(socketDirectory, socketName);
}
else
{
socketDirectory = Service<Dalamud>.Get().StartInfo.TempDirectory;
if (socketDirectory == null)
{
this.SocketPath = Path.Combine(Path.GetTempPath(), socketName);
this.log.Warning("Temp dir was not set in StartInfo; using system temp for unix socket.");
}
else
{
this.SocketPath = Path.Combine(socketDirectory, socketName);
this.cleanupSocketDirectory = socketDirectory;
}
}
}
/// <summary>
/// Gets the path of the Unix socket this RPC host is using.
/// </summary>
public string SocketPath { get; }
/// <inheritdoc/>
public IReadOnlyDictionary<Guid, RpcConnection> Connections => this.sessions;
/// <summary>Starts accepting client connections.</summary>
public void Start()
{
if (this.acceptLoopTask != null) return;
// Make the directory for the socket if it doesn't exist
var socketDir = Path.GetDirectoryName(this.SocketPath);
if (!string.IsNullOrEmpty(socketDir) && !Directory.Exists(socketDir))
{
this.log.Error("Directory for unix socket does not exist: {Path}", socketDir);
return;
}
// Delete existing socket for this PID, if it exists.
if (File.Exists(this.SocketPath))
{
try
{
File.Delete(this.SocketPath);
}
catch (Exception ex)
{
this.log.Warning(ex, "Failed to delete existing socket file: {Path}", this.SocketPath);
}
}
this.acceptLoopTask = Task.Factory.StartNew(this.AcceptLoopAsync, TaskCreationOptions.LongRunning);
}
/// <summary>Invoke an RPC request on a specific client expecting a result.</summary>
/// <param name="clientId">The client ID to invoke.</param>
/// <param name="method">The method to invoke.</param>
/// <param name="arguments">Any arguments to invoke.</param>
/// <returns>An optional return based on the specified RPC.</returns>
/// <typeparam name="T">The expected response type.</typeparam>
public Task<T> InvokeClientAsync<T>(Guid clientId, string method, params object[] arguments)
{
if (!this.sessions.TryGetValue(clientId, out var session))
throw new KeyNotFoundException($"No client {clientId}");
return session.Rpc.InvokeAsync<T>(method, arguments);
}
/// <summary>Send a notification to all connected clients (no response expected).</summary>
/// <param name="method">The method name to broadcast.</param>
/// <param name="arguments">The arguments to broadcast.</param>
/// <returns>Returns a Task when completed.</returns>
public Task BroadcastNotifyAsync(string method, params object[] arguments)
{
var list = this.sessions.Values;
var tasks = new List<Task>(list.Count);
foreach (var s in list)
{
tasks.Add(s.Rpc.NotifyAsync(method, arguments));
}
return Task.WhenAll(tasks);
}
/// <inheritdoc/>
public void Dispose()
{
this.cts.Cancel();
this.acceptLoopTask?.Wait(1000);
foreach (var kv in this.sessions)
{
kv.Value.Dispose();
}
this.sessions.Clear();
this.listenSocket?.Dispose();
if (File.Exists(this.SocketPath))
{
try
{
File.Delete(this.SocketPath);
}
catch (Exception ex)
{
this.log.Warning(ex, "Failed to delete socket file on dispose: {Path}", this.SocketPath);
}
}
this.cts.Dispose();
this.log.Information("UnixRpcHost disposed ({Socket})", this.SocketPath);
GC.SuppressFinalize(this);
}
private async Task AcceptLoopAsync()
{
var token = this.cts.Token;
try
{
var endpoint = new UnixDomainSocketEndPoint(this.SocketPath);
this.listenSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
this.listenSocket.Bind(endpoint);
this.listenSocket.Listen(128);
while (!token.IsCancellationRequested)
{
Socket? clientSocket = null;
try
{
clientSocket = await this.listenSocket.AcceptAsync(token).ConfigureAwait(false);
var stream = new NetworkStream(clientSocket, ownsSocket: true);
var session = new RpcConnection(stream, this.registry);
this.sessions.TryAdd(session.Id, session);
this.log.Debug("RPC connection created: {Id}", session.Id);
_ = session.Completion.ContinueWith(t =>
{
this.sessions.TryRemove(session.Id, out _);
this.log.Debug("RPC connection removed: {Id}", session.Id);
}, TaskScheduler.Default);
}
catch (OperationCanceledException)
{
clientSocket?.Dispose();
break;
}
catch (Exception ex)
{
clientSocket?.Dispose();
this.log.Error(ex, "Error in socket accept loop");
await Task.Delay(500, token).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
this.log.Error(ex, "Fatal error in Unix socket accept loop");
}
}
}

View file

@ -0,0 +1,24 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.Networking.Rpc.Model;
namespace Dalamud.Plugin.Services;
/// <summary>
/// A service to allow plugins to subscribe to dalamud:// URIs targeting them. Plugins will receive any URI sent to the
/// <c>dalamud://plugin/{PLUGIN_INTERNAL_NAME}/...</c> namespace.
/// </summary>
[Experimental("DAL_RPC", Message = "This service will be finalized around 7.41 and may change before then.")]
public interface IPluginLinkHandler : IDalamudService
{
/// <summary>
/// A delegate containing the received URI.
/// </summary>
/// <param name="uri">The URI opened by the user.</param>
public delegate void PluginUriReceived(DalamudUri uri);
/// <summary>
/// The event fired when a URI targeting this plugin is received.
/// </summary>
event PluginUriReceived OnUriReceived;
}

View file

@ -1,6 +1,8 @@
using System.Collections.Generic;
namespace Dalamud.Plugin.SelfTest;
using Dalamud.Plugin.SelfTest;
namespace Dalamud.Plugin.Services;
/// <summary>
/// Interface for registering and unregistering self-test steps from plugins.
@ -44,7 +46,7 @@ namespace Dalamud.Plugin.SelfTest;
/// }
/// </code>
/// </example>
public interface ISelfTestRegistry
public interface ISelfTestRegistry : IDalamudService
{
/// <summary>
/// Registers the self-test steps for this plugin.

View file

@ -2,9 +2,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Dalamud.Plugin.Services;
namespace Dalamud.Game;
namespace Dalamud.Plugin.Services;
/// <summary>
/// A SigScanner facilitates searching for memory signatures in a given ProcessModule.

View file

@ -1,7 +1,7 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
namespace Dalamud.Game.ClientState.Objects;
namespace Dalamud.Plugin.Services;
/// <summary>
/// Get and set various kinds of targets for the player.
@ -37,13 +37,13 @@ public interface ITargetManager : IDalamudService
/// Set to null to clear the target.
/// </summary>
public IGameObject? SoftTarget { get; set; }
/// <summary>
/// Gets or sets the gpose target.
/// Set to null to clear the target.
/// </summary>
public IGameObject? GPoseTarget { get; set; }
/// <summary>
/// Gets or sets the mouseover nameplate target.
/// Set to null to clear the target.

View file

@ -1,6 +1,8 @@
using System.Runtime.InteropServices;
using System.Text;
using Windows.Win32.Foundation;
namespace Dalamud;
/// <summary>
@ -12,11 +14,11 @@ namespace Dalamud;
/// </remarks>
public static class SafeMemory
{
private static readonly SafeHandle Handle;
private static readonly HANDLE Handle;
static SafeMemory()
{
Handle = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
Handle = Windows.Win32.PInvoke.GetCurrentProcess();
}
/// <summary>
@ -28,6 +30,12 @@ public static class SafeMemory
/// <returns>Whether the read succeeded.</returns>
public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer)
{
if (Handle.IsNull)
{
buffer = [];
return false;
}
buffer = new byte[count <= 0 ? 0 : count];
fixed (byte* p = buffer)
{
@ -54,6 +62,9 @@ public static class SafeMemory
/// <returns>Whether the write succeeded.</returns>
public static unsafe bool WriteBytes(IntPtr address, byte[] buffer)
{
if (Handle.IsNull)
return false;
if (buffer.Length == 0)
return true;

View file

@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
@ -294,18 +294,18 @@ internal sealed class LoadingDialog
? null
: Icon.ExtractAssociatedIcon(Path.Combine(workingDirectory, "Dalamud.Injector.exe"));
fixed (void* pszEmpty = "-")
fixed (void* pszWindowTitle = "Dalamud")
fixed (void* pszDalamudBoot = "Dalamud.Boot.dll")
fixed (void* pszThemesManifestResourceName = "RT_MANIFEST_THEMES")
fixed (void* pszHide = Loc.Localize("LoadingDialogHide", "Hide"))
fixed (void* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs"))
fixed (void* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs"))
fixed (char* pszEmpty = "-")
fixed (char* pszWindowTitle = "Dalamud")
fixed (char* pszDalamudBoot = "Dalamud.Boot.dll")
fixed (char* pszThemesManifestResourceName = "RT_MANIFEST_THEMES")
fixed (char* pszHide = Loc.Localize("LoadingDialogHide", "Hide"))
fixed (char* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs"))
fixed (char* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs"))
{
var taskDialogButton = new TASKDIALOG_BUTTON
{
nButtonID = IDOK,
pszButtonText = (ushort*)pszHide,
pszButtonText = pszHide,
};
var taskDialogConfig = new TASKDIALOGCONFIG
{
@ -318,8 +318,8 @@ internal sealed class LoadingDialog
(int)TDF_CALLBACK_TIMER |
(extractedIcon is null ? 0 : (int)TDF_USE_HICON_MAIN),
dwCommonButtons = 0,
pszWindowTitle = (ushort*)pszWindowTitle,
pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (ushort*)extractedIcon.Handle,
pszWindowTitle = pszWindowTitle,
pszMainIcon = extractedIcon is null ? TD.TD_INFORMATION_ICON : (char*)extractedIcon.Handle,
pszMainInstruction = null,
pszContent = null,
cButtons = 1,
@ -329,9 +329,9 @@ internal sealed class LoadingDialog
pRadioButtons = null,
nDefaultRadioButton = 0,
pszVerificationText = null,
pszExpandedInformation = (ushort*)pszEmpty,
pszExpandedControlText = (ushort*)pszShowLatestLogs,
pszCollapsedControlText = (ushort*)pszHideLatestLogs,
pszExpandedInformation = pszEmpty,
pszExpandedControlText = pszShowLatestLogs,
pszCollapsedControlText = pszHideLatestLogs,
pszFooterIcon = null,
pszFooter = null,
pfCallback = &HResultFuncBinder,
@ -348,8 +348,8 @@ internal sealed class LoadingDialog
{
cbSize = (uint)sizeof(ACTCTXW),
dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID,
lpResourceName = (ushort*)pszThemesManifestResourceName,
hModule = GetModuleHandleW((ushort*)pszDalamudBoot),
lpResourceName = pszThemesManifestResourceName,
hModule = GetModuleHandleW(pszDalamudBoot),
};
hActCtx = CreateActCtxW(&actctx);
if (hActCtx == default)

View file

@ -11,12 +11,12 @@ public enum DalamudAssetPurpose
Empty = 0,
/// <summary>
/// The asset is a .png file, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>.
/// The asset is a .png file, and can be purposed as a <see cref="TerraFX.Interop.DirectX.ID3D11Texture2D"/>.
/// </summary>
TextureFromPng = 10,
/// <summary>
/// The asset is a raw texture, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>.
/// The asset is a raw texture, and can be purposed as a <see cref="TerraFX.Interop.DirectX.ID3D11Texture2D"/>.
/// </summary>
TextureFromRaw = 1001,

View file

@ -30,8 +30,8 @@ internal static class ClipboardFormats
private static unsafe uint ClipboardFormatFromName(ReadOnlySpan<char> name)
{
uint cf;
fixed (void* p = name)
cf = RegisterClipboardFormatW((ushort*)p);
fixed (char* p = name)
cf = RegisterClipboardFormatW(p);
if (cf != 0)
return cf;
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()) ??

View file

@ -1,7 +1,8 @@
using System.ComponentModel;
using System.ComponentModel;
using System.IO;
using System.Text;
using Windows.Win32.Foundation;
using Windows.Win32.Storage.FileSystem;
namespace Dalamud.Utility;
@ -47,30 +48,39 @@ public static class FilesystemUtil
// Open the temp file
var tempPath = path + ".tmp";
using var tempFile = Windows.Win32.PInvoke.CreateFile(
var tempFile = Windows.Win32.PInvoke.CreateFile(
tempPath,
(uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE),
FILE_SHARE_MODE.FILE_SHARE_NONE,
null,
FILE_CREATION_DISPOSITION.CREATE_ALWAYS,
FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL,
null);
HANDLE.Null);
if (tempFile.IsInvalid)
if (tempFile.IsNull)
throw new Win32Exception();
// Write the data
uint bytesWritten = 0;
if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan<byte>(bytes), &bytesWritten, null))
throw new Win32Exception();
fixed (byte* ptr = bytes)
{
if (!Windows.Win32.PInvoke.WriteFile(tempFile, ptr, (uint)bytes.Length, &bytesWritten, null))
throw new Win32Exception();
}
if (bytesWritten != bytes.Length)
{
Windows.Win32.PInvoke.CloseHandle(tempFile);
throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})");
}
if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile))
{
Windows.Win32.PInvoke.CloseHandle(tempFile);
throw new Win32Exception();
}
tempFile.Close();
Windows.Win32.PInvoke.CloseHandle(tempFile);
if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
throw new Win32Exception();

View file

@ -57,60 +57,60 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
static ManagedIStream? ToManagedObject(void* pThis) =>
GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as ManagedIStream;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int QueryInterfaceStatic(IStream* pThis, Guid* riid, void** ppvObject) =>
ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint AddRefStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0);
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static uint ReleaseStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0);
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int ReadStatic(IStream* pThis, void* pv, uint cb, uint* pcbRead) =>
ToManagedObject(pThis)?.Read(pv, cb, pcbRead) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int WriteStatic(IStream* pThis, void* pv, uint cb, uint* pcbWritten) =>
ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int SeekStatic(
IStream* pThis, LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition) =>
ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int SetSizeStatic(IStream* pThis, ULARGE_INTEGER libNewSize) =>
ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int CopyToStatic(
IStream* pThis, IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead,
ULARGE_INTEGER* pcbWritten) =>
ToManagedObject(pThis)?.CopyTo(pstm, cb, pcbRead, pcbWritten) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int CommitStatic(IStream* pThis, uint grfCommitFlags) =>
ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int RevertStatic(IStream* pThis) => ToManagedObject(pThis)?.Revert() ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int LockRegionStatic(IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int UnlockRegionStatic(
IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int StatStatic(IStream* pThis, STATSTG* pstatstg, uint grfStatFlag) =>
ToManagedObject(pThis)?.Stat(pstatstg, grfStatFlag) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
static int CloneStatic(IStream* pThis, IStream** ppstm) => ToManagedObject(pThis)?.Clone(ppstm) ?? E.E_UNEXPECTED;
}

View file

@ -88,7 +88,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
fixed (char* pPath = path)
{
SHCreateStreamOnFileEx(
(ushort*)pPath,
pPath,
grfMode,
(uint)attributes,
fCreate,
@ -115,7 +115,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
{
fixed (char* pName = name)
{
var option = new PROPBAG2 { pstrName = (ushort*)pName };
var option = new PROPBAG2 { pstrName = pName };
return obj.Write(1, &option, &varValue);
}
}
@ -145,7 +145,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
try
{
fixed (char* pName = name)
return obj.SetMetadataByName((ushort*)pName, &propVarValue);
return obj.SetMetadataByName(pName, &propVarValue);
}
finally
{
@ -165,7 +165,7 @@ internal static unsafe partial class TerraFxComInterfaceExtensions
public static HRESULT RemoveMetadataByName(ref this IWICMetadataQueryWriter obj, string name)
{
fixed (char* pName = name)
return obj.RemoveMetadataByName((ushort*)pName);
return obj.RemoveMetadataByName(pName);
}
[LibraryImport("propsys.dll")]

View file

@ -158,16 +158,6 @@ public static partial class Util
return branchInternal = gitBranch;
}
/// <summary>
/// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
/// downloaded from webservices.
/// </summary>
/// <returns>The name of the track, or null.</returns>
internal static string? GetActiveTrack()
{
return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
}
/// <inheritdoc cref="DescribeAddress(nint)"/>
public static unsafe string DescribeAddress(void* p) => DescribeAddress((nint)p);
@ -703,6 +693,16 @@ public static partial class Util
}
}
/// <summary>
/// Gets the active Dalamud track, if this instance was launched through XIVLauncher and used a version
/// downloaded from webservices.
/// </summary>
/// <returns>The name of the track, or null.</returns>
internal static string? GetActiveTrack()
{
return Environment.GetEnvironmentVariable("DALAMUD_BRANCH");
}
/// <summary>
/// Gets a random, inoffensive, human-friendly string.
/// </summary>
@ -858,7 +858,7 @@ public static partial class Util
var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2);
var dropFilesSize = sizeof(DROPFILES);
var hGlobal = Win32_PInvoke.GlobalAlloc_SafeHandle(
var hGlobal = Win32_PInvoke.GlobalAlloc(
GLOBAL_ALLOC_FLAGS.GHND,
// struct size + size of encoded strings + null terminator for each
// string + two null terminators for end of list
@ -896,12 +896,11 @@ public static partial class Util
{
Win32_PInvoke.SetClipboardData(
(uint)CLIPBOARD_FORMAT.CF_HDROP,
hGlobal);
(Windows.Win32.Foundation.HANDLE)hGlobal.Value);
Win32_PInvoke.CloseClipboard();
return true;
}
hGlobal.Dispose();
return false;
}

View file

@ -1,51 +0,0 @@
using System.Numerics;
namespace Dalamud.Utility;
/// <summary>
/// Extension methods for System.Numerics.VectorN and SharpDX.VectorN.
/// </summary>
public static class VectorExtensions
{
/// <summary>
/// Converts a SharpDX vector to System.Numerics.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static Vector2 ToSystem(this SharpDX.Vector2 vec) => new(x: vec.X, y: vec.Y);
/// <summary>
/// Converts a SharpDX vector to System.Numerics.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static Vector3 ToSystem(this SharpDX.Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z);
/// <summary>
/// Converts a SharpDX vector to System.Numerics.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static Vector4 ToSystem(this SharpDX.Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W);
/// <summary>
/// Converts a System.Numerics vector to SharpDX.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static SharpDX.Vector2 ToSharpDX(this Vector2 vec) => new(x: vec.X, y: vec.Y);
/// <summary>
/// Converts a System.Numerics vector to SharpDX.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static SharpDX.Vector3 ToSharpDX(this Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z);
/// <summary>
/// Converts a System.Numerics vector to SharpDX.
/// </summary>
/// <param name="vec">Vector to convert.</param>
/// <returns>A converted vector.</returns>
public static SharpDX.Vector4 ToSharpDX(this Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W);
}

View file

@ -2,7 +2,7 @@
<Project>
<PropertyGroup Label="Target">
<TargetFramework>net9.0-windows</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms>
<LangVersion>13.0</LangVersion>

View file

@ -1,65 +1,66 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup>
<ItemGroup>
<!-- Analyzers -->
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4"/>
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556"/>
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.2"/>
<ItemGroup>
<!-- Analyzers -->
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.2" />
<!-- Misc Libraries -->
<PackageVersion Include="BitFaster.Caching" Version="2.4.1"/>
<PackageVersion Include="CheapLoc" Version="1.1.8"/>
<PackageVersion Include="MinSharp" Version="1.0.4"/>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageVersion Include="Lumina" Version="6.5.1"/>
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.7"/>
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0"/>
<PackageVersion Include="System.Drawing.Common" Version="8.0.0"/>
<PackageVersion Include="System.Reactive" Version="5.0.0"/>
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="8.0.0"/>
<PackageVersion Include="System.Resources.Extensions" Version="8.0.0"/>
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.39"/>
<PackageVersion Include="sqlite-net-pcl" Version="1.8.116"/>
<!-- Misc Libraries -->
<PackageVersion Include="BitFaster.Caching" Version="2.4.1" />
<PackageVersion Include="CheapLoc" Version="1.1.8" />
<PackageVersion Include="MinSharp" Version="1.0.4" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Lumina" Version="6.5.1" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.0" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.0" />
<PackageVersion Include="System.Reactive" Version="5.0.0" />
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="10.0.0" />
<PackageVersion Include="System.Resources.Extensions" Version="10.0.0" />
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.39" />
<PackageVersion Include="sqlite-net-pcl" Version="1.8.116" />
<!-- DirectX / Win32 -->
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.2"/>
<PackageVersion Include="SharpDX.Direct3D11" Version="4.2.0"/>
<PackageVersion Include="SharpDX.Mathematics" Version="4.2.0"/>
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183"/>
<!-- DirectX / Win32 -->
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.259" />
<!-- Logging -->
<PackageVersion Include="Serilog" Version="4.0.2"/>
<PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0"/>
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0"/>
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0"/>
<!-- Logging -->
<PackageVersion Include="Serilog" Version="4.0.2" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<!-- Injector Utilities -->
<PackageVersion Include="Iced" Version="1.17.0"/>
<PackageVersion Include="PeNet" Version="2.6.4"/>
<!-- Injector Utilities -->
<PackageVersion Include="Iced" Version="1.17.0" />
<PackageVersion Include="PeNet" Version="2.6.4" />
<!-- HexaGen -->
<PackageVersion Include="HexaGen.Runtime" Version="1.1.20"/>
<!-- HexaGen -->
<PackageVersion Include="HexaGen.Runtime" Version="1.1.20" />
<!-- Reloaded -->
<PackageVersion Include="goatcorp.Reloaded.Hooks" Version="4.2.0-goatcorp7"/>
<PackageVersion Include="goatcorp.Reloaded.Assembler" Version="1.0.14-goatcorp5"/>
<PackageVersion Include="Reloaded.Memory" Version="7.0.0"/>
<PackageVersion Include="Reloaded.Memory.Buffers" Version="2.0.0"/>
<!-- Reloaded -->
<PackageVersion Include="goatcorp.Reloaded.Hooks" Version="4.2.0-goatcorp7" />
<PackageVersion Include="goatcorp.Reloaded.Assembler" Version="1.0.14-goatcorp5" />
<PackageVersion Include="Reloaded.Memory" Version="7.0.0" />
<PackageVersion Include="Reloaded.Memory.Buffers" Version="2.0.0" />
<!-- Unit Testing -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="16.10.0"/>
<PackageVersion Include="xunit" Version="2.4.1"/>
<PackageVersion Include="xunit.abstractions" Version="2.0.3"/>
<PackageVersion Include="xunit.analyzers" Version="0.10.0"/>
<PackageVersion Include="xunit.assert" Version="2.4.1"/>
<PackageVersion Include="xunit.core" Version="2.4.1"/>
<PackageVersion Include="xunit.extensibility.core" Version="2.4.1"/>
<PackageVersion Include="xunit.extensibility.execution" Version="2.4.1"/>
<PackageVersion Include="xunit.runner.console" Version="2.4.1"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.3"/>
</ItemGroup>
<!-- Named Pipes / RPC -->
<PackageVersion Include="StreamJsonRpc" Version="2.22.23" />
<!-- Unit Testing -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageVersion Include="xunit" Version="2.4.1" />
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
<PackageVersion Include="xunit.analyzers" Version="0.10.0" />
<PackageVersion Include="xunit.assert" Version="2.4.1" />
<PackageVersion Include="xunit.core" Version="2.4.1" />
<PackageVersion Include="xunit.extensibility.core" Version="2.4.1" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.4.1" />
<PackageVersion Include="xunit.runner.console" Version="2.4.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.3" />
</ItemGroup>
</Project>

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using Nuke.Common;
using Nuke.Common.Execution;
using Nuke.Common.Git;
@ -42,10 +41,7 @@ public class DalamudBuild : NukeBuild
AbsolutePath InjectorProjectDir => RootDirectory / "Dalamud.Injector";
AbsolutePath InjectorProjectFile => InjectorProjectDir / "Dalamud.Injector.csproj";
AbsolutePath InjectorBootProjectDir => RootDirectory / "Dalamud.Injector.Boot";
AbsolutePath InjectorBootProjectFile => InjectorBootProjectDir / "Dalamud.Injector.Boot.vcxproj";
AbsolutePath TestProjectDir => RootDirectory / "Dalamud.Test";
AbsolutePath TestProjectFile => TestProjectDir / "Dalamud.Test.csproj";
@ -131,7 +127,7 @@ public class DalamudBuild : NukeBuild
if (IsCIBuild)
{
s = s
.SetProcessArgumentConfigurator(a => a.Add("/clp:NoSummary")); // Disable MSBuild summary on CI builds
.SetProcessAdditionalArguments("/clp:NoSummary"); // Disable MSBuild summary on CI builds
}
// We need to emit compiler generated files for the docs build, since docfx can't run generators directly
// TODO: This fails every build after this because of redefinitions...
@ -172,14 +168,6 @@ public class DalamudBuild : NukeBuild
.EnableNoRestore());
});
Target CompileInjectorBoot => _ => _
.Executes(() =>
{
MSBuildTasks.MSBuild(s => s
.SetTargetPath(InjectorBootProjectFile)
.SetConfiguration(Configuration));
});
Target SetCILogging => _ => _
.DependentFor(Compile)
.OnlyWhenStatic(() => IsCIBuild)
@ -196,7 +184,6 @@ public class DalamudBuild : NukeBuild
.DependsOn(CompileDalamudBoot)
.DependsOn(CompileDalamudCrashHandler)
.DependsOn(CompileInjector)
.DependsOn(CompileInjectorBoot)
;
Target CI => _ => _
@ -250,12 +237,6 @@ public class DalamudBuild : NukeBuild
.SetProject(InjectorProjectFile)
.SetConfiguration(Configuration));
MSBuildTasks.MSBuild(s => s
.SetProjectFile(InjectorBootProjectFile)
.SetConfiguration(Configuration)
.SetTargets("Clean"));
FileSystemTasks.DeleteDirectory(ArtifactsDirectory);
Directory.CreateDirectory(ArtifactsDirectory);
ArtifactsDirectory.CreateOrCleanDirectory();
});
}

View file

@ -11,7 +11,8 @@
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="6.2.1" />
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="9.0.0" />
<PackageReference Include="Nuke.Common" Version="10.1.0" />
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0" />
<PackageReference Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers" />
</ItemGroup>
</Project>

View file

@ -1,7 +1,7 @@
{
"sdk": {
"version": "9.0.0",
"version": "10.0.0",
"rollForward": "latestMinor",
"allowPrerelease": true
}
}
}

View file

@ -26,6 +26,7 @@
<PackageReference Include="Veldrid" Version="4.9.0" />
<PackageReference Include="Veldrid.SDL2" Version="4.9.0" />
<PackageReference Include="Veldrid.StartupUtilities" Version="4.9.0" />
<PackageReference Remove="Microsoft.CodeAnalysis.BannedApiAnalyzers"/>
</ItemGroup>
<ItemGroup>

@ -1 +1 @@
Subproject commit 6f339d8f725fa6922449f7e5c584ca6b8fa2fb19
Subproject commit e5dedba42a3fea8f050ea54ac583a5874bf51c6f

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Linux'))">$(HOME)/.xlcore/dalamud/Hooks/dev/</DalamudLibPath>
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/</DalamudLibPath>
<DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath>
</PropertyGroup>
<Import Project="$(DalamudLibPath)/targets/Dalamud.Plugin.targets"/>
</Project>

View file

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Platforms>x64</Platforms>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AssemblySearchPaths>$(AssemblySearchPaths);$(DalamudLibPath)</AssemblySearchPaths>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="11.0.0" />
<Reference Include="FFXIVClientStructs" Private="false" />
<Reference Include="Newtonsoft.Json" Private="false" />
<Reference Include="Dalamud" Private="false" />
<Reference Include="ImGui.NET" Private="false" />
<Reference Include="ImGuiScene" Private="false" />
<Reference Include="Lumina" Private="false" />
<Reference Include="Lumina.Excel" Private="false" />
<Reference Include="Serilog" Private="false" />
</ItemGroup>
<Target Name="Message" BeforeTargets="BeforeBuild">
<Message Text="Dalamud.Plugin: root at $(DalamudLibPath)" Importance="high" />
</Target>
<Target Name="DeprecationNotice" BeforeTargets="BeforeBuild">
<Warning Text="Using the targets file to include the Dalamud SDK is no longer recommended. Please upgrade to Dalamud.NET.Sdk - learn more here: https://dalamud.dev/plugin-development/how-tos/v12-sdk-migration" />
</Target>
</Project>