diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6e8d5ee --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,110 @@ +name: Release Dalamud Plugin + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: windows-2022 + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + + - name: Download Dalamud dev files + shell: pwsh + run: | + $target = Join-Path $env:AppData "XIVLauncher\addon\Hooks\dev" + New-Item -ItemType Directory -Force -Path $target | Out-Null + Invoke-WebRequest -Uri "https://goatcorp.github.io/dalamud-distrib/latest.zip" -OutFile latest.zip + Expand-Archive -Path latest.zip -DestinationPath $target -Force + + - name: Validate tag matches project version + shell: pwsh + run: | + [xml]$project = Get-Content "StarlightBreaker.Dalamud/StarlightBreaker.csproj" + $version = $project.Project.PropertyGroup.Version | Select-Object -First 1 + if (-not $version) { + throw "Version not found in StarlightBreaker.Dalamud/StarlightBreaker.csproj." + } + + $tag = "${{ github.ref_name }}" + if ($tag -ne "v$version") { + throw "Tag '$tag' does not match project version 'v$version'." + } + + - name: Restore + run: dotnet restore StarlightBreaker.Dalamud/StarlightBreaker.csproj --locked-mode + + - name: Build + run: dotnet build StarlightBreaker.Dalamud/StarlightBreaker.csproj --configuration Release --no-restore + + - name: Package release assets + shell: pwsh + run: | + $tag = "${{ github.ref_name }}" + $assetDir = Join-Path $env:RUNNER_TEMP "release" + New-Item -ItemType Directory -Force -Path $assetDir | Out-Null + + $files = @( + "Output/StarlightBreaker.dll", + "Output/StarlightBreaker.deps.json", + "Output/StarlightBreaker.json" + ) + + foreach ($file in $files) { + if (-not (Test-Path $file)) { + throw "Missing release file: $file" + } + + Copy-Item $file -Destination $assetDir + } + + $zipPath = Join-Path $env:RUNNER_TEMP "StarlightBreaker-$tag.zip" + if (Test-Path $zipPath) { + Remove-Item $zipPath -Force + } + + Compress-Archive -Path (Join-Path $assetDir "*") -DestinationPath $zipPath + + - name: Upload workflow artifact + uses: actions/upload-artifact@v4 + with: + name: StarlightBreaker-${{ github.ref_name }} + path: | + Output/StarlightBreaker.dll + Output/StarlightBreaker.deps.json + Output/StarlightBreaker.json + ${{ runner.temp }}/StarlightBreaker-${{ github.ref_name }}.zip + if-no-files-found: error + + - name: Publish GitHub release + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $tag = "${{ github.ref_name }}" + $zipPath = Join-Path $env:RUNNER_TEMP "StarlightBreaker-$tag.zip" + $assets = @( + $zipPath, + "Output/StarlightBreaker.dll", + "Output/StarlightBreaker.deps.json", + "Output/StarlightBreaker.json" + ) + + gh release view $tag *> $null + if ($LASTEXITCODE -eq 0) { + gh release upload $tag @assets --clobber + } else { + gh release create $tag @assets --title $tag --generate-notes + } diff --git a/.gitignore b/.gitignore index dfcfd56..6acdcaa 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ +Output/ # StyleCop StyleCopReport.xml diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..17d10ee --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,66 @@ +# StarlightBreaker Handoff + +## Summary +- Maintenance target is `StarlightBreaker.Dalamud` +- This round of work focused on making the Dalamud plugin load correctly on the current `XIVLauncherCN` environment and wiring up automatic GitHub releases +- The plugin now builds against the active `XIVLauncherCN` dev assemblies and the latest tested output is: + - `Output/StarlightBreaker.dll` + - `Output/StarlightBreaker.deps.json` + - `Output/StarlightBreaker.json` +- GitHub Actions now builds and publishes release assets when a matching `v*` tag is pushed +- A pull request to upstream was opened from fork `main`: + - `https://github.com/Loskh/StarlightBreaker/pull/20` + +## What Was Fixed Today +- Adapted the plugin to the current API 15 environment used by `XIVLauncherCN` +- Fixed the plugin loading failure caused by building against the wrong local Dalamud installation +- Removed direct plugin-service injection of the scanner type, because the runtime-exposed scanner API differs across local Dalamud builds +- Switched signature resolution to `IGameInteropProvider.InitializeFromAttributes(...)` with `SignatureAttribute`, which uses the documented public API and avoids private reflection over internal scanner fields +- Fixed local reference resolution by pinning the project to `$(AppData)\XIVLauncherCN\addon\Hooks\dev\` +- Corrected windowing compatibility so the built assembly now targets `WindowSystem.AddWindow(IWindow)` instead of the old `Window` signature +- Updated new-config defaults so these options start enabled: + - `ChatLogConfig.EnableColor` + - `FontConfig.Italics` + - `FontConfig.EnableColor` + - `PartyFinderConfig.Enable` was already enabled by default + +## Root Cause +- The machine had multiple local Dalamud installations: + - `XIVLauncher\addon\Hooks\dev` + - `XIVLauncherCN\addon\Hooks\dev` +- The game was running with `XIVLauncherCN`, but the project was originally compiling against the other local `Dalamud.dll` +- Because of that mismatch, the plugin compiled successfully but failed at runtime with API-shape differences such as: + - missing `Dalamud.Game.ISigScanner` + - old/new `WindowSystem.AddWindow(...)` signature mismatch + +## Current Build Configuration +- Project file: [StarlightBreaker.Dalamud/StarlightBreaker.csproj](StarlightBreaker.Dalamud/StarlightBreaker.csproj) +- Important settings: + - `DalamudLibPath` prefers `$(AppData)\XIVLauncherCN\addon\Hooks\dev\` when that local path exists + - If `DALAMUD_HOME` is set, the SDK uses that path instead + - Otherwise CI and other environments fall back to the SDK default `$(AppData)\XIVLauncher\addon\Hooks\dev\` + - `TargetFramework` is `net10.0-windows` + - Dalamud-related references use explicit `HintPath` values rooted at `$(DalamudLibPath)` + - Output is written to `Output/` + - Release workflow file is `.github/workflows/release.yml` + +## Key Files +- [StarlightBreaker.Dalamud/Plugin.cs](StarlightBreaker.Dalamud/Plugin.cs) +- [StarlightBreaker.Dalamud/Configuration.cs](StarlightBreaker.Dalamud/Configuration.cs) +- [StarlightBreaker.Dalamud/StarlightBreaker.csproj](StarlightBreaker.Dalamud/StarlightBreaker.csproj) +- [.github/workflows/release.yml](.github/workflows/release.yml) + +## Validation Performed +- `dotnet build StarlightBreaker.Dalamud\StarlightBreaker.csproj` +- Verified the produced assembly references the active CN dev environment +- Verified the built plugin no longer exposes scanner injection properties that break current runtime loading +- Verified the built plugin references `WindowSystem.AddWindow(IWindow)` in assembly metadata + +## Remaining Notes +- Existing local user config is not force-migrated; default option changes only affect new configs +- The repo still contains legacy non-Dalamud targets, but the active work path is the Dalamud plugin +- GitHub release publishing is tag-driven; push a tag that matches the project version in `StarlightBreaker.Dalamud/StarlightBreaker.csproj`, for example `v2.15.0` +- If a future loading error appears, check these first: + - whether `XIVLauncherCN` is still the actual runtime host + - whether local overrides or `DALAMUD_HOME` still point to the same host's `addon\Hooks\dev` + - whether the built output in `Output/` is the file actually loaded by Dalamud diff --git a/README.md b/README.md index 09eb93c..e991535 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ FFXIV国服屏蔽词解除 可自动查找偏移,理论上来说,除非大版本更新,插件本体不用更新.~~同时也可以在国际服使用(?)~~ +## 交接文档 + +这次 API 15 / `XIVLauncherCN` 兼容修复与自动发布工作流的完整交接记录见: + +- [HANDOFF.md](HANDOFF.md) + 使用方法:选择一个喜欢的就行,不可同时选择 * 使用ACT插件管理加载StarlightBreaker.dll即可 diff --git a/StarlightBreaker.Dalamud/Configuration.cs b/StarlightBreaker.Dalamud/Configuration.cs index c2b332f..166b24c 100644 --- a/StarlightBreaker.Dalamud/Configuration.cs +++ b/StarlightBreaker.Dalamud/Configuration.cs @@ -8,7 +8,7 @@ namespace StarlightBreaker public class ChatLogConfig { public bool Enable = true; - public bool EnableColor = false; + public bool EnableColor = true; } [Serializable] public class PartyFinderConfig @@ -20,8 +20,8 @@ public class PartyFinderConfig public class FontConfig { public ushort Color = 17; - public bool Italics = false; - public bool EnableColor = false; + public bool Italics = true; + public bool EnableColor = true; } [Serializable] public class Configuration : IPluginConfiguration diff --git a/StarlightBreaker.Dalamud/Plugin.cs b/StarlightBreaker.Dalamud/Plugin.cs index 2a1fd35..b250f6e 100644 --- a/StarlightBreaker.Dalamud/Plugin.cs +++ b/StarlightBreaker.Dalamud/Plugin.cs @@ -1,3 +1,4 @@ +using System; using Dalamud.Game.Command; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; @@ -7,17 +8,14 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using Dalamud.Utility; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.Text; using InteropGenerator.Runtime; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; using UTF8String = FFXIVClientStructs.FFXIV.Client.System.String.Utf8String; namespace StarlightBreaker @@ -30,8 +28,6 @@ public unsafe class Plugin : IDalamudPlugin [PluginService] internal static IDalamudPluginInterface PluginInterface { get; private set; } = null!; [PluginService] - internal static ISigScanner Scanner { get; private set; } = null!; - [PluginService] internal static ICommandManager CommandManager { get; private set; } = null!; [PluginService] internal static IDataManager DataManager { get; private set; } = null!; @@ -58,18 +54,23 @@ public unsafe class Plugin : IDalamudPlugin //private VulgarCheckDelegate VulgarCheck; public delegate Utf8String* RaptureTextModuleChatLogFilterDelegate(RaptureTextModule* textModule, Utf8String* text, nint unk, uint bytesNum); - Hook RaptureTextModuleChatLogFilterHook; + [Signature("40 53 48 83 EC 20 48 8D 99 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 48 8B 0D", DetourName = nameof(RaptureTextModuleChatLogFilterDetour))] + private Hook RaptureTextModuleChatLogFilterHook = null!; public delegate uint AgentLookingForGroupTextFilterDelegate(AgentLookingForGroup* agent, Utf8String* text); - Hook AgentLookingForGroupTextFilterHook; + [Signature("48 89 5C 24 ?? 57 48 83 EC 20 C6 81 ?? ?? ?? ?? ?? 48 8B D9 48 8B 49 10 48 8B FA", DetourName = nameof(AgentLookingForGroupTextFilterDetour))] + private Hook AgentLookingForGroupTextFilterHook = null!; [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate void RaptureTextModulePartyFinderFilterDelegate(RaptureTextModule* textModule, Utf8String* text, nint unk, bool unk1); - private RaptureTextModulePartyFinderFilterDelegate RaptureTextModulePartyFinderFilterOrigin; - private CallHook AgentLookingForGroupDetailedWindowTextFilterHook; + private RaptureTextModulePartyFinderFilterDelegate RaptureTextModulePartyFinderFilterOrigin = null!; + private CallHook AgentLookingForGroupDetailedWindowTextFilterHook = null!; + + [Signature("E8 ?? ?? ?? ?? 44 38 A3 ?? ?? ?? ?? 74 ?? 48 8D 15", UseFlags = SignatureUseFlags.Pointer)] + private IntPtr AgentLookingForGroupDetailedWindowTextFilterHookAddress = IntPtr.Zero; - public delegate Utf8String* AgentLookingForGroupProcessStringDelegate(AgentLookingForGroup* agent, Utf8String* text, uint bytesNum); - Hook AgentLookingForGroupProcessStringHook; + [Signature("40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B F9 49 8B F0", UseFlags = SignatureUseFlags.Pointer)] + private IntPtr RaptureTextModulePartyFinderFilterAddress = IntPtr.Zero; public unsafe Plugin() { @@ -86,24 +87,22 @@ public unsafe Plugin() HelpMessage = "Open Config Window for StarlightBreaker" }); - this.RaptureTextModuleChatLogFilterHook = GameInteropProvider.HookFromSignature("40 53 48 83 EC 20 48 8D 99 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 48 8B 0D", this.RaptureTextModuleChatLogFilterDetour); + GameInteropProvider.InitializeFromAttributes(this); + this.RaptureTextModuleChatLogFilterHook.Enable(); - this.AgentLookingForGroupTextFilterHook = GameInteropProvider.HookFromSignature("48 89 5C 24 ?? 57 48 83 EC 20 C6 81 ?? ?? ?? ?? ?? 48 8B D9 48 8B 49 10 48 8B FA", this.AgentLookingForGroupTextFilterDetour); this.AgentLookingForGroupTextFilterHook.Enable(); - var hookAddress = Scanner.ScanAllText("E8 ?? ?? ?? ?? 44 38 A3 ?? ?? ?? ?? 74 ?? 48 8D 15").First(); - PluginLog.Debug($"AgentLookingForGroupDetailedWindowTextFilterHook:{Util.DescribeAddress(hookAddress)}"); - this.AgentLookingForGroupDetailedWindowTextFilterHook = new CallHook(hookAddress, RaptureTextModulePartyFinderFilter); + PluginLog.Debug($"AgentLookingForGroupDetailedWindowTextFilterHook:{Util.DescribeAddress(this.AgentLookingForGroupDetailedWindowTextFilterHookAddress)}"); + this.AgentLookingForGroupDetailedWindowTextFilterHook = new CallHook(this.AgentLookingForGroupDetailedWindowTextFilterHookAddress, RaptureTextModulePartyFinderFilter); this.AgentLookingForGroupDetailedWindowTextFilterHook?.Enable(); //由于"E8 ?? ?? ?? ?? 44 38 A3 ?? ?? ?? ?? 74 11"和"E8 ?? ?? ?? ?? 44 38 A3 ?? ?? ?? ?? 74 ?? 48 8D 15"是同一地址的不同签名ffxiv_dx11.exe+0x5419A3 函数地址为ffxiv_dx11.exe+0x97B820 //之前的版本已经被Hook,因此开头不再是E9或E8,因此后面的ScanText会返回ffxiv_dx11.exe+0x5419A3,而不是0x97B820 //var raptureTextModulePartyFinderFilterAddress = Scanner.ScanText("E8 ?? ?? ?? ?? 44 38 A3 ?? ?? ?? ?? 74 11"); - var raptureTextModulePartyFinderFilterAddress = Scanner.ScanText("40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B F9 49 8B F0"); - PluginLog.Debug($"RaptureTextModulePartyFinderFilter:{Util.DescribeAddress(raptureTextModulePartyFinderFilterAddress)}"); - this.RaptureTextModulePartyFinderFilterOrigin = Marshal.GetDelegateForFunctionPointer(raptureTextModulePartyFinderFilterAddress); + PluginLog.Debug($"RaptureTextModulePartyFinderFilter:{Util.DescribeAddress(this.RaptureTextModulePartyFinderFilterAddress)}"); + this.RaptureTextModulePartyFinderFilterOrigin = Marshal.GetDelegateForFunctionPointer(this.RaptureTextModulePartyFinderFilterAddress); //TODO: //E8 ?? ?? ?? ?? 0F B6 BB ?? ?? ?? ?? 40 84 FF 74 ?? 48 8D 15 //当出现无法处理招募的时候检查文本并显示导致无法发送的地方 @@ -233,21 +232,27 @@ private void HighlightCensoredParts(Utf8String* original, Utf8String* processed, var origText = (TextPayload)orig; var procText = (TextPayload)proc; + var origStr = origText.Text; + var procStr = procText.Text; + + if (origStr is null || procStr is null) + { + builder.Add(origText); + continue; + } - if (origText.Text == procText.Text) + if (origStr == procStr) { builder.Add(origText); continue; } - if (origText.Text.Length != procText.Text.Length) + if (origStr.Length != procStr.Length) { builder.Add(origText); continue; } - string origStr = origText.Text; - string procStr = procText.Text; int length = origStr.Length; int j = 0; diff --git a/StarlightBreaker.Dalamud/StarlightBreaker.csproj b/StarlightBreaker.Dalamud/StarlightBreaker.csproj index 746b200..e3ef56f 100644 --- a/StarlightBreaker.Dalamud/StarlightBreaker.csproj +++ b/StarlightBreaker.Dalamud/StarlightBreaker.csproj @@ -2,18 +2,66 @@ StarlightBreaker - 2.15.0 + 2.15.1 + + $(AppData)\XIVLauncherCN\addon\Hooks\dev\ + net10.0-windows + {CandidateAssemblyFiles};{HintPathFromItem};{TargetFrameworkDirectory};{RawFileName};$(DalamudLibPath) Loskh 关闭屏蔽词过滤,并特殊显示聊天频道或招募板上发出的屏蔽词 关闭屏蔽词过滤,并特殊显示聊天频道或招募板上发出的屏蔽词 - https://github.com/Loskh/StarlightBreaker + https://github.com/anmili2022/StarlightBreaker + https://github.com/anmili2022/StarlightBreaker MIT false + ..\Output\ - + + $(DalamudLibPath)Dalamud.dll + + + $(DalamudLibPath)Dalamud.Bindings.ImGui.dll + + + $(DalamudLibPath)Dalamud.Bindings.ImPlot.dll + + + $(DalamudLibPath)Dalamud.Bindings.ImGuizmo.dll + + + $(DalamudLibPath)FFXIVClientStructs.dll + + + $(DalamudLibPath)InteropGenerator.Runtime.dll + + + $(DalamudLibPath)Newtonsoft.Json.dll + + + $(DalamudLibPath)Lumina.dll + + + $(DalamudLibPath)Lumina.Excel.dll + + + $(DalamudLibPath)Serilog.dll + + + $(DalamudLibPath)Microsoft.Extensions.ObjectPool.dll + + + + $(DalamudLibPath)goatcorp.Reloaded.Hooks.dll + + + $(DalamudLibPath)Reloaded.Hooks.Definitions.dll + + + $(DalamudLibPath)Iced.dll +