Compare commits

...

91 commits

Author SHA1 Message Date
github-actions[bot]
95526ced0e 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-08 23:19:26 +00:00
goaaats
e53ccdbcc0 build: 13.0.0.15
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Failing after 3s
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-09 00:18:28 +01:00
goaaats
97df73acea Ensure that we don't catch mouse up events without corresponding mouse down events
Fixes an issue wherein the cursor could get locked by the game if WantCaptureMouse becomes true in between down and up events
2025-12-08 21:00:08 +01:00
goaaats
2806e59dba Also remove borders for dev bar, to prevent themes from causing weirdness 2025-12-08 20:09:31 +01:00
goaaats
24caa1cb18 PresetWindow.IsDefault can be JsonIgnore 2025-12-08 20:05:14 +01:00
goaaats
5d08170333 Keep rendering title bar buttons if one is not available clickthrough 2025-12-08 20:03:43 +01:00
goaaats
d0110f7251 Hardcode HasModifiedGameDataFiles to false for now until XL is fixed 2025-12-08 20:03:22 +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
100 changed files with 2615 additions and 1533 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

@ -6,7 +6,7 @@
<PropertyGroup Label="Feature">
<Description>XIV Launcher addon framework</Description>
<DalamudVersion>13.0.0.14</DalamudVersion>
<DalamudVersion>13.0.0.15</DalamudVersion>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version>
<FileVersion>$(DalamudVersion)</FileVersion>
@ -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

@ -84,8 +84,11 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
dalamud.StartInfo.TroubleshootingPackData);
// Don't fail for IndexIntegrityResult.Exception, since the check during launch has a very small timeout
this.HasModifiedGameDataFiles =
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed;
// this.HasModifiedGameDataFiles =
// tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed;
// TODO: Put above back when check in XL is fixed
this.HasModifiedGameDataFiles = false;
if (this.HasModifiedGameDataFiles)
Log.Verbose("Game data integrity check failed!\n{TsData}", dalamud.StartInfo.TroubleshootingPackData);

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

@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Bindings.ImGui;
using Dalamud.Console;
using Dalamud.Memory;
using Dalamud.Utility;
@ -37,6 +38,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
private readonly WndProcDelegate wndProcDelegate;
private readonly nint platformNamePtr;
private readonly IConsoleVariable<bool> cvLogMouseEvents;
private ViewportHandler viewportHandler;
private int mouseButtonsDown;
@ -87,6 +90,11 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
this.cursors[(int)ImGuiMouseCursor.ResizeNwse] = LoadCursorW(default, IDC.IDC_SIZENWSE);
this.cursors[(int)ImGuiMouseCursor.Hand] = LoadCursorW(default, IDC.IDC_HAND);
this.cursors[(int)ImGuiMouseCursor.NotAllowed] = LoadCursorW(default, IDC.IDC_NO);
this.cvLogMouseEvents = Service<ConsoleManager>.Get().AddVariable(
"imgui.log_mouse_events",
"Log mouse events to console for debugging",
false);
}
/// <summary>
@ -267,11 +275,23 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
case WM.WM_XBUTTONDOWN:
case WM.WM_XBUTTONDBLCLK:
{
if (this.cvLogMouseEvents.Value)
{
Log.Verbose(
"Handle MouseDown {Btn} WantCaptureMouse: {Want} mouseButtonsDown: {Down}",
GetButton(msg, wParam),
io.WantCaptureMouse,
this.mouseButtonsDown);
}
var button = GetButton(msg, wParam);
if (io.WantCaptureMouse)
{
if (this.mouseButtonsDown == 0 && GetCapture() == nint.Zero)
{
SetCapture(hWndCurrent);
}
this.mouseButtonsDown |= 1 << button;
io.AddMouseButtonEvent(button, true);
return default(LRESULT);
@ -288,12 +308,28 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
case WM.WM_MBUTTONUP:
case WM.WM_XBUTTONUP:
{
if (this.cvLogMouseEvents.Value)
{
Log.Verbose(
"Handle MouseUp {Btn} WantCaptureMouse: {Want} mouseButtonsDown: {Down}",
GetButton(msg, wParam),
io.WantCaptureMouse,
this.mouseButtonsDown);
}
var button = GetButton(msg, wParam);
if (io.WantCaptureMouse)
// Need to check if we captured the button event away from the game here, otherwise the game might get
// a down event but no up event, causing the cursor to get stuck.
// Can happen if WantCaptureMouse becomes true in between down and up
if (io.WantCaptureMouse && (this.mouseButtonsDown & (1 << button)) != 0)
{
this.mouseButtonsDown &= ~(1 << button);
if (this.mouseButtonsDown == 0 && GetCapture() == hWndCurrent)
{
ReleaseCapture();
}
io.AddMouseButtonEvent(button, false);
return default(LRESULT);
}
@ -672,7 +708,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)
@ -701,7 +737,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
fixed (char* windowClassNamePtr = WindowClassName)
{
UnregisterClassW(
(ushort*)windowClassNamePtr,
windowClassNamePtr,
(HINSTANCE)Marshal.GetHINSTANCE(typeof(ViewportHandler).Module));
}
@ -815,8 +851,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,
@ -1030,7 +1066,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

@ -669,6 +669,8 @@ internal class DalamudInterface : IInternalDisposableService
{
using var barColor = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0.060f, 0.060f, 0.060f, 0.773f));
barColor.Push(ImGuiCol.MenuBarBg, Vector4.Zero);
barColor.Push(ImGuiCol.Border, Vector4.Zero);
barColor.Push(ImGuiCol.BorderShadow, Vector4.Zero);
if (ImGui.BeginMainMenuBar())
{
var pluginManager = Service<PluginManager>.Get();

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

@ -53,6 +53,7 @@ internal class PresetModel
/// <summary>
/// Gets a value indicating whether this preset is in the default state.
/// </summary>
[JsonIgnore]
public bool IsDefault =>
!this.IsPinned &&
!this.IsClickThrough &&

View file

@ -864,7 +864,7 @@ public abstract class Window
foreach (var button in this.allButtons)
{
if (this.internalIsClickthrough && !button.AvailableClickthrough)
return;
continue;
Vector2 position = new(titleBarRect.Max.X - padR - buttonSize, titleBarRect.Min.Y + style.FramePadding.Y);
padR += buttonSize + style.ItemInnerSpacing.X;
@ -908,7 +908,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>