Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd01fa63a1 | |||
| b81c50b433 | |||
| 355a57089b | |||
| cf7ab6226c | |||
| 03da6d58a4 | |||
| 90a4544ab2 | |||
| 9b4557f197 | |||
| e594258cf3 | |||
| bb863c5b32 | |||
| 0797d1a517 | |||
| 8dc8b87580 | |||
| baeec369e6 | |||
| a1f2b22b19 | |||
| 5931f2f301 | |||
| 0b25df0ea7 | |||
| b75c7b177a | |||
| ccc5a4e17a | |||
| daa800c8b1 | |||
| a531973c0d | |||
| 4c8b0da3da | |||
| 9a8a014795 | |||
| 9640d336a6 | |||
| 12ce015d83 | |||
| f455bf4736 | |||
| 9bc66c7cf3 | |||
| e9022de150 | |||
| cb327b8073 | |||
| 1c354d18bb | |||
| 0ed88691c2 |
@@ -0,0 +1,21 @@
|
|||||||
|
name: Security
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master]
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * 1'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
scan:
|
||||||
|
uses: JonKazama-Hellion/security-workflows/.gitea/workflows/security-scan.yml@main
|
||||||
|
with:
|
||||||
|
# MessageStore.cs uses string-interpolation in CommandText for table
|
||||||
|
# names and clause-joins that come from internal code constants, not
|
||||||
|
# user input. Values are bound via SqlParameter, the SQL surface is
|
||||||
|
# local-only inside a Dalamud plugin. Semgrep matches the pattern
|
||||||
|
# without dataflow, so it flags those eight call sites; CodeQL
|
||||||
|
# would not. Suppressed for this repo only.
|
||||||
|
semgrep-exclude-rules: 'csharp.lang.security.sqli.csharp-sqli.csharp-sqli'
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# .githooks/pre-push — invokes preflight.sh (A/B/C/D=build).
|
||||||
|
exec scripts/preflight.sh
|
||||||
@@ -8,7 +8,7 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
Thanks for reporting. Please fill in the fields below so I can
|
Thanks for reporting. Please fill in the fields below so I can
|
||||||
reproduce the issue. If this is a security issue, stop here and
|
reproduce the issue. If this is a security issue, stop here and
|
||||||
use the [private vulnerability advisory](https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new)
|
report it privately to [kontakt@hellion-media.de](mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D)
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ blank_issues_enabled: false
|
|||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Security vulnerability
|
- name: Security vulnerability
|
||||||
url: https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new
|
url: mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
|
||||||
about: Do not open a public issue for security problems. Use the private advisory instead.
|
about: Do not open a public issue for security problems. Report by e-mail instead.
|
||||||
|
|
||||||
- name: Upstream Chat 2 issue
|
- name: Upstream Chat 2 issue
|
||||||
url: https://github.com/Infiziert90/ChatTwo/issues
|
url: https://github.com/Infiziert90/ChatTwo/issues
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ Thanks for contributing to HellionChat. Please fill in the sections
|
|||||||
below so the review goes quickly. Delete sections that genuinely do
|
below so the review goes quickly. Delete sections that genuinely do
|
||||||
not apply, but do not delete the whole template.
|
not apply, but do not delete the whole template.
|
||||||
|
|
||||||
If this is a security fix, stop here and use a private security
|
If this is a security fix, stop here and report it privately by
|
||||||
advisory instead:
|
e-mail instead of opening a public PR:
|
||||||
https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new
|
mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
@@ -23,7 +23,6 @@ https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new
|
|||||||
- [ ] Documentation only
|
- [ ] Documentation only
|
||||||
- [ ] Translation update
|
- [ ] Translation update
|
||||||
- [ ] Build, CI or tooling change
|
- [ ] Build, CI or tooling change
|
||||||
- [ ] Upstream cherry-pick from Chat 2
|
|
||||||
|
|
||||||
## Linked issue
|
## Linked issue
|
||||||
|
|
||||||
@@ -53,7 +52,6 @@ new commands, new translations, removed behaviour. If none, write
|
|||||||
bump and is it covered by the existing migration tests?
|
bump and is it covered by the existing migration tests?
|
||||||
- Does this change the schema in MessageStore?
|
- Does this change the schema in MessageStore?
|
||||||
- Does this change the repo.json or HellionChat.yaml manifest fields?
|
- Does this change the repo.json or HellionChat.yaml manifest fields?
|
||||||
- Does this affect the upstream cherry-pick path? See docs/UPSTREAM_SYNC.md.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
subtitle: Async-Lifecycle + Gitea-Cutover
|
||||||
|
versionsnatur: Architecture-Refactor
|
||||||
|
---
|
||||||
|
|
||||||
|
**Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover**
|
||||||
|
|
||||||
|
Vierter Sub-Patch der v1.4.x Polish-Sweep-Serie. Plugin-
|
||||||
|
Lifecycle auf Dalamud's `IAsyncDalamudPlugin`-API migriert
|
||||||
|
und das Custom-Repo zieht von GitHub auf Gitea um.
|
||||||
|
|
||||||
|
- **Async-Plugin-Architektur.** Konstruktor übernimmt nur
|
||||||
|
noch die Bootstrap-Essentials (Config-Load, Language-Init,
|
||||||
|
Conflict-Detection). Migrationen, Service-Allokationen,
|
||||||
|
Window-Konstruktion und Hook-Subscription wandern in
|
||||||
|
LoadAsync, sodass Dalamud die UI während der schweren
|
||||||
|
Arbeit responsive halten kann. Per-Line-CaptureFailure in
|
||||||
|
DisposeAsync mirrort LightlessSync's Pattern, plus
|
||||||
|
Idempotency-Guard gegen Reload-Races
|
||||||
|
- **Custom-Repo-URL umgezogen auf Gitea.** Bestehende Tester
|
||||||
|
müssen einmalig in XIVLauncher die Custom-Repo-URL auf
|
||||||
|
`https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
|
||||||
|
umstellen, dann XIVLauncher neu starten. Das alte
|
||||||
|
GitHub-Repo bleibt als eingefrorener v1.4.2-Snapshot
|
||||||
|
stehen und wird nicht mehr aktualisiert
|
||||||
|
- **Schema-Gate statt Migrations-Kette.** Die v9 → v16
|
||||||
|
Migrationen sind raus, ersetzt durch einen harten
|
||||||
|
Schema-Check in Phase 1. Configs auf Schema v16+ laden
|
||||||
|
direkt; ältere Configs (vor v1.2.1) bekommen jetzt eine
|
||||||
|
klare „install v1.4.2 first"-Fehlermeldung statt eines
|
||||||
|
impliziten Migrations-Pfads
|
||||||
|
- **AutoTranslate-Cache läuft im Hintergrund.** Der Cache
|
||||||
|
füllt sich jetzt fire-and-forget statt blockierend im
|
||||||
|
Plugin-Load. Trade-off: die erste Auto-Translate-Nutzung
|
||||||
|
einer Session kann einen kurzen Hitch haben, dafür kein
|
||||||
|
300-ms-Block beim Plugin-Start
|
||||||
|
- **Plugin-Load-Zeit ehrlich.** Median 3,7 s über fünf
|
||||||
|
Reloads, vergleichbar mit v1.4.2. Der Async-Refactor ist
|
||||||
|
Foundation für künftige Lazy-Init-Optimierungen (v1.4.4)
|
||||||
|
und Code-Architektur-Hygiene, kein direkter
|
||||||
|
User-spürbarer Speed-Win in dieser Release
|
||||||
|
|
||||||
|
Keine User-sichtbaren Funktions-Änderungen außer dem
|
||||||
|
Repo-URL-Update. Settings, Themes und Tabs bleiben
|
||||||
|
unangetastet.
|
||||||
@@ -8,19 +8,19 @@ Dalamud main plugin repo. To install:
|
|||||||
|
|
||||||
1. In XIVLauncher: **Settings → Experimental → Custom Plugin Repositories**
|
1. In XIVLauncher: **Settings → Experimental → Custom Plugin Repositories**
|
||||||
2. Add the URL:
|
2. Add the URL:
|
||||||
`https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/repo.json`
|
`https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
|
||||||
3. Enable, save, then `/xlplugins` → search **Hellion Chat** → install
|
3. Enable, save, then `/xlplugins` → search **Hellion Chat** → install
|
||||||
|
|
||||||
## Project documents
|
## Project documents
|
||||||
|
|
||||||
- [README](https://github.com/JonKazama-Hellion/HellionChat/blob/main/README.md) — features, architecture, build
|
- [README](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/README.md) — features, architecture, build
|
||||||
- [Privacy notice](https://github.com/JonKazama-Hellion/HellionChat/blob/main/PRIVACY.md) — what the plugin stores and sends
|
- [Privacy notice](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/PRIVACY.md) — what the plugin stores and sends
|
||||||
- [Third-party notices](https://github.com/JonKazama-Hellion/HellionChat/blob/main/docs/THIRD_PARTY_NOTICES.md) — dependencies and licences
|
- [Third-party notices](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/docs/THIRD_PARTY_NOTICES.md) — dependencies and licences
|
||||||
- [Security policy](https://github.com/JonKazama-Hellion/HellionChat/blob/main/SECURITY.md) — vulnerability reporting
|
- [Security policy](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SECURITY.md) — vulnerability reporting
|
||||||
- [Support](https://github.com/JonKazama-Hellion/HellionChat/blob/main/SUPPORT.md) — bug reports, questions, contact paths
|
- [Support](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SUPPORT.md) — bug reports, questions, contact paths
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
[EUPL-1.2](https://github.com/JonKazama-Hellion/HellionChat/blob/main/LICENSE).
|
[EUPL-1.2](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/LICENSE).
|
||||||
Based on [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna,
|
Based on [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna,
|
||||||
also EUPL-1.2.
|
also EUPL-1.2.
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ name: Build
|
|||||||
# Verifies that every push to main and every PR still builds against the
|
# Verifies that every push to main and every PR still builds against the
|
||||||
# current Dalamud staging branch. Does not produce release artefacts; the
|
# current Dalamud staging branch. Does not produce release artefacts; the
|
||||||
# release workflow handles that on tag.
|
# release workflow handles that on tag.
|
||||||
|
#
|
||||||
|
# Linux runner: gitea.com Cloud Actions provides ubuntu-latest. The plugin
|
||||||
|
# csproj targets net10.0-windows, but `dotnet build` cross-compiles on
|
||||||
|
# Linux as long as the Dalamud staging assemblies are present at the
|
||||||
|
# expected lookup path ($(HOME)/.xlcore/dalamud/Hooks/dev/, which the
|
||||||
|
# Dalamud SDK 15 uses on Linux).
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -21,7 +27,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build (Release)
|
name: Build (Release)
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -34,23 +40,14 @@ jobs:
|
|||||||
dotnet-version: 10.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Download Dalamud staging
|
- name: Download Dalamud staging
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
mkdir -p "$hooks"
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
unzip -oq dalamud.zip -d "$hooks"
|
||||||
|
|
||||||
- name: Restore
|
- name: Restore
|
||||||
run: dotnet restore HellionChat/HellionChat.csproj
|
run: dotnet restore HellionChat/HellionChat.csproj
|
||||||
|
|
||||||
- name: Build (Release)
|
- name: Build (Release)
|
||||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
||||||
|
|
||||||
- name: Upload build output
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: HellionChat-build-${{ github.run_number }}
|
|
||||||
path: HellionChat/bin/Release/**/HellionChat/**
|
|
||||||
if-no-files-found: warn
|
|
||||||
retention-days: 14
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
name: CodeQL
|
|
||||||
|
|
||||||
# Replaces the GitHub default-setup CodeQL scan. The default setup runs
|
|
||||||
# without resolving the Dalamud assemblies (they live in a user-AppData
|
|
||||||
# path) and reports "Low C# analysis quality" because call-target
|
|
||||||
# resolution sits at ~64%. This workflow downloads the Dalamud staging
|
|
||||||
# distribution before the build, runs a manual dotnet build, and then
|
|
||||||
# lets CodeQL analyse the fully-resolved compilation. Quality climbs
|
|
||||||
# back above the 85% thresholds.
|
|
||||||
#
|
|
||||||
# This workflow only consumes trusted inputs: the tag/branch ref via
|
|
||||||
# the standard checkout action, and the Dalamud distribution URL which
|
|
||||||
# is pinned to a goatcorp-controlled GitHub Pages target. No user-
|
|
||||||
# controlled event payload (issue title, PR body, commit message) flows
|
|
||||||
# into a run-step.
|
|
||||||
#
|
|
||||||
# Disable the default setup in the repo before this workflow lands:
|
|
||||||
# Settings -> Code security -> Code scanning -> "CodeQL analysis" tile
|
|
||||||
# -> Switch to advanced.
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
schedule:
|
|
||||||
- cron: '17 6 * * 1'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze-csharp:
|
|
||||||
name: Analyze (csharp)
|
|
||||||
runs-on: windows-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Setup .NET 10
|
|
||||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
|
||||||
with:
|
|
||||||
dotnet-version: 10.0.x
|
|
||||||
|
|
||||||
- name: Download Dalamud staging
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
|
||||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
|
||||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
languages: csharp
|
|
||||||
build-mode: manual
|
|
||||||
queries: security-extended
|
|
||||||
|
|
||||||
- name: Restore
|
|
||||||
run: dotnet restore HellionChat/HellionChat.csproj
|
|
||||||
|
|
||||||
- name: Build (Release)
|
|
||||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
|
||||||
|
|
||||||
- name: Perform CodeQL analysis
|
|
||||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
category: /language:csharp
|
|
||||||
|
|
||||||
analyze-actions:
|
|
||||||
name: Analyze (actions)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
languages: actions
|
|
||||||
build-mode: none
|
|
||||||
|
|
||||||
- name: Perform CodeQL analysis
|
|
||||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
||||||
with:
|
|
||||||
category: /language:actions
|
|
||||||
@@ -34,10 +34,9 @@ jobs:
|
|||||||
announce:
|
announce:
|
||||||
name: Post changelog to Hellion Forge
|
name: Post changelog to Hellion Forge
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# The DISCORD_FORGE_WEBHOOK secret lives under Settings → Environments
|
# The DISCORD_FORGE_WEBHOOK secret is set as a repo-level Actions Secret
|
||||||
# → Webhook (case-sensitive). Without this declaration the secret is
|
# on Gitea (Settings → Actions → Secrets). Repo-level secrets are in
|
||||||
# not in scope for the job.
|
# scope for every job by default, no environment: declaration needed.
|
||||||
environment: Webhook
|
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -134,7 +133,7 @@ jobs:
|
|||||||
# ---------- Embed-Payload bauen ----------
|
# ---------- Embed-Payload bauen ----------
|
||||||
$payload = [ordered]@{
|
$payload = [ordered]@{
|
||||||
username = "Forge Herald"
|
username = "Forge Herald"
|
||||||
avatar_url = "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/icon.png"
|
avatar_url = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png"
|
||||||
content = "<@&1500489631555260446>"
|
content = "<@&1500489631555260446>"
|
||||||
allowed_mentions = [ordered]@{
|
allowed_mentions = [ordered]@{
|
||||||
parse = @()
|
parse = @()
|
||||||
@@ -143,7 +142,7 @@ jobs:
|
|||||||
embeds = @(
|
embeds = @(
|
||||||
[ordered]@{
|
[ordered]@{
|
||||||
title = $title
|
title = $title
|
||||||
url = "https://github.com/JonKazama-Hellion/HellionChat/releases/tag/$tag"
|
url = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/$tag"
|
||||||
color = 12730636
|
color = 12730636
|
||||||
description = $description
|
description = $description
|
||||||
footer = [ordered]@{ text = $footerText }
|
footer = [ordered]@{ text = $footerText }
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ name: Release
|
|||||||
|
|
||||||
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
||||||
# current Dalamud staging branch, locates the latest.zip produced by
|
# current Dalamud staging branch, locates the latest.zip produced by
|
||||||
# DalamudPackager and attaches it to the matching GitHub Release.
|
# DalamudPackager and attaches it to the matching Gitea Release.
|
||||||
#
|
#
|
||||||
# User-controlled inputs touched by this workflow:
|
# User-controlled inputs touched by this workflow:
|
||||||
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
||||||
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
||||||
# All other values are either repo-controlled (paths under
|
# All other values are either repo-controlled (paths under
|
||||||
# HellionChat/bin/Release derived from Get-ChildItem) or pinned URLs to
|
# HellionChat/bin/Release derived from find / Get-ChildItem) or pinned
|
||||||
# goatcorp / GitHub. Nothing from a webhook event payload (issue/PR
|
# URLs to goatcorp / gitea. Nothing from a webhook event payload (issue/PR
|
||||||
# titles, commit messages, etc.) flows into a run-step.
|
# titles, commit messages, etc.) flows into a run-step.
|
||||||
|
#
|
||||||
|
# Linux runner: gitea.com Cloud Actions only ships ubuntu-latest. The
|
||||||
|
# plugin csproj targets net10.0-windows, `dotnet build` cross-compiles on
|
||||||
|
# Linux when the Dalamud staging assemblies sit under $(HOME)/.xlcore/...
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -33,7 +37,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Build and attach release ZIP
|
name: Build and attach release ZIP
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -52,27 +56,25 @@ jobs:
|
|||||||
dotnet-version: 10.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Download Dalamud staging
|
- name: Download Dalamud staging
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
mkdir -p "$hooks"
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
unzip -oq dalamud.zip -d "$hooks"
|
||||||
|
|
||||||
- name: Build (Release)
|
- name: Build (Release)
|
||||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
||||||
|
|
||||||
- name: Locate latest.zip
|
- name: Locate latest.zip
|
||||||
id: locate
|
id: locate
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$zip = Get-ChildItem -Path HellionChat\bin\Release -Recurse -Filter latest.zip | Select-Object -First 1
|
zip="$(find HellionChat/bin/Release -name latest.zip -print -quit)"
|
||||||
if (-not $zip)
|
if [ -z "$zip" ]; then
|
||||||
{
|
echo "latest.zip not found under HellionChat/bin/Release" >&2
|
||||||
throw "latest.zip not found under HellionChat\bin\Release"
|
exit 1
|
||||||
}
|
fi
|
||||||
Write-Host "Found: $($zip.FullName)"
|
echo "Found: $zip"
|
||||||
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
echo "path=$zip" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# Build a release body from the matching changelog block in
|
# Build a release body from the matching changelog block in
|
||||||
# HellionChat.yaml plus a static install / docs footer. Fails the
|
# HellionChat.yaml plus a static install / docs footer. Fails the
|
||||||
@@ -150,8 +152,13 @@ jobs:
|
|||||||
Write-Host $body
|
Write-Host $body
|
||||||
Write-Host "----------------------------------------"
|
Write-Host "----------------------------------------"
|
||||||
|
|
||||||
- name: Attach to GitHub release
|
# Gitea-native release action. Creates the release if the tag has no
|
||||||
uses: softprops/action-gh-release@v3
|
# release yet, or updates the existing one. body_path provides the
|
||||||
|
# generated release body, files attaches latest.zip. The auto-injected
|
||||||
|
# GITHUB_TOKEN on Gitea Actions has Gitea-API scope and is sufficient
|
||||||
|
# for release write.
|
||||||
|
- name: Attach to Gitea release
|
||||||
|
uses: https://gitea.com/actions/release-action@main
|
||||||
with:
|
with:
|
||||||
# Explicit tag_name so the action targets the correct release in
|
# Explicit tag_name so the action targets the correct release in
|
||||||
# both push:tags (auto) and workflow_dispatch (manual recovery)
|
# both push:tags (auto) and workflow_dispatch (manual recovery)
|
||||||
@@ -160,5 +167,4 @@ jobs:
|
|||||||
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
|
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
|
||||||
files: ${{ steps.locate.outputs.path }}
|
files: ${{ steps.locate.outputs.path }}
|
||||||
body_path: release-body.md
|
body_path: release-body.md
|
||||||
fail_on_unmatched_files: true
|
api_key: ${{ secrets.GITHUB_TOKEN }}
|
||||||
generate_release_notes: false
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
.envrc
|
.envrc
|
||||||
!.env.example
|
!.env.example
|
||||||
.vscode/
|
.vscode/
|
||||||
scripts/
|
scripts/setup-dev-env.sh
|
||||||
|
|
||||||
# Local test project (stays out of the published plugin repo;
|
# Local test project (stays out of the published plugin repo;
|
||||||
# pure-function safety net for refactor cycles)
|
# pure-function safety net for refactor cycles)
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ to make a contribution land smoothly.
|
|||||||
- Read the [README](README.md) so you understand the scope: a
|
- Read the [README](README.md) so you understand the scope: a
|
||||||
privacy-focused, EUPL-1.2-licensed Dalamud plugin that intentionally
|
privacy-focused, EUPL-1.2-licensed Dalamud plugin that intentionally
|
||||||
removes the upstream webinterface and ships privacy-first defaults.
|
removes the upstream webinterface and ships privacy-first defaults.
|
||||||
- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Cherry-picks
|
- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Active
|
||||||
from upstream Chat 2 are selective and deliberate; not everything
|
cherry-picking from upstream Chat 2 has ended in the v1.4.x cycle;
|
||||||
that lands there belongs here.
|
HellionChat continues as an independent codebase. Existing
|
||||||
|
upstream-derived code keeps its attribution. New contributions
|
||||||
|
stand on their own and do not need to be cherry-pick-compatible.
|
||||||
- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes
|
- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes
|
||||||
through a private advisory, never a public issue or PR.
|
through a private advisory, never a public issue or PR.
|
||||||
- Read the [Code of Conduct](CODE_OF_CONDUCT.md).
|
- Read the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||||
@@ -43,9 +45,11 @@ to make a contribution land smoothly.
|
|||||||
"Was gegenüber Chat 2 fehlt".
|
"Was gegenüber Chat 2 fehlt".
|
||||||
- Features that bypass the privacy filter or weaken the default
|
- Features that bypass the privacy filter or weaken the default
|
||||||
retention behaviour without an explicit, documented opt-in.
|
retention behaviour without an explicit, documented opt-in.
|
||||||
- Sweeping refactors that touch large parts of the codebase. They make
|
- Sweeping refactors that touch large parts of the codebase. The
|
||||||
selective upstream cherry-picks much harder and the maintenance cost
|
maintenance cost outweighs the benefit for a one-person project.
|
||||||
outweighs the benefit for a one-person project.
|
(This used to be doubly important because of the upstream
|
||||||
|
cherry-pick path; that path is closed now, but the rule still
|
||||||
|
holds on its own merits.)
|
||||||
- AI-generated code dropped in without disclosure or human review. See
|
- AI-generated code dropped in without disclosure or human review. See
|
||||||
[`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for how I handle
|
[`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for how I handle
|
||||||
AI assistance on my side; I expect comparable transparency from
|
AI assistance on my side; I expect comparable transparency from
|
||||||
@@ -117,9 +121,15 @@ Hellion-specific strings live in
|
|||||||
direct pull requests.
|
direct pull requests.
|
||||||
|
|
||||||
The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx`
|
The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx`
|
||||||
are **not** translated here. They are owned by the upstream project
|
are **not** translated here. They are kept as-is from the last
|
||||||
and synced in via cherry-pick. Please contribute those to
|
upstream sync and remain the work of the Chat 2 Crowdin community.
|
||||||
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) instead.
|
Active cherry-picking from upstream ended in the v1.4.x cycle (see
|
||||||
|
[`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md)), so future
|
||||||
|
translation improvements to those upstream strings will not flow
|
||||||
|
into HellionChat automatically anymore. If you have improvements
|
||||||
|
for the original Chat 2 strings, please contribute them to
|
||||||
|
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo)
|
||||||
|
directly.
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
@@ -145,3 +155,19 @@ I respond on weekdays during European business hours and take weekends
|
|||||||
and FFXIV patch days off. A pull request that sits for a few days has
|
and FFXIV patch days off. A pull request that sits for a few days has
|
||||||
not been ignored. Pinging once after a week is fine; please do not
|
not been ignored. Pinging once after a week is fine; please do not
|
||||||
ping daily.
|
ping daily.
|
||||||
|
|
||||||
|
## First-time setup
|
||||||
|
|
||||||
|
After cloning, run once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-hooks.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This wires `core.hooksPath` to `.githooks/`. The pre-push hook runs preflight
|
||||||
|
(versions/manifest/changelog/build).
|
||||||
|
|
||||||
|
### Test suite
|
||||||
|
|
||||||
|
The plugin's test suite lives in a separate local repository and is not part of
|
||||||
|
this codebase. If you need access for development, contact the maintainer.
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ namespace HellionChat.Branding;
|
|||||||
// Centralised so a future invite rotation only touches one file. The same
|
// Centralised so a future invite rotation only touches one file. The same
|
||||||
// link is currently hard-coded in repo.json, README.md, SUPPORT.md,
|
// link is currently hard-coded in repo.json, README.md, SUPPORT.md,
|
||||||
// CONTRIBUTORS.md and HellionChat.yaml — those will be migrated to consume
|
// CONTRIBUTORS.md and HellionChat.yaml — those will be migrated to consume
|
||||||
// this constant in a separate housekeeping sweep, but that's out of scope
|
// this constant in a separate housekeeping sweep
|
||||||
// for this Cycle.
|
|
||||||
internal static class BrandingLinks
|
internal static class BrandingLinks
|
||||||
{
|
{
|
||||||
public const string HellionForgeDiscordInvite = "https://discord.gg/X9V7Kcv5gR";
|
public const string HellionForgeDiscordInvite = "https://discord.gg/X9V7Kcv5gR";
|
||||||
|
|||||||
@@ -100,6 +100,19 @@ public class FontManager
|
|||||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges);
|
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Async wrapper around <see cref="BuildFonts"/> for the Phase-1 LoadAsync
|
||||||
|
/// path. The font-atlas build is CPU-bound, so we offload via Task.Run and
|
||||||
|
/// honour the cancellation token at the scheduling boundary; this lets the
|
||||||
|
/// font build run in parallel with the theme init without blocking the
|
||||||
|
/// loader. Settings-driven manual rebuilds keep using the sync entry point.
|
||||||
|
/// </summary>
|
||||||
|
public async Task BuildFontsAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
await Task.Run(BuildFonts, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public void BuildFonts()
|
public void BuildFonts()
|
||||||
{
|
{
|
||||||
SetUpRanges();
|
SetUpRanges();
|
||||||
|
|||||||
@@ -16,6 +16,18 @@ public unsafe class ChatBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void SendMessage(string message)
|
public static void SendMessage(string message)
|
||||||
|
{
|
||||||
|
var bytes = ValidateMessage(message);
|
||||||
|
SendMessageUnsafe(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation split out so the deterministic checks (UTF-8 length, sanitise
|
||||||
|
// round-trip) can run in xUnit without ClientStructs game memory. The
|
||||||
|
// sanitiser is injectable so tests can pin throw behaviour without invoking
|
||||||
|
// Utf8String->SanitizeString, which only resolves in-process. Returns the
|
||||||
|
// already-encoded bytes so SendMessage doesn't pay GetBytes twice.
|
||||||
|
// TEST-MIRROR: ../../../Hellion Build test/GameFunctions/ChatBoxTests.cs
|
||||||
|
internal static byte[] ValidateMessage(string message, Func<string, string>? sanitiserOverride = null)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes(message);
|
var bytes = Encoding.UTF8.GetBytes(message);
|
||||||
if (bytes.Length == 0)
|
if (bytes.Length == 0)
|
||||||
@@ -24,10 +36,11 @@ public unsafe class ChatBox
|
|||||||
if (bytes.Length > 500)
|
if (bytes.Length > 500)
|
||||||
throw new ArgumentException(Language.ChatBox_Error_Too_Long, nameof(message));
|
throw new ArgumentException(Language.ChatBox_Error_Too_Long, nameof(message));
|
||||||
|
|
||||||
if (message.Length != SanitiseText(message).Length)
|
var sanitiser = sanitiserOverride ?? SanitiseText;
|
||||||
|
if (message.Length != sanitiser(message).Length)
|
||||||
throw new ArgumentException(Language.ChatBox_Error_Invalid, nameof(message));
|
throw new ArgumentException(Language.ChatBox_Error_Invalid, nameof(message));
|
||||||
|
|
||||||
SendMessageUnsafe(bytes);
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SanitiseText(string text)
|
private static string SanitiseText(string text)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
||||||
called out in the yaml changelog so users can see what it
|
called out in the yaml changelog so users can see what it
|
||||||
derives from. -->
|
derives from. -->
|
||||||
<Version>1.4.2</Version>
|
<Version>1.4.3</Version>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<!-- Honor packages.lock.json on restore so floating version ranges
|
<!-- Honor packages.lock.json on restore so floating version ranges
|
||||||
|
|||||||
@@ -58,18 +58,27 @@ description: |-
|
|||||||
its tab aggregation behind the same one-second cache it uses
|
its tab aggregation behind the same one-second cache it uses
|
||||||
for the format strings.
|
for the format strings.
|
||||||
|
|
||||||
|
v1.4.3 — Plugin-Load Async-Init plus Repo-Cutover. Plugin
|
||||||
|
migrated to Dalamud's IAsyncDalamudPlugin so the heavy work
|
||||||
|
(migrations, service allocations, window construction, hook
|
||||||
|
subscription) runs in LoadAsync without blocking Dalamud's
|
||||||
|
UI. Schema-gate replaces the v9 → v16 migration chain;
|
||||||
|
configs on schema v16+ load directly. Custom-repo URL moves
|
||||||
|
to gitea.hellion-forge.cloud, the GitHub repo stays as a
|
||||||
|
frozen v1.4.2 snapshot.
|
||||||
|
|
||||||
Based on Chat 2 by Infi and Anna, licensed under EUPL-1.2.
|
Based on Chat 2 by Infi and Anna, licensed under EUPL-1.2.
|
||||||
|
|
||||||
Modding & support: join the Hellion Forge Discord at
|
Modding & support: join the Hellion Forge Discord at
|
||||||
https://discord.gg/X9V7Kcv5gR — community for Hellion Chat and
|
https://discord.gg/X9V7Kcv5gR — community for Hellion Chat and
|
||||||
other Hellion Online Media plugins/tools.
|
other Hellion Online Media plugins/tools.
|
||||||
repo_url: https://github.com/JonKazama-Hellion/HellionChat
|
repo_url: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat
|
||||||
accepts_feedback: true
|
accepts_feedback: true
|
||||||
icon_url: https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/icon.png
|
icon_url: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png
|
||||||
image_urls:
|
image_urls:
|
||||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/chatWindow.png
|
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/chatWindow.png
|
||||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/settingsOverview.png
|
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/settingsOverview.png
|
||||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/themesPicker.png
|
- https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/themesPicker.png
|
||||||
tags:
|
tags:
|
||||||
- Social
|
- Social
|
||||||
- UI
|
- UI
|
||||||
@@ -77,6 +86,42 @@ tags:
|
|||||||
- Replacement
|
- Replacement
|
||||||
- Privacy
|
- Privacy
|
||||||
changelog: |-
|
changelog: |-
|
||||||
|
**Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover (2026-05-08)**
|
||||||
|
|
||||||
|
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin`
|
||||||
|
API. The constructor now does only the bootstrap-essentials
|
||||||
|
(config load, language init, conflict detection); migrations,
|
||||||
|
service allocations, window construction and hook subscription
|
||||||
|
move to LoadAsync. Dalamud can keep its UI responsive while the
|
||||||
|
heavy work runs.
|
||||||
|
|
||||||
|
- IAsyncDalamudPlugin two-phase load with per-line CaptureFailure
|
||||||
|
in DisposeAsync (mirrors LightlessSync's pattern); idempotency
|
||||||
|
guard protects against reload races
|
||||||
|
- Schema-gate replaces the v9 → v16 migration chain. Configs
|
||||||
|
on schema v16+ load directly; older configs trigger an
|
||||||
|
"install v1.4.2 first" error so the historic migration
|
||||||
|
path stays intact
|
||||||
|
- AutoTranslate.PreloadCache moved off the load path. First
|
||||||
|
use may have a sub-second hitch instead of every-load; the
|
||||||
|
upstream chose differently, we accept first-use latency
|
||||||
|
- FontManager.BuildFonts is called sync at the start of
|
||||||
|
LoadAsync; Dalamud rebuilds the font atlas on its own
|
||||||
|
pipeline so the custom Hellion-Exo2 font appears with a
|
||||||
|
brief font-pop after load (matches ChatTwo's behaviour)
|
||||||
|
- Custom-repo URL moved to gitea.hellion-forge.cloud/
|
||||||
|
JonKazama-Hellion/HellionChat. GitHub repo stays as a
|
||||||
|
frozen v1.4.2 snapshot; new releases ship from Gitea.
|
||||||
|
Existing testers need to update the custom-repo URL once
|
||||||
|
- Plugin-load time in this release sits at ~3.7 s median
|
||||||
|
(5 reloads), comparable to v1.4.2. Async migration is
|
||||||
|
foundational for v1.4.4 Lazy-Init optimisations rather
|
||||||
|
than an immediate user-perceived win
|
||||||
|
|
||||||
|
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
|
||||||
|
|
||||||
|
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||||
|
|
||||||
**Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path**
|
**Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path**
|
||||||
|
|
||||||
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame
|
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame
|
||||||
@@ -171,26 +216,6 @@ changelog: |-
|
|||||||
|
|
||||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||||
|
|
||||||
**Hellion Chat 1.3.0 - Plugin Integrations: Honorific**
|
|
||||||
|
|
||||||
First step on the plugin-integration roadmap. HellionChat now
|
|
||||||
listens to Honorific and shows your custom title in the chat
|
|
||||||
header. The slot auto-hides when Honorific is not installed,
|
|
||||||
when no custom title is active, or when you are using the
|
|
||||||
original FFXIV title.
|
|
||||||
|
|
||||||
- New "Integrations" settings tab
|
|
||||||
- Honorific integration with auto-detection and live updates
|
|
||||||
- "Coming soon" preview of the next five planned integrations:
|
|
||||||
context menu actions, smart notifications, RP status block,
|
|
||||||
ExtraChat channels, and quick DM compose
|
|
||||||
- Maintainer attribution buttons for Honorific repo and Caraxi
|
|
||||||
- New service-class pattern under HellionChat/Integrations/
|
|
||||||
|
|
||||||
Modding and support: join Hellion Forge - https://discord.gg/X9V7Kcv5gR
|
|
||||||
|
|
||||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Earlier history: https://github.com/JonKazama-Hellion/HellionChat/releases
|
Earlier history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Diagnostics;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
using HellionChat.Ipc;
|
using HellionChat.Ipc;
|
||||||
using HellionChat.Resources;
|
using HellionChat.Resources;
|
||||||
using HellionChat.Ui;
|
using HellionChat.Ui;
|
||||||
@@ -17,7 +18,7 @@ using Dalamud.Interface.ImGuiFileDialog;
|
|||||||
namespace HellionChat;
|
namespace HellionChat;
|
||||||
|
|
||||||
// ReSharper disable once ClassNeverInstantiated.Global
|
// ReSharper disable once ClassNeverInstantiated.Global
|
||||||
public sealed class Plugin : IDalamudPlugin
|
public sealed class Plugin : IAsyncDalamudPlugin
|
||||||
{
|
{
|
||||||
public const string PluginName = "Hellion Chat";
|
public const string PluginName = "Hellion Chat";
|
||||||
|
|
||||||
@@ -41,32 +42,42 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
[PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
|
[PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
|
||||||
[PluginService] public static IPlayerState PlayerState { get; private set; } = null!;
|
[PluginService] public static IPlayerState PlayerState { get; private set; } = null!;
|
||||||
[PluginService] public static ISeStringEvaluator Evaluator { get; private set; } = null!;
|
[PluginService] public static ISeStringEvaluator Evaluator { get; private set; } = null!;
|
||||||
|
[PluginService] public static ISelfTestRegistry SelfTestRegistry { get; private set; } = null!;
|
||||||
|
|
||||||
public static Configuration Config = null!;
|
public static Configuration Config = null!;
|
||||||
public static FileDialogManager FileDialogManager { get; private set; } = null!;
|
public static FileDialogManager FileDialogManager { get; private set; } = null!;
|
||||||
|
|
||||||
public readonly WindowSystem WindowSystem = new(PluginName);
|
public readonly WindowSystem WindowSystem = new(PluginName);
|
||||||
public SettingsWindow SettingsWindow { get; }
|
|
||||||
public ChatLogWindow ChatLogWindow { get; }
|
|
||||||
public DbViewer DbViewer { get; }
|
|
||||||
public InputPreview InputPreview { get; }
|
|
||||||
public CommandHelpWindow CommandHelpWindow { get; }
|
|
||||||
public SeStringDebugger SeStringDebugger { get; }
|
|
||||||
public FirstRunWizard FirstRunWizard { get; }
|
|
||||||
public DebuggerWindow DebuggerWindow { get; }
|
|
||||||
|
|
||||||
internal Commands Commands { get; }
|
// v1.4.3: properties moved from { get; } to { get; private set; } = null!;
|
||||||
internal GameFunctions.GameFunctions Functions { get; }
|
// because LoadAsync now owns construction of the Phase-2 services.
|
||||||
internal MessageManager MessageManager { get; }
|
// Phase-1 services use the same shape for consistency, even though
|
||||||
internal AutoTellTabsService AutoTellTabsService { get; }
|
// they're still allocated in the ctor.
|
||||||
internal IpcManager Ipc { get; }
|
public SettingsWindow SettingsWindow { get; private set; } = null!;
|
||||||
internal ExtraChat ExtraChat { get; }
|
public ChatLogWindow ChatLogWindow { get; private set; } = null!;
|
||||||
internal TypingIpc TypingIpc { get; }
|
public DbViewer DbViewer { get; private set; } = null!;
|
||||||
internal FontManager FontManager { get; }
|
public InputPreview InputPreview { get; private set; } = null!;
|
||||||
|
public CommandHelpWindow CommandHelpWindow { get; private set; } = null!;
|
||||||
|
public SeStringDebugger SeStringDebugger { get; private set; } = null!;
|
||||||
|
public FirstRunWizard FirstRunWizard { get; private set; } = null!;
|
||||||
|
public DebuggerWindow DebuggerWindow { get; private set; } = null!;
|
||||||
|
|
||||||
|
internal Commands Commands { get; private set; } = null!;
|
||||||
|
internal GameFunctions.GameFunctions Functions { get; private set; } = null!;
|
||||||
|
internal MessageManager MessageManager { get; private set; } = null!;
|
||||||
|
internal AutoTellTabsService AutoTellTabsService { get; private set; } = null!;
|
||||||
|
internal IpcManager Ipc { get; private set; } = null!;
|
||||||
|
internal ExtraChat ExtraChat { get; private set; } = null!;
|
||||||
|
internal TypingIpc TypingIpc { get; private set; } = null!;
|
||||||
|
internal FontManager FontManager { get; private set; } = null!;
|
||||||
internal Themes.ThemeRegistry ThemeRegistry { get; private set; } = null!;
|
internal Themes.ThemeRegistry ThemeRegistry { get; private set; } = null!;
|
||||||
internal Ui.StatusBar StatusBar { get; private set; } = null!;
|
internal Ui.StatusBar StatusBar { get; private set; } = null!;
|
||||||
internal Integrations.HonorificService HonorificService { get; private set; } = null!;
|
internal Integrations.HonorificService HonorificService { get; private set; } = null!;
|
||||||
|
|
||||||
|
// (B3) Lightless idempotency guard — Dalamud may fire DisposeAsync twice
|
||||||
|
// in a reload race; second call short-circuits.
|
||||||
|
private int _disposeStarted;
|
||||||
|
|
||||||
internal int DeferredSaveFrames = -1;
|
internal int DeferredSaveFrames = -1;
|
||||||
|
|
||||||
// Serialises retention sweeps. The 24h auto-sweep on plugin load and
|
// Serialises retention sweeps. The 24h auto-sweep on plugin load and
|
||||||
@@ -97,324 +108,59 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
|
|
||||||
public Plugin()
|
public Plugin()
|
||||||
{
|
{
|
||||||
|
// Phase-1 ctor stays minimal: bootstrap-essentials only (conflict
|
||||||
|
// gate, config load, language + ImGui init, WindowSystem skeleton).
|
||||||
|
// Schema migrations and every service / window allocation moved to
|
||||||
|
// LoadAsync so the sync ctor returns fast. On failure here nothing
|
||||||
|
// is initialized yet, so just throw — there is nothing to clean up.
|
||||||
|
|
||||||
// Refuse to start if upstream Chat 2 is loaded — prevents IPC
|
// Refuse to start if upstream Chat 2 is loaded — prevents IPC
|
||||||
// channel collisions and double-replacement of the in-game chat
|
// channel collisions and double-replacement of the in-game chat
|
||||||
// window. Throwing here makes Dalamud abort the load cleanly with
|
// window. Throwing here makes Dalamud abort the load cleanly with
|
||||||
// our localized message instead of crashing FFXIV mid-frame.
|
// our localized message instead of crashing FFXIV mid-frame.
|
||||||
ChatTwoConflictDetector.ThrowIfChatTwoIsLoaded(Interface);
|
ChatTwoConflictDetector.ThrowIfChatTwoIsLoaded(Interface);
|
||||||
|
|
||||||
|
GameStarted = Process.GetCurrentProcess().StartTime.ToUniversalTime();
|
||||||
|
|
||||||
|
// Hellion Chat: take over config + database from upstream ChatTwo
|
||||||
|
// before Dalamud loads our plugin config. Idempotent: only acts on
|
||||||
|
// the first start where the legacy paths exist and ours don't.
|
||||||
|
MigrateFromChatTwoLayout();
|
||||||
|
|
||||||
|
Config = Interface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||||
|
|
||||||
|
// Schema-gate: v1.4.3 only supports config schema v16. Older configs
|
||||||
|
// went through their migrations in v1.2.1 (v15→v16) and earlier; users
|
||||||
|
// who skipped past those releases need to install v1.4.2 first to run
|
||||||
|
// the migration chain, then upgrade to v1.4.3.
|
||||||
|
if (Config.Version < 16)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"HellionChat v1.4.3 requires config schema v16, got v{Config.Version}. " +
|
||||||
|
"Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.3.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hellion Chat — Auto-Tell-Tabs Defense-in-Depth. SaveConfig
|
||||||
|
// already strips temp tabs before persistence, but a previous
|
||||||
|
// crash or external write could have left them in the JSON.
|
||||||
|
// Drop them on load to guarantee the session-only invariant.
|
||||||
|
Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||||
|
|
||||||
|
LanguageChanged(Interface.UiLanguage);
|
||||||
|
ImGuiUtil.Initialize(this);
|
||||||
|
|
||||||
|
DeferredSaveFrames = -1;
|
||||||
|
|
||||||
|
// WindowSystem skeleton is initialised by the readonly field above —
|
||||||
|
// no AddWindow yet; window construction lives in LoadAsync.
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
GameStarted = Process.GetCurrentProcess().StartTime.ToUniversalTime();
|
|
||||||
|
|
||||||
// Hellion Chat: take over config + database from upstream ChatTwo
|
|
||||||
// before Dalamud loads our plugin config. Idempotent: only acts on
|
|
||||||
// the first start where the legacy paths exist and ours don't.
|
|
||||||
MigrateFromChatTwoLayout();
|
|
||||||
|
|
||||||
Config = Interface.GetPluginConfig() as Configuration ?? new Configuration();
|
|
||||||
|
|
||||||
// Hellion Chat — Auto-Tell-Tabs Defense-in-Depth. SaveConfig
|
|
||||||
// already strips temp tabs before persistence, but a previous
|
|
||||||
// crash or external write could have left them in the JSON.
|
|
||||||
// Drop them on load to guarantee the session-only invariant.
|
|
||||||
Config.Tabs.RemoveAll(t => t.IsTempTab);
|
|
||||||
|
|
||||||
// Hellion Chat v9 → v10 — wipes the configuration so the new 8-tab
|
|
||||||
// layout starts from defaults instead of mapping every previous setting
|
|
||||||
// to its new position. Backup-Failure ist non-fatal, der Wipe läuft
|
|
||||||
// trotzdem; dem User fehlt dann nur das manuelle Restore-Sicherheitsnetz.
|
|
||||||
if (Config.Version < 10)
|
|
||||||
{
|
|
||||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
|
||||||
if (pluginConfigsDir is not null)
|
|
||||||
{
|
|
||||||
var liveConfigPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json");
|
|
||||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v10-backup");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(liveConfigPath))
|
|
||||||
{
|
|
||||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "HellionChat: pre-v10 config backup failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Config = new Configuration
|
|
||||||
{
|
|
||||||
Version = 10,
|
|
||||||
FirstRunCompleted = true,
|
|
||||||
};
|
|
||||||
SaveConfig();
|
|
||||||
|
|
||||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
|
||||||
{
|
|
||||||
Title = HellionStrings.SettingsRefactor_Migration_Title,
|
|
||||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
|
||||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
|
||||||
InitialDuration = TimeSpan.FromSeconds(25),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hellion Chat v10 → v11 — adds the global Configuration.PopOutInputEnabled
|
|
||||||
// master switch and SeenPopOutInputHint flag for the v0.6.0 pop-out
|
|
||||||
// input feature. Lightweight migration: defaults both fields,
|
|
||||||
// no user-facing notification because the change is opt-in only.
|
|
||||||
if (Config.Version < 11)
|
|
||||||
{
|
|
||||||
Config.PopOutInputEnabled = false;
|
|
||||||
Config.SeenPopOutInputHint = false;
|
|
||||||
Config.Version = 11;
|
|
||||||
SaveConfig();
|
|
||||||
Log.Information(
|
|
||||||
"Migrated config v10 → v11: PopOutInputEnabled added (global, default off), " +
|
|
||||||
"SeenPopOutInputHint added (default false)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hellion Chat v11 → v12 — flips Configuration.PopOutInputEnabled from
|
|
||||||
// the v0.6.0 opt-in default (false) to opt-out (true) per v0.6.1 UX
|
|
||||||
// polish. Hard-flip is a deliberate design call (see Spec section 5.7);
|
|
||||||
// users are notified via the v0.6.1 hint banner (SeenPopOutHeaderHint
|
|
||||||
// reset). Re-toggle after migration is preserved because this block
|
|
||||||
// only fires for Version < 12.
|
|
||||||
if (Config.Version < 12)
|
|
||||||
{
|
|
||||||
Config.PopOutInputEnabled = true;
|
|
||||||
Config.SeenPopOutHeaderHint = false;
|
|
||||||
Config.Version = 12;
|
|
||||||
SaveConfig();
|
|
||||||
Log.Information(
|
|
||||||
"Migrated config v11 → v12: PopOutInputEnabled hard-flipped to true (v0.6.1 default), " +
|
|
||||||
"SeenPopOutHeaderHint reset to false (v0.6.1 banner re-armed)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hellion Chat v12 → v13 — hard-resets the tab layout to the
|
|
||||||
// sharpened v1.0.0 defaults (5 thematic tabs, see TabsUtil and
|
|
||||||
// the default-fill block below). Existing tab state is wiped
|
|
||||||
// because per-channel mapping from the old General preset to
|
|
||||||
// the new General/System split would be ambiguous and would
|
|
||||||
// produce subtly wrong results for users who tweaked the old
|
|
||||||
// layout. A timestamped backup of the live config is written
|
|
||||||
// alongside it as a manual restore safety net. The wipe scope
|
|
||||||
// is intentionally narrow: only Config.Tabs is reset; Privacy,
|
|
||||||
// Retention, Theme and every other knob keeps its current value.
|
|
||||||
if (Config.Version < 13)
|
|
||||||
{
|
|
||||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
|
||||||
if (pluginConfigsDir is not null)
|
|
||||||
{
|
|
||||||
var liveConfigPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json");
|
|
||||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v13-backup");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(liveConfigPath))
|
|
||||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "HellionChat: pre-v13 config backup failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.Tabs.Clear();
|
|
||||||
Config.Version = 13;
|
|
||||||
SaveConfig();
|
|
||||||
|
|
||||||
Log.Information(
|
|
||||||
"Migrated config v12 → v13: tab layout hard-reset to v1.0.0 defaults; " +
|
|
||||||
"pre-v13 config backup written next to the live file. " +
|
|
||||||
"Default tabs will be populated by the Tabs.Count == 0 block.");
|
|
||||||
|
|
||||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
|
||||||
{
|
|
||||||
Title = HellionStrings.SettingsRefactor_Migration_Title,
|
|
||||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
|
||||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
|
||||||
InitialDuration = TimeSpan.FromSeconds(25),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hellion Chat v13 → v14 — theme-engine migration. Alle User landen
|
|
||||||
// auf "hellion-arctic" als neues Default-Theme; die alte
|
|
||||||
// HellionThemeEnabled-Flag wird deprecated und nur noch ein Release
|
|
||||||
// als Safety-Net im JSON behalten. Window-Opacity wandert von
|
|
||||||
// HellionThemeWindowOpacity in das neue WindowOpacity-Feld.
|
|
||||||
//
|
|
||||||
// v1.4.0 (F5.4): Pre-v13-Backup wird gelesen, HellionThemeWindowOpacity
|
|
||||||
// ins neue Feld gezogen. Override nur wenn WindowOpacity noch beim
|
|
||||||
// Default sitzt — sonst hat der User in der Zwischenzeit (z.B. via
|
|
||||||
// WindowAlpha → WindowOpacity in v15→v16) explizit etwas gesetzt.
|
|
||||||
if (Config.Version < 14)
|
|
||||||
{
|
|
||||||
Config.Theme = "hellion-arctic";
|
|
||||||
|
|
||||||
var oldThemeOpacity = TryReadPreV13ThemeOpacity();
|
|
||||||
if (oldThemeOpacity is { } legacy
|
|
||||||
&& Math.Abs(Config.WindowOpacity - 0.85f) < 0.001f)
|
|
||||||
{
|
|
||||||
Config.WindowOpacity = Math.Clamp(legacy, 0.5f, 1.0f);
|
|
||||||
Log.Information(
|
|
||||||
$"Migrated pre-v13 HellionThemeWindowOpacity {legacy} to WindowOpacity {Config.WindowOpacity}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.ReduceMotion = false;
|
|
||||||
Config.UseCompactDensity = false;
|
|
||||||
Config.Version = 14;
|
|
||||||
SaveConfig();
|
|
||||||
Log.Information(
|
|
||||||
"Migrated config v13 → v14: theme engine introduced, all users land on hellion-arctic; " +
|
|
||||||
"pick chat2-classic in Settings → Themes for the upstream look");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.Version < 15)
|
|
||||||
{
|
|
||||||
// v1.2.0 — keine Datenmigration nötig. Removal der deprecated
|
|
||||||
// Theme-Felder ist reine Schema-Bereinigung (System.Text.Json
|
|
||||||
// ignoriert unbekannte Felder im JSON, daher kein Crash bei
|
|
||||||
// Configs die noch HellionThemeEnabled/HellionThemeWindowOpacity
|
|
||||||
// serialisiert haben — die Werte verfallen einfach).
|
|
||||||
Config.Version = 15;
|
|
||||||
SaveConfig();
|
|
||||||
Log.Information(
|
|
||||||
"Migrated config v14 → v15: legacy theme fields removed " +
|
|
||||||
"(HellionThemeEnabled, HellionThemeWindowOpacity)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hellion Chat v15 → v16 — Settings Cleanup. Re-Sortierung der
|
|
||||||
// Tabs auf der UI-Seite (datenneutral). 4 tote Felder verfallen
|
|
||||||
// beim System.Text.Json-Deserialize (OverrideStyle, ChosenStyle,
|
|
||||||
// WindowAlpha, ShowThemeQuickPicker — sind alle nicht mehr im
|
|
||||||
// Configuration-Schema definiert). WindowAlpha wird zuvor auf
|
|
||||||
// WindowOpacity gemappt damit User die ihn gesetzt hatten ihre
|
|
||||||
// Transparenz-Einstellung behalten.
|
|
||||||
if (Config.Version < 16)
|
|
||||||
{
|
|
||||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
|
||||||
var liveConfigPath = pluginConfigsDir is not null
|
|
||||||
? Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json")
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Backup-Datei neben der live Config — Pattern aus v13 Branch.
|
|
||||||
if (pluginConfigsDir is not null && liveConfigPath is not null)
|
|
||||||
{
|
|
||||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v16-backup");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(liveConfigPath))
|
|
||||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "HellionChat: pre-v16 config backup failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-v16 Felder einmalig roh aus dem JSON lesen, da sie nicht
|
|
||||||
// mehr im Configuration-Schema sind (und damit aus Config nicht
|
|
||||||
// mehr abrufbar). WindowAlpha → WindowOpacity Mapping nur wenn
|
|
||||||
// User WindowOpacity noch nicht selbst angefasst hat (Default
|
|
||||||
// 0.85), sonst gewinnt der User-Wert.
|
|
||||||
float oldWindowAlpha = 100f;
|
|
||||||
bool oldOverrideStyle = false;
|
|
||||||
if (liveConfigPath is not null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(liveConfigPath))
|
|
||||||
{
|
|
||||||
var rawJson = File.ReadAllText(liveConfigPath);
|
|
||||||
using var doc = System.Text.Json.JsonDocument.Parse(rawJson);
|
|
||||||
if (doc.RootElement.TryGetProperty("WindowAlpha", out var alphaProp)
|
|
||||||
&& alphaProp.ValueKind == System.Text.Json.JsonValueKind.Number)
|
|
||||||
{
|
|
||||||
oldWindowAlpha = alphaProp.GetSingle();
|
|
||||||
}
|
|
||||||
if (doc.RootElement.TryGetProperty("OverrideStyle", out var ovProp)
|
|
||||||
&& ovProp.ValueKind is System.Text.Json.JsonValueKind.True)
|
|
||||||
{
|
|
||||||
oldOverrideStyle = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "HellionChat: pre-v16 legacy-field lookup failed, defaults assumed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldWindowAlpha != 100f
|
|
||||||
&& Math.Abs(Config.WindowOpacity - 0.85f) < 0.001f)
|
|
||||||
{
|
|
||||||
Config.WindowOpacity = Math.Clamp(oldWindowAlpha / 100f, 0.5f, 1.0f);
|
|
||||||
Log.Information(
|
|
||||||
$"Migrated WindowAlpha {oldWindowAlpha} to WindowOpacity {Config.WindowOpacity}");
|
|
||||||
}
|
|
||||||
else if (oldWindowAlpha != 100f)
|
|
||||||
{
|
|
||||||
Log.Information(
|
|
||||||
$"Skipped WindowAlpha→WindowOpacity migration: WindowOpacity already user-set " +
|
|
||||||
$"({Config.WindowOpacity}), legacy WindowAlpha value {oldWindowAlpha} dropped.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldOverrideStyle)
|
|
||||||
{
|
|
||||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
|
||||||
{
|
|
||||||
Title = "Hellion Chat 1.2.1",
|
|
||||||
Content = HellionStrings.Migration_v16_OverrideStyle_Toast,
|
|
||||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
|
||||||
InitialDuration = TimeSpan.FromSeconds(25),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// v1.2.1 Default-Bumps für UX-Verbesserungen. Pattern: nur
|
|
||||||
// migrieren wenn der User noch auf dem alten Default ist.
|
|
||||||
// Bei bool-Werten ist die Erkennung pragmatisch — wer den
|
|
||||||
// alten Default aktiv ausgeschaltet hatte, erlebt das als
|
|
||||||
// Regression und stellt es einmal in den Settings zurück.
|
|
||||||
// Der Trade-Off ist akzeptabel weil die alten Defaults in
|
|
||||||
// v1.2.0 erst neu eingeführt wurden und kaum jemand aktiv
|
|
||||||
// umgeschaltet hat.
|
|
||||||
if (!Config.UseCompactDensity)
|
|
||||||
{
|
|
||||||
Config.UseCompactDensity = true;
|
|
||||||
Log.Information("v16 default-bump: UseCompactDensity false → true");
|
|
||||||
}
|
|
||||||
if (!Config.HideInNewGamePlusMenu)
|
|
||||||
{
|
|
||||||
Config.HideInNewGamePlusMenu = true;
|
|
||||||
Log.Information("v16 default-bump: HideInNewGamePlusMenu false → true");
|
|
||||||
}
|
|
||||||
if (!Config.HideSameTimestamps)
|
|
||||||
{
|
|
||||||
Config.HideSameTimestamps = true;
|
|
||||||
Log.Information("v16 default-bump: HideSameTimestamps false → true");
|
|
||||||
}
|
|
||||||
if (Config.MaxLinesToRender == 5000)
|
|
||||||
{
|
|
||||||
Config.MaxLinesToRender = 2500;
|
|
||||||
Log.Information("v16 default-bump: MaxLinesToRender 5000 → 2500");
|
|
||||||
}
|
|
||||||
if (Config.ChatColours.Count == 0)
|
|
||||||
{
|
|
||||||
foreach (var (channel, colour) in Resources.ChatColourPresets.All["Hellion"].Colours)
|
|
||||||
Config.ChatColours[channel] = colour;
|
|
||||||
Log.Information("v16 default-bump: ChatColours empty → Hellion brand preset");
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.Version = 16;
|
|
||||||
SaveConfig();
|
|
||||||
Log.Information(
|
|
||||||
"Migrated config v15 → v16: settings cleanup, " +
|
|
||||||
"OverrideStyle/ChosenStyle/WindowAlpha/ShowThemeQuickPicker dropped from schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hellion v1.0.0 default tab layout. Five thematically separated
|
// Hellion v1.0.0 default tab layout. Five thematically separated
|
||||||
// tabs: General catches the immediate-surroundings public chat
|
// tabs: General catches the immediate-surroundings public chat
|
||||||
// (Say/Yell/Shout) only; System absorbs the rest of the technical
|
// (Say/Yell/Shout) only; System absorbs the rest of the technical
|
||||||
@@ -432,48 +178,54 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
Config.Tabs.Add(TabsUtil.HellionLinkshell);
|
Config.Tabs.Add(TabsUtil.HellionLinkshell);
|
||||||
}
|
}
|
||||||
|
|
||||||
LanguageChanged(Interface.UiLanguage);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
ImGuiUtil.Initialize(this);
|
|
||||||
|
|
||||||
FileDialogManager = new FileDialogManager();
|
// Sync allocation + handle registration. BuildFonts() registers
|
||||||
|
// IFontHandles with Dalamud's UiBuilder.FontAtlas — registration
|
||||||
Commands = new Commands();
|
// itself is non-blocking (handles stored, lambdas queued). Dalamud
|
||||||
Functions = new GameFunctions.GameFunctions(this);
|
// rebuilds the atlas on its own pipeline a few frames later; first
|
||||||
Ipc = new IpcManager();
|
// frames render with the default font until the rebuild lands and
|
||||||
TypingIpc = new TypingIpc(this);
|
// ImGui switches to Hellion-Exo2 / NotoSans (visible "font-pop").
|
||||||
ExtraChat = new ExtraChat();
|
// Mirrors ChatTwo Plugin.cs:152.
|
||||||
FontManager = new FontManager();
|
FontManager = new FontManager();
|
||||||
|
FontManager.BuildFonts();
|
||||||
|
|
||||||
// v1.1.0 — Theme-Engine init. Custom-Themes liegen in
|
// Theme init stays sync on the LoadAsync continuation — cheap,
|
||||||
// pluginConfigs/HellionChat/themes/, lazy geladen beim ersten Get.
|
// and Active is read every Draw frame, so the registry must be
|
||||||
|
// wired before the first hook fires.
|
||||||
var customThemesDir = Path.Combine(Interface.ConfigDirectory.FullName, "themes");
|
var customThemesDir = Path.Combine(Interface.ConfigDirectory.FullName, "themes");
|
||||||
Directory.CreateDirectory(customThemesDir);
|
Directory.CreateDirectory(customThemesDir);
|
||||||
SeedExampleThemeIfEmpty(customThemesDir);
|
SeedExampleThemeIfEmpty(customThemesDir);
|
||||||
ThemeRegistry = new Themes.ThemeRegistry(customThemesDir);
|
ThemeRegistry = new Themes.ThemeRegistry(customThemesDir);
|
||||||
ThemeRegistry.Switch(Config.Theme);
|
ThemeRegistry.Switch(Config.Theme);
|
||||||
|
|
||||||
// Plugin integrations register their IPC subscribers up-front so
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
// Ready/Disposing events from the target plugins are caught from
|
|
||||||
// the very first frame, even if the user's Honorific reloads
|
// Service allocations: order encodes dependencies. Commands is
|
||||||
// mid-session. See HellionChat/Integrations/HonorificService.cs.
|
// alloc-only here; Initialise() runs after windows exist so the
|
||||||
|
// slash-commands can toggle their visibility. HonorificService
|
||||||
|
// registers IPC subscribers up-front so Ready/Disposing events
|
||||||
|
// are caught from the very first frame.
|
||||||
|
FileDialogManager = new FileDialogManager();
|
||||||
|
Commands = new Commands();
|
||||||
|
Functions = new GameFunctions.GameFunctions(this);
|
||||||
|
Ipc = new IpcManager();
|
||||||
|
TypingIpc = new TypingIpc(this);
|
||||||
|
ExtraChat = new ExtraChat();
|
||||||
HonorificService = new Integrations.HonorificService(Interface, Log, Framework);
|
HonorificService = new Integrations.HonorificService(Interface, Log, Framework);
|
||||||
|
|
||||||
StatusBar = new Ui.StatusBar();
|
StatusBar = new Ui.StatusBar();
|
||||||
|
MessageManager = new MessageManager(this);
|
||||||
|
|
||||||
MessageManager = new MessageManager(this); // Does it require UI?
|
// Auto-Tell-Tabs subscribes to MessageManager.MessageProcessed for
|
||||||
|
// live tells and to ClientState.Logout for cleanup; needs the live
|
||||||
// Hellion Chat — Auto-Tell-Tabs service. Subscribes to the
|
// store handed in at construction.
|
||||||
// MessageManager's MessageProcessed event for live tells and
|
|
||||||
// to ClientState.Logout for the cleanup pass. Created after
|
|
||||||
// MessageManager so the constructor can hand off the live
|
|
||||||
// store and event source.
|
|
||||||
AutoTellTabsService = new AutoTellTabsService(this, MessageManager, MessageManager.Store);
|
AutoTellTabsService = new AutoTellTabsService(this, MessageManager, MessageManager.Store);
|
||||||
AutoTellTabsService.Initialize();
|
AutoTellTabsService.Initialize();
|
||||||
|
|
||||||
// Hellion Chat — daily retention sweep, off-thread so it never
|
// SelfTest steps poll Active per frame and need the registry wired.
|
||||||
// blocks plugin load. Skips itself when disabled or already ran
|
SelfTestRegistry.RegisterTestSteps([
|
||||||
// within the past 24 hours.
|
new SelfTests.ThemeSwitchSelfTestStep(this),
|
||||||
RunRetentionSweepIfDue();
|
]);
|
||||||
|
|
||||||
ChatLogWindow = new ChatLogWindow(this);
|
ChatLogWindow = new ChatLogWindow(this);
|
||||||
SettingsWindow = new SettingsWindow(this);
|
SettingsWindow = new SettingsWindow(this);
|
||||||
@@ -498,17 +250,38 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
if (!Config.FirstRunCompleted)
|
if (!Config.FirstRunCompleted)
|
||||||
FirstRunWizard.IsOpen = true;
|
FirstRunWizard.IsOpen = true;
|
||||||
|
|
||||||
FontManager.BuildFonts();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
Interface.UiBuilder.DisableCutsceneUiHide = true;
|
|
||||||
Interface.UiBuilder.DisableGposeUiHide = true;
|
|
||||||
|
|
||||||
// let all the other components register, then initialize commands
|
// let all the other components register, then initialize commands
|
||||||
Commands.Initialise();
|
Commands.Initialise();
|
||||||
|
|
||||||
|
// Daily retention sweep, fire-and-forget. Skips itself when
|
||||||
|
// disabled or when it already ran within the past 24 hours.
|
||||||
|
RunRetentionSweepIfDue();
|
||||||
|
|
||||||
|
if (Config.ShowEmotes)
|
||||||
|
_ = EmoteCache.LoadData(); // Fire-and-forget, exceptions caught inside
|
||||||
|
|
||||||
if (Interface.Reason is not PluginLoadReason.Boot)
|
if (Interface.Reason is not PluginLoadReason.Boot)
|
||||||
MessageManager.FilterAllTabsAsync();
|
MessageManager.FilterAllTabsAsync();
|
||||||
|
|
||||||
|
Interface.UiBuilder.DisableCutsceneUiHide = true;
|
||||||
|
Interface.UiBuilder.DisableGposeUiHide = true;
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
// Fire-and-forget on a worker thread. The first auto-translate use of
|
||||||
|
// a session may have a sub-second hitch if the cache hasn't filled yet,
|
||||||
|
// but that's preferable to making every user wait ~300 ms during
|
||||||
|
// plugin load for a cache they may never touch. ChatTwo (upstream)
|
||||||
|
// does this sync; we trade load-time for first-use latency.
|
||||||
|
_ = Task.Run(AutoTranslate.PreloadCache, cancellationToken);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// (B1) Hooks last: every service and window must be live before
|
||||||
|
// Dalamud fires our first Draw / FrameworkUpdate tick. Anything
|
||||||
|
// earlier risks rendering against null FontManager / ThemeRegistry.
|
||||||
Framework.Update += FrameworkUpdate;
|
Framework.Update += FrameworkUpdate;
|
||||||
Interface.UiBuilder.Draw += Draw;
|
Interface.UiBuilder.Draw += Draw;
|
||||||
Interface.LanguageChanged += LanguageChanged;
|
Interface.LanguageChanged += LanguageChanged;
|
||||||
@@ -517,102 +290,127 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
// most useful landing place; OpenConfigUi is already wired to
|
// most useful landing place; OpenConfigUi is already wired to
|
||||||
// the same toggle inside SettingsWindow.
|
// the same toggle inside SettingsWindow.
|
||||||
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
||||||
|
|
||||||
if (Config.ShowEmotes)
|
|
||||||
_ = EmoteCache.LoadData(); // Fire-and-forget intentional, exceptions are caught inside
|
|
||||||
|
|
||||||
#if !DEBUG
|
|
||||||
// Avoid 300ms hitch when sending first message by preloading the
|
|
||||||
// auto-translate cache. Don't do this in debug because it makes
|
|
||||||
// profiling difficult.
|
|
||||||
AutoTranslate.PreloadCache();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Plugin load threw an error, turning off plugin");
|
// Mirror the v1.4.0 load-failure recovery: hand off to DisposeAsync
|
||||||
Dispose();
|
// so partially-built services are torn down. Swallow the cleanup
|
||||||
|
// exception so the original load failure stays the visible cause.
|
||||||
// Re-throw the exception to fail the plugin load.
|
try { await DisposeAsync().ConfigureAwait(false); }
|
||||||
|
catch { /* keep original failure */ }
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suppressing this warning because Dispose() is called in Plugin() if the
|
// Suppressing this warning because DisposeAsync may run after a partial
|
||||||
// load fails, so some values may not be initialized.
|
// LoadAsync, so some properties may not be initialized.
|
||||||
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract")]
|
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract")]
|
||||||
public void Dispose()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
Interface.UiBuilder.OpenMainUi -= OpenMainUi;
|
// (B3) Idempotency guard — Dalamud may reload-race us; second
|
||||||
Interface.LanguageChanged -= LanguageChanged;
|
// call short-circuits so we don't double-dispose services.
|
||||||
Interface.UiBuilder.Draw -= Draw;
|
if (Interlocked.Exchange(ref _disposeStarted, 1) != 0)
|
||||||
Framework.Update -= FrameworkUpdate;
|
return;
|
||||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
|
||||||
|
|
||||||
// FrameworkUpdate would have fired the pending save in N frames,
|
Exception? failure = null;
|
||||||
// but we just unsubscribed it. -1 is the idle sentinel.
|
|
||||||
if (DeferredSaveFrames >= 0)
|
// Hooks unsubscribe FIRST so no Draw / FrameworkUpdate / LanguageChanged
|
||||||
|
// tick can fire while we're tearing services down. Mirrors the
|
||||||
|
// hooks-last subscribe order in LoadAsync.
|
||||||
|
failure = CaptureFailure(failure, () => Interface.UiBuilder.OpenMainUi -= OpenMainUi);
|
||||||
|
failure = CaptureFailure(failure, () => Interface.LanguageChanged -= LanguageChanged);
|
||||||
|
failure = CaptureFailure(failure, () => Interface.UiBuilder.Draw -= Draw);
|
||||||
|
failure = CaptureFailure(failure, () => Framework.Update -= FrameworkUpdate);
|
||||||
|
|
||||||
|
// v1.4.0 F5.3 — flush a pending DeferredSave before service teardown,
|
||||||
|
// since FrameworkUpdate just got unsubscribed and won't fire it.
|
||||||
|
failure = CaptureFailure(failure, () =>
|
||||||
{
|
{
|
||||||
SaveConfig();
|
if (DeferredSaveFrames >= 0)
|
||||||
DeferredSaveFrames = -1;
|
{
|
||||||
|
SaveConfig();
|
||||||
|
DeferredSaveFrames = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-Tell-Tabs unsubscribes from MessageProcessed before MessageManager
|
||||||
|
// goes away. Pure-memory cleanup, no framework-thread requirement.
|
||||||
|
failure = CaptureFailure(failure, () => AutoTellTabsService?.Dispose());
|
||||||
|
|
||||||
|
// v1.4.0 F6.2 — MessageManager has its own async dispose path
|
||||||
|
// (DB flush, pending-message thread shutdown). Run it before the
|
||||||
|
// framework-block so the worker threads are quiesced first.
|
||||||
|
if (MessageManager is not null)
|
||||||
|
{
|
||||||
|
failure = await CaptureFailureAsync(failure, () => MessageManager.DisposeAsync().AsTask())
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
HonorificService?.Dispose();
|
// (B4) Game-Function / IPC / UI-Window cleanup MUST run on the
|
||||||
|
// framework thread. WindowSystem mutations and IPC subscriber
|
||||||
WindowSystem?.RemoveAllWindows();
|
// disposes touch Dalamud state that's only safe from the framework.
|
||||||
ChatLogWindow?.Dispose();
|
// Worker-thread DisposeAsync would race the next Draw tick.
|
||||||
DbViewer?.Dispose();
|
// Per-line CaptureFailure so a single throw can't strand the lines
|
||||||
InputPreview?.Dispose();
|
// behind it; mirrors Lightless DisposeFrameworkBoundServicesAsync.
|
||||||
SettingsWindow?.Dispose();
|
|
||||||
DebuggerWindow?.Dispose();
|
|
||||||
SeStringDebugger?.Dispose();
|
|
||||||
|
|
||||||
TypingIpc?.Dispose();
|
|
||||||
ExtraChat?.Dispose();
|
|
||||||
Ipc?.Dispose();
|
|
||||||
// Dispose the Auto-Tell-Tabs service before MessageManager so it
|
|
||||||
// can cleanly unsubscribe from the MessageProcessed event before
|
|
||||||
// its source goes away.
|
|
||||||
AutoTellTabsService?.Dispose();
|
|
||||||
MessageManager?.DisposeAsync().AsTask().Wait();
|
|
||||||
Functions?.Dispose();
|
|
||||||
Commands?.Dispose();
|
|
||||||
|
|
||||||
EmoteCache.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads HellionThemeWindowOpacity from the pre-v13 backup the v12→v13
|
|
||||||
// block writes alongside the live config. Null when absent, unreadable,
|
|
||||||
// or schema-incompatible — all valid steady states (fresh install,
|
|
||||||
// backup pruned, pre-v12 config). Errors log at Warning so a corrupted
|
|
||||||
// backup stays visible in /xllog without breaking the migration.
|
|
||||||
private static float? TryReadPreV13ThemeOpacity()
|
|
||||||
{
|
|
||||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
|
||||||
if (pluginConfigsDir is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v13-backup");
|
|
||||||
if (!File.Exists(backupPath))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stream = File.OpenRead(backupPath);
|
await Framework.RunOnFrameworkThread(() =>
|
||||||
using var doc = System.Text.Json.JsonDocument.Parse(stream);
|
|
||||||
if (doc.RootElement.TryGetProperty("HellionThemeWindowOpacity", out var prop)
|
|
||||||
&& prop.ValueKind == System.Text.Json.JsonValueKind.Number
|
|
||||||
&& prop.TryGetSingle(out var value))
|
|
||||||
{
|
{
|
||||||
return value;
|
// Game-Functions first — other services may still query
|
||||||
}
|
// chat-interactable state during their Dispose.
|
||||||
return null;
|
failure = CaptureFailure(failure, () => GameFunctions.GameFunctions.SetChatInteractable(true));
|
||||||
|
|
||||||
|
// IPC subscribers — dispose before windows so any final
|
||||||
|
// event firing from the IPC source can't reach a half-torn
|
||||||
|
// ChatLogWindow.
|
||||||
|
failure = CaptureFailure(failure, () => HonorificService?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => TypingIpc?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => ExtraChat?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => Ipc?.Dispose());
|
||||||
|
|
||||||
|
// Windows — RemoveAllWindows first, then per-window Dispose.
|
||||||
|
// Order matches the pre-v1.4.3 Dispose body byte-for-byte.
|
||||||
|
// CommandHelpWindow and FirstRunWizard don't implement
|
||||||
|
// IDisposable; their resources are reclaimed via WindowSystem.
|
||||||
|
failure = CaptureFailure(failure, () => WindowSystem?.RemoveAllWindows());
|
||||||
|
failure = CaptureFailure(failure, () => ChatLogWindow?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => DbViewer?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => InputPreview?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => SettingsWindow?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => DebuggerWindow?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => SeStringDebugger?.Dispose());
|
||||||
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "HellionChat: pre-v13 backup lookup failed, defaulting WindowOpacity");
|
failure ??= ex;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pure-memory cleanups — no Framework / UI / IPC touch, so they
|
||||||
|
// run on whatever thread DisposeAsync resumes on.
|
||||||
|
failure = CaptureFailure(failure, () => Functions?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => Commands?.Dispose());
|
||||||
|
failure = CaptureFailure(failure, () => EmoteCache.Dispose());
|
||||||
|
|
||||||
|
if (failure is not null)
|
||||||
|
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lightless-pattern capture helpers: run cleanup, remember the FIRST
|
||||||
|
// exception, keep going. Without these one mid-teardown failure would
|
||||||
|
// skip every cleanup behind it and leave services half-torn.
|
||||||
|
private static Exception? CaptureFailure(Exception? failure, Action action)
|
||||||
|
{
|
||||||
|
try { action(); }
|
||||||
|
catch (Exception ex) { failure ??= ex; }
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async ValueTask<Exception?> CaptureFailureAsync(Exception? failure, Func<Task> action)
|
||||||
|
{
|
||||||
|
try { await action().ConfigureAwait(false); }
|
||||||
|
catch (Exception ex) { failure ??= ex; }
|
||||||
|
return failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MigrateFromChatTwoLayout()
|
private static void MigrateFromChatTwoLayout()
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Plugin.SelfTest;
|
||||||
|
using HellionChat.Themes;
|
||||||
|
|
||||||
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
|
// Validates the runtime theme-switch contract from the user side. The
|
||||||
|
// caller toggles the active theme via Settings -> Theme & Layout, the
|
||||||
|
// step polls ThemeRegistry.Active per frame and only passes once the
|
||||||
|
// slug has moved away from the initial value and back. The ABGR cache
|
||||||
|
// is sanity-checked on every frame: a freshly switched theme must carry
|
||||||
|
// a populated cache, otherwise Switch() forgot the recompute and the UI
|
||||||
|
// would still draw, just with all-transparent slots.
|
||||||
|
internal sealed class ThemeSwitchSelfTestStep : ISelfTestStep
|
||||||
|
{
|
||||||
|
private readonly Plugin plugin;
|
||||||
|
private string? initialSlug;
|
||||||
|
private bool switchedAway;
|
||||||
|
|
||||||
|
public ThemeSwitchSelfTestStep(Plugin plugin)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => "Hellion Chat - Theme switch";
|
||||||
|
|
||||||
|
public SelfTestStepResult RunStep()
|
||||||
|
{
|
||||||
|
var registry = this.plugin.ThemeRegistry;
|
||||||
|
if (registry is null)
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
|
||||||
|
var active = registry.Active;
|
||||||
|
if (active is null)
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
|
||||||
|
if (!HasPopulatedCache(active))
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
|
||||||
|
if (this.initialSlug is null)
|
||||||
|
{
|
||||||
|
this.initialSlug = active.Slug;
|
||||||
|
ImGui.Text($"Initial theme: \"{this.initialSlug}\". Open Settings -> Theme & Layout and pick a different theme.");
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.switchedAway)
|
||||||
|
{
|
||||||
|
if (!string.Equals(active.Slug, this.initialSlug, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
this.switchedAway = true;
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Text($"Switch the active theme away from \"{this.initialSlug}\".");
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(active.Slug, this.initialSlug, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ImGui.Text($"Switch back to \"{this.initialSlug}\" to finish the test.");
|
||||||
|
return SelfTestStepResult.Waiting;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SelfTestStepResult.Pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUp()
|
||||||
|
{
|
||||||
|
this.initialSlug = null;
|
||||||
|
this.switchedAway = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any non-zero slot proves the cache was actually recomputed for the
|
||||||
|
// current theme. We don't compare against a reference, because custom
|
||||||
|
// themes can legitimately share slot values with a built-in.
|
||||||
|
private static bool HasPopulatedCache(Theme theme)
|
||||||
|
{
|
||||||
|
var cache = theme.AbgrCache;
|
||||||
|
return (cache.Primary
|
||||||
|
| cache.WindowBg
|
||||||
|
| cache.TextPrimary
|
||||||
|
| cache.Border) != 0u;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using HellionChat._Helpers;
|
||||||
using HellionChat.Code;
|
using HellionChat.Code;
|
||||||
using HellionChat.Util;
|
using HellionChat.Util;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
@@ -89,60 +90,42 @@ public sealed class ChatInputBar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SubmitCompact(Tab tab)
|
// TEST-MIRROR: ../_Helpers/CompactInputSubmitter.cs
|
||||||
{
|
private void SubmitCompact(Tab tab) =>
|
||||||
if (string.IsNullOrWhiteSpace(_state.Buffer))
|
CompactInputSubmitter.TrySubmit(_state, tab, _host.SendChatBoxFromExternal);
|
||||||
return;
|
|
||||||
|
|
||||||
var text = _state.Buffer;
|
// History-navigation callback for the compact input. Cursor math is
|
||||||
_state.Buffer = string.Empty;
|
// delegated to CompactInputHistoryNavigator; only the ImGui buffer
|
||||||
_state.HistoryCursor = -1;
|
// splice stays here because it needs the live callback data.
|
||||||
_host.SendChatBoxFromExternal(tab, text);
|
// TEST-MIRROR: ../_Helpers/CompactInputHistoryNavigator.cs
|
||||||
}
|
|
||||||
|
|
||||||
// History-navigation callback for the compact input. Mirrors the main
|
|
||||||
// window's logic but operates on _state.HistoryCursor and the shared
|
|
||||||
// InputHistoryService. Index semantics match v0.5.x InputBacklog:
|
|
||||||
// 0 = oldest, Count-1 = newest.
|
|
||||||
private int CompactCallback(scoped ref ImGuiInputTextCallbackData data)
|
private int CompactCallback(scoped ref ImGuiInputTextCallbackData data)
|
||||||
{
|
{
|
||||||
if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory)
|
if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var prev = _state.HistoryCursor;
|
var direction = data.EventKey switch
|
||||||
switch (data.EventKey)
|
|
||||||
{
|
{
|
||||||
case ImGuiKey.UpArrow:
|
ImGuiKey.UpArrow => CompactInputHistoryNavigator.Direction.Up,
|
||||||
switch (_state.HistoryCursor)
|
ImGuiKey.DownArrow => CompactInputHistoryNavigator.Direction.Down,
|
||||||
{
|
_ => (CompactInputHistoryNavigator.Direction?)null,
|
||||||
case -1:
|
};
|
||||||
var offset = 0;
|
if (direction is null)
|
||||||
if (!string.IsNullOrWhiteSpace(_state.Buffer))
|
|
||||||
{
|
|
||||||
InputHistoryService.Push(_state.Buffer);
|
|
||||||
offset = 1;
|
|
||||||
}
|
|
||||||
_state.HistoryCursor = InputHistoryService.Count - 1 - offset;
|
|
||||||
break;
|
|
||||||
case > 0:
|
|
||||||
_state.HistoryCursor--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ImGuiKey.DownArrow:
|
|
||||||
if (_state.HistoryCursor != -1)
|
|
||||||
if (++_state.HistoryCursor >= InputHistoryService.Count)
|
|
||||||
_state.HistoryCursor = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev == _state.HistoryCursor)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var historyStr = InputHistoryService.GetByCursor(_state.HistoryCursor) ?? string.Empty;
|
var (cursor, replacement) = CompactInputHistoryNavigator.Navigate(
|
||||||
data.DeleteChars(0, data.BufTextLen);
|
direction.Value,
|
||||||
data.InsertChars(0, historyStr);
|
_state.HistoryCursor,
|
||||||
|
_state.Buffer,
|
||||||
|
() => InputHistoryService.Count,
|
||||||
|
InputHistoryService.Push,
|
||||||
|
InputHistoryService.GetByCursor);
|
||||||
|
|
||||||
|
_state.HistoryCursor = cursor;
|
||||||
|
if (replacement is null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
data.DeleteChars(0, data.BufTextLen);
|
||||||
|
data.InsertChars(0, replacement);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ internal sealed class Information : ISettingsTab
|
|||||||
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
|
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
|
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
|
||||||
Dalamud.Utility.Util.OpenLink("https://github.com/JonKazama-Hellion/HellionChat/issues");
|
Dalamud.Utility.Util.OpenLink("https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HellionChat._Helpers;
|
||||||
|
|
||||||
|
// Pure-helper mirror of the compact pop-out history-navigation cursor
|
||||||
|
// math. The original CompactCallback was tangled with ImGuiInputTextCallbackData
|
||||||
|
// (DeleteChars/InsertChars), which can't be exercised in xUnit. The
|
||||||
|
// ImGui buffer mutation stays at the call site; only the deterministic
|
||||||
|
// cursor-and-replacement decision lives here.
|
||||||
|
//
|
||||||
|
// Index semantics match InputHistoryService:
|
||||||
|
// index 0 = oldest entry
|
||||||
|
// index Count - 1 = newest entry
|
||||||
|
// cursor == -1 = "not browsing history"
|
||||||
|
//
|
||||||
|
// TEST-MIRROR: ../../../Hellion Build test/Ui/CompactInputHistoryNavigatorTests.cs
|
||||||
|
public static class CompactInputHistoryNavigator
|
||||||
|
{
|
||||||
|
public enum Direction { Up, Down }
|
||||||
|
|
||||||
|
// replacement == null means: caller must NOT touch the buffer. This
|
||||||
|
// distinguishes "cursor unchanged, leave the user's typing alone"
|
||||||
|
// from "cursor moved to an empty slot, clear the buffer".
|
||||||
|
public static (int cursor, string? replacement) Navigate(
|
||||||
|
Direction direction,
|
||||||
|
int currentCursor,
|
||||||
|
string currentBuffer,
|
||||||
|
Func<int> getCount,
|
||||||
|
Action<string> push,
|
||||||
|
Func<int, string?> getByCursor)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(getCount);
|
||||||
|
ArgumentNullException.ThrowIfNull(push);
|
||||||
|
ArgumentNullException.ThrowIfNull(getByCursor);
|
||||||
|
|
||||||
|
var prev = currentCursor;
|
||||||
|
var next = currentCursor;
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case Direction.Up:
|
||||||
|
if (currentCursor == -1)
|
||||||
|
{
|
||||||
|
// First Up press from a fresh buffer: stash whatever
|
||||||
|
// the user typed so they can recover it after browsing.
|
||||||
|
var offset = 0;
|
||||||
|
if (!string.IsNullOrWhiteSpace(currentBuffer))
|
||||||
|
{
|
||||||
|
push(currentBuffer);
|
||||||
|
offset = 1;
|
||||||
|
}
|
||||||
|
next = getCount() - 1 - offset;
|
||||||
|
}
|
||||||
|
else if (currentCursor > 0)
|
||||||
|
{
|
||||||
|
next--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Direction.Down:
|
||||||
|
if (currentCursor != -1)
|
||||||
|
{
|
||||||
|
next++;
|
||||||
|
if (next >= getCount())
|
||||||
|
next = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev == next)
|
||||||
|
return (next, null);
|
||||||
|
|
||||||
|
var replacement = getByCursor(next) ?? string.Empty;
|
||||||
|
return (next, replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using HellionChat.Ui;
|
||||||
|
|
||||||
|
namespace HellionChat._Helpers;
|
||||||
|
|
||||||
|
// Pure-helper mirror of the compact pop-out submit flow. ChatInputBar's
|
||||||
|
// SubmitCompact used to inline this against a sealed ChatLogWindow, which
|
||||||
|
// blocks Moq-based isolation. Lifting the deterministic part into a POCO
|
||||||
|
// keeps the production call site a one-liner while letting xUnit assert
|
||||||
|
// the buffer/cursor reset and the sender contract directly.
|
||||||
|
// TEST-MIRROR: ../../../Hellion Build test/Ui/CompactInputSubmitterTests.cs
|
||||||
|
public static class CompactInputSubmitter
|
||||||
|
{
|
||||||
|
public static bool TrySubmit(InputState state, Tab tab, Action<Tab, string> sender)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(state);
|
||||||
|
ArgumentNullException.ThrowIfNull(tab);
|
||||||
|
ArgumentNullException.ThrowIfNull(sender);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(state.Buffer))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var text = state.Buffer;
|
||||||
|
state.Buffer = string.Empty;
|
||||||
|
state.HistoryCursor = -1;
|
||||||
|
sender(tab, text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 45 KiB |
@@ -35,9 +35,19 @@ edits minimal, isolated to clearly-marked Hellion files, and reversible.
|
|||||||
|
|
||||||
Concrete example: when API 15 hit, I cherry-picked your fix for the
|
Concrete example: when API 15 hit, I cherry-picked your fix for the
|
||||||
BetterTTV emote regression with `git cherry-pick -x` so authorship and
|
BetterTTV emote regression with `git cherry-pick -x` so authorship and
|
||||||
co-author trail stay intact. That is the standard I want to keep using as
|
co-author trail stay intact. That was the standard I held to as long
|
||||||
long as both projects are alive. You should never have to look at this
|
as cherry-picking was viable, and you should never have to look at
|
||||||
fork and wonder if I quietly ate your work.
|
this fork and wonder if I quietly ate your work.
|
||||||
|
|
||||||
|
With ChatTwo entering its rework cycle, the active cherry-pick
|
||||||
|
pipeline is closed since v1.4.x — see [docs/UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md)
|
||||||
|
for the full reasoning. The attribution standard stays exactly the
|
||||||
|
same: every existing `(cherry picked from commit ...)` line remains
|
||||||
|
in the git history, the EUPL-1.2 anchor lines in source files are
|
||||||
|
untouched, and this NOTICE.md remains canonical. If anything from
|
||||||
|
this point forward originates from Chat 2 it will be a hand-port at
|
||||||
|
most, called out as such in the commit message and source comments,
|
||||||
|
not a `git cherry-pick`.
|
||||||
|
|
||||||
If anything in this fork ever steps on something you would not be okay
|
If anything in this fork ever steps on something you would not be okay
|
||||||
with, please reach out and I will fix it. Genuinely. The list of contacts
|
with, please reach out and I will fix it. Genuinely. The list of contacts
|
||||||
@@ -48,7 +58,7 @@ is below.
|
|||||||
If something in HellionChat causes problems, especially if it relates back
|
If something in HellionChat causes problems, especially if it relates back
|
||||||
to Chat 2 or to anything Infi or Anna would want flagged:
|
to Chat 2 or to anything Infi or Anna would want flagged:
|
||||||
|
|
||||||
- **GitHub Issues:** [JonKazama-Hellion/HellionChat/issues](https://github.com/JonKazama-Hellion/HellionChat/issues)
|
- **Gitea Issues:** [JonKazama-Hellion/HellionChat/issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues)
|
||||||
- **Discord:** `@j.j_kazama`
|
- **Discord:** `@j.j_kazama`
|
||||||
- **Email (business):** kontakt@hellion-media.de
|
- **Email (business):** kontakt@hellion-media.de
|
||||||
|
|
||||||
@@ -62,8 +72,10 @@ full-history-by-default position fits a much larger one, including the
|
|||||||
roleplaying community where chat archive is part of the play experience.
|
roleplaying community where chat archive is part of the play experience.
|
||||||
Trying to upstream HellionChat's defaults would have meant arguing that
|
Trying to upstream HellionChat's defaults would have meant arguing that
|
||||||
Chat 2's defaults are wrong, and they are not. They are right for the
|
Chat 2's defaults are wrong, and they are not. They are right for the
|
||||||
user base ChatTwo serves. So I keep the fork separate, attribute clearly,
|
user base ChatTwo serves. So I keep the fork separate and attribute
|
||||||
and pull selected upstream patches when they apply.
|
clearly. Active cherry-picking from upstream stopped in the v1.4.x
|
||||||
|
cycle once Chat 2's rework made selective patches no longer portable;
|
||||||
|
the existing cherry-pick trail stays in the git history.
|
||||||
|
|
||||||
## Why HellionChat left the GitHub fork network
|
## Why HellionChat left the GitHub fork network
|
||||||
|
|
||||||
@@ -72,8 +84,9 @@ that a fork is either a development branch or a dead mirror. HellionChat
|
|||||||
is neither. It is an independently-maintained EUPL-1.2 fork with its own
|
is neither. It is an independently-maintained EUPL-1.2 fork with its own
|
||||||
release cadence, its own custom repo, its own user base. Detaching the
|
release cadence, its own custom repo, its own user base. Detaching the
|
||||||
fork-network relation just makes the situation honest. The git history,
|
fork-network relation just makes the situation honest. The git history,
|
||||||
the cherry-pick trail, and the attribution stay exactly the same. The
|
the existing cherry-pick trail, and the attribution stay exactly the
|
||||||
only thing that changes is the GitHub UI no longer says "forked from".
|
same. The only thing that changes is the GitHub UI no longer says
|
||||||
|
"forked from".
|
||||||
|
|
||||||
## Trademarks and naming
|
## Trademarks and naming
|
||||||
|
|
||||||
|
|||||||
@@ -121,10 +121,10 @@ Adjust the channel whitelist or set retention to a low value. Both take effect i
|
|||||||
| Party | Why they appear | What reaches them | Their privacy policy |
|
| Party | Why they appear | What reaches them | Their privacy policy |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| BetterTTV (NightDev LLC) | Optional emote rendering | HTTPS request for an emote ID; your IP | <https://betterttv.com/privacy> |
|
| BetterTTV (NightDev LLC) | Optional emote rendering | HTTPS request for an emote ID; your IP | <https://betterttv.com/privacy> |
|
||||||
| GitHub (Microsoft) | Plugin distribution via custom repo, issue tracker | Whatever GitHub sees from any HTTPS request to a public repo | <https://docs.github.com/site-policy/privacy-policies/github-general-privacy-statement> |
|
| Hellion Forge (Gitea, self-hosted by Hellion Online Media) | Plugin distribution via custom repo, issue tracker | Whatever the Gitea instance sees from any HTTPS request to a public repo | <https://hellion-media.de/datenschutz> |
|
||||||
| Dalamud / XIVLauncher (goatcorp) | Plugin loader, font subsystem, repo polling | Whatever Dalamud reports for itself; out of HellionChat's scope | <https://github.com/goatcorp/Dalamud> |
|
| Dalamud / XIVLauncher (goatcorp) | Plugin loader, font subsystem, repo polling | Whatever Dalamud reports for itself; out of HellionChat's scope | <https://github.com/goatcorp/Dalamud> |
|
||||||
|
|
||||||
GitHub and the Dalamud/XIVLauncher loader are unavoidable for anyone playing FFXIV through Dalamud at all. BetterTTV is the only third party HellionChat introduces on top of that baseline, and it is opt-out via settings.
|
The Hellion Forge Gitea instance and the Dalamud/XIVLauncher loader are unavoidable for anyone using HellionChat through Dalamud at all. BetterTTV is the only third party HellionChat introduces on top of that baseline, and it is opt-out via settings.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# Hellion Chat
|
# Hellion Chat
|
||||||
|
|
||||||
[](https://github.com/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
|
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
|
||||||
[](https://github.com/JonKazama-Hellion/HellionChat/security/code-scanning)
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://github.com/JonKazama-Hellion/HellionChat/releases/latest)
|
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
|
||||||
[](https://github.com/goatcorp/Dalamud)
|
[](https://github.com/goatcorp/Dalamud)
|
||||||
[](https://dotnet.microsoft.com/)
|
[](https://dotnet.microsoft.com/)
|
||||||
[](https://www.finalfantasyxiv.com/)
|
[](https://www.finalfantasyxiv.com/)
|
||||||
@@ -12,13 +11,13 @@
|
|||||||
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
|
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**Version 1.4.2** — Privacy-First-Chat-Plugin für FINAL FANTASY XIV / Dalamud, basierend auf [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
|
**Version 1.4.3** — Privacy-First-Chat-Plugin für FINAL FANTASY XIV / Dalamud, basierend auf [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
|
||||||
|
|
||||||
Hellion Chat ist ein Privacy-First-Plugin auf dem Chat-2-Fundament. Der größte Teil der Engine kommt aus Chat 2 (Message-Store, Channel-Logik, Hook-System), die meisten Tastenkürzel funktionieren weiterhin wie gewohnt. Was sich ändert: schärfere Privacy-Defaults von Haus aus, eigene Slash-Commands unter `/hellionchat`, kein Webinterface mehr, und mit v1.1.0 eine Theme-Engine als Schritt in Richtung eigenes UI-Look-and-Feel.
|
Hellion Chat ist ein Privacy-First-Plugin auf dem Chat-2-Fundament. Der größte Teil der Engine kommt aus Chat 2 (Message-Store, Channel-Logik, Hook-System), die meisten Tastenkürzel funktionieren weiterhin wie gewohnt. Was sich ändert: schärfere Privacy-Defaults von Haus aus, eigene Slash-Commands unter `/hellionchat`, kein Webinterface mehr, und mit v1.1.0 eine Theme-Engine als Schritt in Richtung eigenes UI-Look-and-Feel.
|
||||||
|
|
||||||
Der Daten-Handling-Fokus liegt auf den DSGVO/EU-, US- und JP-Regelungen, soweit für ein Chat-Plugin praktisch umsetzbar: Speicherzeit pro Kanal, granulare Filter, Selbstauskunft per Export. Eine ausführliche Auflistung steht in [`PRIVACY.md`](PRIVACY.md).
|
Der Daten-Handling-Fokus liegt auf den DSGVO/EU-, US- und JP-Regelungen, soweit für ein Chat-Plugin praktisch umsetzbar: Speicherzeit pro Kanal, granulare Filter, Selbstauskunft per Export. Eine ausführliche Auflistung steht in [`PRIVACY.md`](PRIVACY.md).
|
||||||
|
|
||||||
Eigenständiges Repository, EUPL-1.2-lizenziert. Mit v1.0.0 ist der Standalone-Cut abgeschlossen: eigener Namespace `HellionChat.*`, eigene IPC-Kanäle, eigene Source-Tree-Struktur. Distribution über Custom-Repo. Selektive Cherry-Picks von Upstream-Chat-2 nach Bedarf, dokumentiert in [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md).
|
Eigenständiges Repository, EUPL-1.2-lizenziert. Mit v1.0.0 ist der Standalone-Cut abgeschlossen: eigener Namespace `HellionChat.*`, eigene IPC-Kanäle, eigene Source-Tree-Struktur. Distribution über Custom-Repo. Aktiver Upstream-Sync ist mit dem v1.4.x-Cycle beendet: Chat 2 befindet sich in einem grundlegenden Rework und Cherry-Picks sind nicht mehr portierbar. Hellion Chat geht ab da als unabhängige Codebase weiter, Hintergrund und Attribution in [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md).
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
@@ -170,7 +169,7 @@ Hellion Chat wird über ein Dalamud-**Custom-Repository** verteilt.
|
|||||||
1. Dalamud-Settings (`/xlsettings`) → **Experimental** öffnen.
|
1. Dalamud-Settings (`/xlsettings`) → **Experimental** öffnen.
|
||||||
2. Neuen Eintrag unter **Custom Plugin Repositories** anlegen:
|
2. Neuen Eintrag unter **Custom Plugin Repositories** anlegen:
|
||||||
```
|
```
|
||||||
https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/repo.json
|
https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json
|
||||||
```
|
```
|
||||||
3. **Save**, dann in `/xlplugins` → **All Plugins** → Refresh.
|
3. **Save**, dann in `/xlplugins` → **All Plugins** → Refresh.
|
||||||
4. Hellion Chat taucht in der Liste auf, dann installieren wie jedes andere Plugin.
|
4. Hellion Chat taucht in der Liste auf, dann installieren wie jedes andere Plugin.
|
||||||
@@ -225,7 +224,7 @@ Eine optionale Submission ans Dalamud-Main-Plugin-Repo (zusätzlich zum eigenen
|
|||||||
|
|
||||||
## Projektstatus
|
## Projektstatus
|
||||||
|
|
||||||
**Version 1.4.2** — ChatLog Frame-Hot-Path: Per-Frame-Allokationen aus dem ChatLogWindow-Render-Pfad und der Settings-StatusBar eliminiert. Card-Mode-Border-Loop in DrawMessages hebt fünf Invarianten in einen Pre-Loop-Hoist innerhalb des BeginChild-Scopes, AutoTellTabTint bekommt einen Per-Tab-Cache via TabTintCache (separate Validation-Keys pro Cache, kein Cross-Invalidation), StatusBar zieht den Cache-Gate-Check vor die LINQ-Pfade und ersetzt Sum+Count durch eine Single-Pass-Foreach. Realistische Frame-Time-Recovery: 2-5 % in typischen Szenen, mehr bei Pop-Out-Heavy-Setups. Dritter Sub-Patch der v1.4.x Polish-Sweep-Serie (Stand: 2026-05-07).
|
**Version 1.4.3** — Plugin-Load Async-Init plus Repo-Cutover: Plugin auf Dalamud's IAsyncDalamudPlugin-API migriert. Der Konstruktor übernimmt nur noch Bootstrap-Essentials (Config-Load, Language-Init, Conflict-Detection); Migrationen, Service-Allokationen, Window-Konstruktion und Hook-Subscription wandern in LoadAsync, sodass Dalamud die UI während der schweren Arbeit responsive halten kann. Schema-Gate ersetzt die v9 → v16 Migrations-Kette; Configs auf Schema v16+ laden direkt, ältere Configs triggern eine "install v1.4.2 first"-Fehlermeldung. Custom-Repo-URL auf `gitea.hellion-forge.cloud` migriert; das GitHub-Repo bleibt als eingefrorener v1.4.2-Snapshot stehen. Plugin-Load-Zeit liegt bei ~3.7 s Median (5 Reloads), vergleichbar mit v1.4.2: Async-Migration ist Foundation für v1.4.4 Lazy-Init-Optimierungen, kein direkter User-spürbarer Win. Vierter Sub-Patch der v1.4.x Polish-Sweep-Serie (Stand: 2026-05-08).
|
||||||
|
|
||||||
Hellion Chat ist ein eigenständiges Plugin, kein Fork mehr im Repository-Sinne. Vollständig abgeschlossen:
|
Hellion Chat ist ein eigenständiges Plugin, kein Fork mehr im Repository-Sinne. Vollständig abgeschlossen:
|
||||||
|
|
||||||
@@ -244,7 +243,7 @@ Hellion Chat ist ein eigenständiges Plugin, kein Fork mehr im Repository-Sinne.
|
|||||||
- Theme-Engine mit zehn eingebauten Themes plus JSON-Authoring-Format (Engine v1.1.0, Katalog erweitert in v1.2.3, inkl. CVD-safe Hellion Spectrum; Synthwave Sunset in v1.4.1)
|
- Theme-Engine mit zehn eingebauten Themes plus JSON-Authoring-Format (Engine v1.1.0, Katalog erweitert in v1.2.3, inkl. CVD-safe Hellion Spectrum; Synthwave Sunset in v1.4.1)
|
||||||
- ABGR-Cache auf den Theme-Records: HellionStyle.PushGlobal liest pre-computed ABGR statt RGBA→ABGR pro Slot pro Frame (v1.4.1, ~13 % Render-Time-Recovery)
|
- ABGR-Cache auf den Theme-Records: HellionStyle.PushGlobal liest pre-computed ABGR statt RGBA→ABGR pro Slot pro Frame (v1.4.1, ~13 % Render-Time-Recovery)
|
||||||
|
|
||||||
In Arbeit: schrittweise Modernisierung des UI-Look-and-Feel über die Theme-Engine hinaus. Was als Nächstes geplant ist und welche Themen langfristig auf der Liste stehen, steht in [`docs/ROADMAP.md`](docs/ROADMAP.md). Konkrete eingeplante Items werden zusätzlich im [GitHub-Issue-Tracker](https://github.com/JonKazama-Hellion/HellionChat/issues) mit dem `roadmap`-Label geführt.
|
In Arbeit: schrittweise Modernisierung des UI-Look-and-Feel über die Theme-Engine hinaus. Was als Nächstes geplant ist und welche Themen langfristig auf der Liste stehen, steht in [`docs/ROADMAP.md`](docs/ROADMAP.md). Konkrete eingeplante Items werden zusätzlich im [Gitea-Issue-Tracker](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues) mit dem `roadmap`-Label geführt.
|
||||||
|
|
||||||
### Zur Release-Kadenz
|
### Zur Release-Kadenz
|
||||||
|
|
||||||
@@ -255,7 +254,7 @@ Wer den Repo zum ersten Mal sieht, bemerkt schnell viele Releases und sehr viele
|
|||||||
## Community und Support
|
## Community und Support
|
||||||
|
|
||||||
- **Hellion Forge Discord** (Modding- und Plugin-Community von Hellion Online Media): https://discord.gg/X9V7Kcv5gR
|
- **Hellion Forge Discord** (Modding- und Plugin-Community von Hellion Online Media): https://discord.gg/X9V7Kcv5gR
|
||||||
- Bug-Reports und Feature-Requests: [GitHub Issues](https://github.com/JonKazama-Hellion/HellionChat/issues)
|
- Bug-Reports und Feature-Requests: [Gitea Issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues)
|
||||||
- Discord DM: `@j.j_kazama`
|
- Discord DM: `@j.j_kazama`
|
||||||
- Weitere Kontaktwege (Security, Privacy, Quick-Questions): siehe [SUPPORT.md](SUPPORT.md)
|
- Weitere Kontaktwege (Security, Privacy, Quick-Questions): siehe [SUPPORT.md](SUPPORT.md)
|
||||||
|
|
||||||
@@ -311,7 +310,7 @@ Im Repo-Root liegen die Standard-Repository-Dokumente, vertiefende Dokumentation
|
|||||||
| [`docs/LEARNING-JOURNEY.md`](docs/LEARNING-JOURNEY.md) | Entwicklungsgeschichte, vom Web-Stack zu C# / Dalamud, was ich aus dem Fork gelernt habe. |
|
| [`docs/LEARNING-JOURNEY.md`](docs/LEARNING-JOURNEY.md) | Entwicklungsgeschichte, vom Web-Stack zu C# / Dalamud, was ich aus dem Fork gelernt habe. |
|
||||||
| [`docs/IPC.md`](docs/IPC.md) | IPC-Kanal-Reference, Tuple-Payload-Felder, Migrations-Diff für Drittplugins. |
|
| [`docs/IPC.md`](docs/IPC.md) | IPC-Kanal-Reference, Tuple-Payload-Felder, Migrations-Diff für Drittplugins. |
|
||||||
| [`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md) | Theme-Engine-Authoring-Guide (EN): JSON-Schema, Color- und Layout-Slots, Channel-Identity-Regeln, Validierung. |
|
| [`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md) | Theme-Engine-Authoring-Guide (EN): JSON-Schema, Color- und Layout-Slots, Channel-Identity-Regeln, Validierung. |
|
||||||
| [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md) | Cherry-Pick-Policy gegenüber Chat 2. |
|
| [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md) | Upstream-Sync-Stand: Cherry-Pick-Pipeline seit v1.4.x geschlossen, Attribution intakt. |
|
||||||
| [`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md) | NuGet-Dependencies mit Lizenzen, Bundled Assets, Network-Status pro Komponente. |
|
| [`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md) | NuGet-Dependencies mit Lizenzen, Bundled Assets, Network-Status pro Komponente. |
|
||||||
| [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) | Offenlegung der KI-Unterstützung im Entwicklungsprozess. |
|
| [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) | Offenlegung der KI-Unterstützung im Entwicklungsprozess. |
|
||||||
|
|
||||||
|
|||||||
@@ -3,24 +3,20 @@
|
|||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
If you find a security issue in HellionChat, please do not open a
|
If you find a security issue in HellionChat, please do not open a
|
||||||
public GitHub issue. Use one of the private channels below so I can
|
public Gitea issue. Use one of the private channels below so I can
|
||||||
investigate and ship a fix before the details go public.
|
investigate and ship a fix before the details go public.
|
||||||
|
|
||||||
**Preferred:**
|
**Preferred:**
|
||||||
[Privately report a vulnerability](https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new)
|
|
||||||
via GitHub Security Advisories. This routes the report directly to me
|
|
||||||
and keeps the conversation off the public timeline.
|
|
||||||
|
|
||||||
**Alternative:**
|
|
||||||
|
|
||||||
| Channel | Address |
|
| Channel | Address |
|
||||||
| ---------- | -------------------------- |
|
| ---------- | -------------------------- |
|
||||||
| Email | `kontakt@hellion-media.de` |
|
| Email | `kontakt@hellion-media.de` |
|
||||||
| Discord DM | `@j.j_kazama` |
|
| Discord DM | `@j.j_kazama` |
|
||||||
|
|
||||||
I respond on weekdays during European business hours. For urgent
|
For urgent disclosures (active exploitation, user-data exposure) email
|
||||||
disclosures (active exploitation, user-data exposure) email is the
|
is the fastest path.
|
||||||
fastest path.
|
|
||||||
|
I respond on weekdays during European business hours.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ HellionChat is a small hobby project maintained by one person. There are a few d
|
|||||||
|
|
||||||
## Bugs and feature requests
|
## Bugs and feature requests
|
||||||
|
|
||||||
GitHub issues, using the templates:
|
Gitea issues, using the templates:
|
||||||
|
|
||||||
- [Bug report](https://github.com/JonKazama-Hellion/HellionChat/issues/new?template=bug_report.yml)
|
- [Bug report](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues/new?template=bug_report.yml)
|
||||||
- [Feature request](https://github.com/JonKazama-Hellion/HellionChat/issues/new?template=feature_request.yml)
|
- [Feature request](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues/new?template=feature_request.yml)
|
||||||
|
|
||||||
Please search [existing issues](https://github.com/JonKazama-Hellion/HellionChat/issues?q=is%3Aissue) first. Duplicates get closed and pointed at the original.
|
Please search [existing issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues?type=issue) first. Duplicates get closed and pointed at the original.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
Do **not** open a public issue for security-relevant findings. Use the private advisory route described in [SECURITY.md](SECURITY.md):
|
Do **not** open a public issue for security-relevant findings. Use the private advisory route described in [SECURITY.md](SECURITY.md):
|
||||||
|
|
||||||
- [Private vulnerability advisory](https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new)
|
- Email `kontakt@hellion-media.de` (preferred for security reports)
|
||||||
- Email `kontakt@hellion-media.de`
|
- Discord DM `@j.j_kazama` for time-sensitive findings
|
||||||
|
|
||||||
## Privacy questions
|
## Privacy questions
|
||||||
|
|
||||||
|
|||||||
@@ -82,4 +82,4 @@ Both are good projects. Use what fits you best.
|
|||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
Questions about this disclosure:
|
Questions about this disclosure:
|
||||||
<https://github.com/JonKazama-Hellion/HellionChat/issues>
|
<https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues>
|
||||||
|
|||||||
@@ -5,13 +5,52 @@ sich an [Keep a Changelog](https://keepachangelog.com/de/1.0.0/), die
|
|||||||
Version-Nummern folgen [Semantischer Versionierung](https://semver.org/lang/de/).
|
Version-Nummern folgen [Semantischer Versionierung](https://semver.org/lang/de/).
|
||||||
|
|
||||||
Detaillierte Release-Notes pro Version stehen direkt am
|
Detaillierte Release-Notes pro Version stehen direkt am
|
||||||
[GitHub-Release](https://github.com/JonKazama-Hellion/HellionChat/releases)
|
[Gitea-Release](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases)
|
||||||
und im Plugin-Changelog-Block (`HellionChat/HellionChat.yaml` →
|
und im Plugin-Changelog-Block (`HellionChat/HellionChat.yaml` →
|
||||||
`changelog:`). Diese Datei fasst die Releases als Überblick zusammen
|
`changelog:`). Diese Datei fasst die Releases als Überblick zusammen
|
||||||
und verlinkt für Details auf die Release-Pages.
|
und verlinkt für Details auf die Release-Pages.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover (2026-05-08)
|
||||||
|
|
||||||
|
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin`
|
||||||
|
API. The constructor now does only the bootstrap-essentials
|
||||||
|
(config load, language init, conflict detection); migrations,
|
||||||
|
service allocations, window construction and hook subscription
|
||||||
|
move to `LoadAsync`. Dalamud can keep its UI responsive while
|
||||||
|
the heavy work runs.
|
||||||
|
|
||||||
|
- `IAsyncDalamudPlugin` two-phase load with per-line
|
||||||
|
`CaptureFailure` in `DisposeAsync` (mirrors LightlessSync's
|
||||||
|
pattern); idempotency guard protects against reload races
|
||||||
|
- Schema-gate replaces the v9 → v16 migration chain. Configs
|
||||||
|
on schema v16+ load directly; older configs trigger an
|
||||||
|
"install v1.4.2 first" error so the historic migration
|
||||||
|
path stays intact
|
||||||
|
- `AutoTranslate.PreloadCache` moved off the load path. First
|
||||||
|
use may have a sub-second hitch instead of every-load; the
|
||||||
|
upstream chose differently, we accept first-use latency
|
||||||
|
- `FontManager.BuildFonts` is called sync at the start of
|
||||||
|
`LoadAsync`; Dalamud rebuilds the font atlas on its own
|
||||||
|
pipeline so the custom Hellion-Exo2 font appears with a
|
||||||
|
brief font-pop after load (matches ChatTwo's behaviour)
|
||||||
|
- Custom-repo URL moved to
|
||||||
|
`gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat`.
|
||||||
|
GitHub repo stays as a frozen v1.4.2 snapshot; new
|
||||||
|
releases ship from Gitea. Existing testers need to
|
||||||
|
update the custom-repo URL once
|
||||||
|
- Plugin-load time in this release sits at ~3.7 s median
|
||||||
|
(5 reloads), comparable to v1.4.2. Async migration is
|
||||||
|
foundational for v1.4.4 Lazy-Init optimisations rather
|
||||||
|
than an immediate user-perceived win
|
||||||
|
|
||||||
|
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
|
||||||
|
|
||||||
|
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path
|
## Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path
|
||||||
|
|
||||||
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame
|
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame
|
||||||
@@ -279,7 +318,7 @@ Vier kleine Polish-Items aus dem Backlog gebündelt:
|
|||||||
auf Verbose-Level. Aus by default, Aktivierung via
|
auf Verbose-Level. Aus by default, Aktivierung via
|
||||||
`/xllog set HellionChat verbose` für Bug-Report-Diagnose.
|
`/xllog set HellionChat verbose` für Bug-Report-Diagnose.
|
||||||
|
|
||||||
[Release-Notes 1.0.3](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v1.0.3)
|
[Release-Notes 1.0.3](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.3)
|
||||||
|
|
||||||
## [1.0.1] — 2026-05-04 — Window Position Recovery
|
## [1.0.1] — 2026-05-04 — Window Position Recovery
|
||||||
|
|
||||||
@@ -295,7 +334,7 @@ Bundled housekeeping since v1.0.0: documentation restructured into
|
|||||||
parser library bumped from 3.3.0 to 3.5.1, GitHub Actions bumps for
|
parser library bumped from 3.3.0 to 3.5.1, GitHub Actions bumps for
|
||||||
`actions/setup-dotnet` (4 → 5) and `github/codeql-action` (3 → 4).
|
`actions/setup-dotnet` (4 → 5) and `github/codeql-action` (3 → 4).
|
||||||
|
|
||||||
[Release-Notes 1.0.1](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v1.0.1)
|
[Release-Notes 1.0.1](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.1)
|
||||||
|
|
||||||
## [1.0.0] — 2026-05-03 — Standalone Major Release
|
## [1.0.0] — 2026-05-03 — Standalone Major Release
|
||||||
|
|
||||||
@@ -308,7 +347,7 @@ User auf Config-Version 12 oder älter neu strukturiert (5 thematische
|
|||||||
Tabs statt 6+ kitchen-sink). Sweep aus Critical- und Major-Findings
|
Tabs statt 6+ kitchen-sink). Sweep aus Critical- und Major-Findings
|
||||||
aus dem Codebase-Audit eingearbeitet.
|
aus dem Codebase-Audit eingearbeitet.
|
||||||
|
|
||||||
[Release-Notes 1.0.0](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v1.0.0)
|
[Release-Notes 1.0.0](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.0)
|
||||||
|
|
||||||
## [0.6.1] — 2026-05-03 — Pop-Out Discoverability & /tell Auto-Pop-Out
|
## [0.6.1] — 2026-05-03 — Pop-Out Discoverability & /tell Auto-Pop-Out
|
||||||
|
|
||||||
@@ -318,7 +357,7 @@ Pop-Out öffnen". Pop-Out-Input ist jetzt standardmäßig aktiv.
|
|||||||
Bugfixes: Ghost-Windows bei LRU-Drop / Logout, Dead-Zone unter dem
|
Bugfixes: Ghost-Windows bei LRU-Drop / Logout, Dead-Zone unter dem
|
||||||
Input-Bar bei aktivem Hint-Banner.
|
Input-Bar bei aktivem Hint-Banner.
|
||||||
|
|
||||||
[Release-Notes 0.6.1](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v0.6.1)
|
[Release-Notes 0.6.1](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.6.1)
|
||||||
|
|
||||||
## [0.6.0] — 2026-05-03 — UX Polish: Pop-Out Input + Colour Presets
|
## [0.6.0] — 2026-05-03 — UX Polish: Pop-Out Input + Colour Presets
|
||||||
|
|
||||||
@@ -328,7 +367,7 @@ Text-Buffer pro Pop-Out. Sieben Built-in-Color-Presets (Klassik,
|
|||||||
High-Contrast, Pastell, Dark-Mode-Tuned, Hellion, Night Blue, Indigo
|
High-Contrast, Pastell, Dark-Mode-Tuned, Hellion, Night Blue, Indigo
|
||||||
Violet) zum One-Click-Apply. Konfigurations-Migration v10 → v11.
|
Violet) zum One-Click-Apply. Konfigurations-Migration v10 → v11.
|
||||||
|
|
||||||
[Release-Notes 0.6.0](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v0.6.0)
|
[Release-Notes 0.6.0](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.6.0)
|
||||||
|
|
||||||
## [0.5.4] — 2026-05-02 — WrapText Hardening
|
## [0.5.4] — 2026-05-02 — WrapText Hardening
|
||||||
|
|
||||||
@@ -338,7 +377,7 @@ CodeQL-Critical-Alert "unvalidated local pointer arithmetic"
|
|||||||
dauerhaft. Keine nutzersichtbare Verhaltensänderung — Word-Wrap-Output
|
dauerhaft. Keine nutzersichtbare Verhaltensänderung — Word-Wrap-Output
|
||||||
ist byte-identisch zu 0.5.3.
|
ist byte-identisch zu 0.5.3.
|
||||||
|
|
||||||
[Release-Notes 0.5.4](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v0.5.4)
|
[Release-Notes 0.5.4](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.5.4)
|
||||||
|
|
||||||
## [0.5.3] — 2026-05-02 — Pointer Arithmetic Hardening
|
## [0.5.3] — 2026-05-02 — Pointer Arithmetic Hardening
|
||||||
|
|
||||||
@@ -346,7 +385,7 @@ Erster Anlauf zur Schließung des CodeQL-Critical-Alerts in
|
|||||||
`ImGuiUtil.WrapText`. Encoded-Byte-Buffer-Length wird vor der
|
`ImGuiUtil.WrapText`. Encoded-Byte-Buffer-Length wird vor der
|
||||||
Pointer-Arithmetik via `GetByteCount` validiert.
|
Pointer-Arithmetik via `GetByteCount` validiert.
|
||||||
|
|
||||||
[Release-Notes 0.5.3](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v0.5.3)
|
[Release-Notes 0.5.3](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.5.3)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -355,7 +394,7 @@ Pointer-Arithmetik via `GetByteCount` validiert.
|
|||||||
Releases vor 0.5.3 (Bootstrap-Phase 0.1.0 bis 0.5.2) sind direkt am
|
Releases vor 0.5.3 (Bootstrap-Phase 0.1.0 bis 0.5.2) sind direkt am
|
||||||
GitHub-Release-Stream einsehbar:
|
GitHub-Release-Stream einsehbar:
|
||||||
|
|
||||||
[Alle Releases](https://github.com/JonKazama-Hellion/HellionChat/releases)
|
[Alle Releases](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ Die Upstream-Sprach-Dateien (`Language.<lang>.resx`) sind nicht Teil dieser Date
|
|||||||
|
|
||||||
## Wie du beitragen kannst
|
## Wie du beitragen kannst
|
||||||
|
|
||||||
Bug-Reports, Feature-Wünsche und Pull-Requests laufen über [GitHub Issues](https://github.com/JonKazama-Hellion/HellionChat/issues). Workflow und Erwartungen stehen in [`../CONTRIBUTING.md`](../CONTRIBUTING.md), Code of Conduct in [`../CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md).
|
Bug-Reports, Feature-Wünsche und Pull-Requests laufen über [Gitea Issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues). Workflow und Erwartungen stehen in [`../CONTRIBUTING.md`](../CONTRIBUTING.md), Code of Conduct in [`../CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
Tester-Pool für neue Versionen läuft über den Hellion-Forge-Discord: [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR). Wer in den Tester-Channel rein will, einfach im Forge melden.
|
Tester-Pool für neue Versionen läuft über den Hellion-Forge-Discord: [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR). Wer in den Tester-Channel rein will, einfach im Forge melden.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Geplante Arbeit nach dem v1.0.0 Standalone-Cut. Diese Liste ist absichtlich
|
Geplante Arbeit nach dem v1.0.0 Standalone-Cut. Diese Liste ist absichtlich
|
||||||
grob: konkrete Specs, Größenschätzungen und Repro-Steps liegen im
|
grob: konkrete Specs, Größenschätzungen und Repro-Steps liegen im
|
||||||
internen Backlog. Tracking nach außen läuft über
|
internen Backlog. Tracking nach außen läuft über
|
||||||
[GitHub Issues](https://github.com/JonKazama-Hellion/HellionChat/issues)
|
[Gitea Issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues)
|
||||||
mit dem `roadmap`-Label, sobald ein Item für einen Cycle eingeplant ist.
|
mit dem `roadmap`-Label, sobald ein Item für einen Cycle eingeplant ist.
|
||||||
|
|
||||||
Reihenfolge ist Priorität, nicht Garantie. Items können sich verschieben
|
Reihenfolge ist Priorität, nicht Garantie. Items können sich verschieben
|
||||||
@@ -12,12 +12,32 @@ Privacy-First-Schnittmenge des Plugins erweisen.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Nächster Cycle (v1.4.3)
|
## Nächster Cycle (v1.4.4)
|
||||||
|
|
||||||
**Plugin-Load Async-Init** — IAsyncDalamudPlugin-Migration
|
**Window-Lazy-Open + Render-Init-Cost-Optimisation** — die in v1.4.3
|
||||||
und FontManager-async, Plugin-Konstruktor von 3.16 Sek auf
|
gelegte IAsyncDalamudPlugin-Foundation jetzt für die echten User-
|
||||||
unter 500 ms perceived load time. Größter und riskantester
|
spürbaren Wins nutzen. Window-Konstruktion erst beim ersten Open,
|
||||||
Patch der Serie, kommt nach drei stabilen Vorläufer-Patches.
|
Render-Path-Init-Kosten in den ersten Frames runter. Konkrete
|
||||||
|
Kandidaten und Größenschätzungen werden im v1.4.4-Brainstorm
|
||||||
|
konsolidiert.
|
||||||
|
|
||||||
|
## v1.4.3 — Plugin-Load Async-Init + Repo-Cutover (released 2026-05-08)
|
||||||
|
|
||||||
|
Vierter und größter Sub-Patch der v1.4.x Polish-Sweep-Serie. Plugin
|
||||||
|
auf Dalamud's IAsyncDalamudPlugin-API migriert: der Konstruktor
|
||||||
|
übernimmt nur noch Bootstrap-Essentials (Config-Load, Language-Init,
|
||||||
|
Conflict-Detection), Migrationen, Service-Allokationen, Window-
|
||||||
|
Konstruktion und Hook-Subscription wandern in LoadAsync. Schema-
|
||||||
|
Gate ersetzt die v9 → v16 Migrations-Kette; Configs auf Schema
|
||||||
|
v16+ laden direkt, ältere Configs triggern eine "install v1.4.2
|
||||||
|
first"-Fehlermeldung. AutoTranslate.PreloadCache vom Load-Pfad
|
||||||
|
runter. FontManager.BuildFonts läuft sync am Start von LoadAsync,
|
||||||
|
Dalamud baut den Font-Atlas auf seiner eigenen Pipeline.
|
||||||
|
Custom-Repo-URL auf `gitea.hellion-forge.cloud` cut-over, das
|
||||||
|
GitHub-Repo bleibt als eingefrorener v1.4.2-Snapshot stehen.
|
||||||
|
Plugin-Load-Zeit liegt bei ~3.7 s Median (5 Reloads), vergleichbar
|
||||||
|
mit v1.4.2: Async-Migration ist Foundation für v1.4.4 Lazy-Init-
|
||||||
|
Optimierungen, kein direkter User-spürbarer Win.
|
||||||
|
|
||||||
## v1.4.2 — ChatLog Frame-Hot-Path (released <Datum>)
|
## v1.4.2 — ChatLog Frame-Hot-Path (released <Datum>)
|
||||||
|
|
||||||
@@ -203,6 +223,7 @@ aktuellen Stand getestet.
|
|||||||
|
|
||||||
Hellion Chat ist EUPL-1.2-lizenziert. Konzept-Imports aus AGPL-3.0-Plugins
|
Hellion Chat ist EUPL-1.2-lizenziert. Konzept-Imports aus AGPL-3.0-Plugins
|
||||||
(z.B. XIV Instant Messenger) sind ausschließlich architektonische
|
(z.B. XIV Instant Messenger) sind ausschließlich architektonische
|
||||||
Inspiration, kein Code-Port. Imports aus dem GPL-3.0-kompatiblen
|
Inspiration, kein Code-Port. Code-Imports aus dem Upstream-Bestand
|
||||||
Upstream-Bestand laufen weiter über
|
sind seit v1.4.x abgeschlossen, weil Chat 2 in einem grundlegenden
|
||||||
[`UPSTREAM_SYNC.md`](UPSTREAM_SYNC.md).
|
Rework ist und selektive Patches nicht mehr sauber portierbar sind.
|
||||||
|
Stand und Begründung in [`UPSTREAM_SYNC.md`](UPSTREAM_SYNC.md).
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ HellionChat is a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo)
|
|||||||
by Infiziert90 (Infi) and Anna Clemens, also licensed under EUPL-1.2.
|
by Infiziert90 (Infi) and Anna Clemens, also licensed under EUPL-1.2.
|
||||||
The bulk of the code, including the message store architecture, the
|
The bulk of the code, including the message store architecture, the
|
||||||
channel logic, the hook system and the ImGui chat window, originates
|
channel logic, the hook system and the ImGui chat window, originates
|
||||||
from upstream. See `../NOTICE.md` and `UPSTREAM_SYNC.md` for the
|
from upstream. See `../NOTICE.md` for the attribution; `UPSTREAM_SYNC.md`
|
||||||
attribution and the cherry-pick policy.
|
documents the upstream-sync history, including the close of active
|
||||||
|
cherry-picking in the v1.4.x cycle.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
HellionChat is a standalone EUPL-1.2 plugin that originated from
|
HellionChat is a standalone EUPL-1.2 plugin that originated from
|
||||||
[Chat 2](https://github.com/Infiziert90/ChatTwo). Since v1.0.0 it
|
[Chat 2](https://github.com/Infiziert90/ChatTwo). Since v1.0.0 it
|
||||||
lives under its own namespace, IPC channels and source tree. I no
|
lives under its own namespace, IPC channels and source tree. The
|
||||||
longer track upstream as a Git fork, but I do monitor Chat 2 commits
|
active cherry-pick pipeline from upstream Chat 2 is closed since
|
||||||
regularly and cherry-pick selectively where it makes sense.
|
the v1.4.x cycle.
|
||||||
|
|
||||||
This document covers how that works so anyone (including future-me)
|
This document covers what that means, why I closed it, and what
|
||||||
can do it cleanly.
|
stays in place.
|
||||||
|
|
||||||
## A Word on Intent
|
## A Word on Intent
|
||||||
|
|
||||||
@@ -28,99 +28,77 @@ new UI from scratch and making deliberate architectural decisions that
|
|||||||
pull in a different direction. Some upstream patches will simply stop
|
pull in a different direction. Some upstream patches will simply stop
|
||||||
applying cleanly and that is expected.
|
applying cleanly and that is expected.
|
||||||
|
|
||||||
## One-Time Setup
|
## Why Cherry-Picking Stopped in v1.4.x
|
||||||
|
|
||||||
Add the upstream repo as a remote on a fresh clone:
|
Two things converged:
|
||||||
|
|
||||||
```bash
|
1. **Chat 2 is in a rework cycle.** Infi mentioned directly that
|
||||||
git remote add upstream https://github.com/Infiziert90/ChatTwo.git
|
parts of ChatTwo are being reworked and "stuff may not be able to
|
||||||
git fetch upstream
|
be cherry picked anymore." Once the upstream code paths I would
|
||||||
```
|
pull from no longer exist in the same shape, `git cherry-pick`
|
||||||
|
stops being a meaningful tool — what would land would not be the
|
||||||
|
change Infi wrote, it would be a hand-port of his concept.
|
||||||
|
2. **HellionChat has drifted enough that selective patches require
|
||||||
|
adaptation anyway.** The UI is being rebuilt, the theme engine
|
||||||
|
sits on top of HellionStyle which has no upstream equivalent, the
|
||||||
|
privacy filter changes how messages flow through MessageManager.
|
||||||
|
Even before the rework was announced, more and more upstream
|
||||||
|
patches needed adaptation rather than a clean apply.
|
||||||
|
|
||||||
Verify both remotes are wired up:
|
Together those two points mean continuing to call this an "active
|
||||||
|
cherry-pick pipeline" was no longer honest. So I closed it.
|
||||||
|
|
||||||
```bash
|
## What Closing the Pipeline Means in Practice
|
||||||
git remote -v
|
|
||||||
# origin https://github.com/JonKazama-Hellion/HellionChat.git (fetch)
|
|
||||||
# origin https://github.com/JonKazama-Hellion/HellionChat.git (push)
|
|
||||||
# upstream https://github.com/Infiziert90/ChatTwo.git (fetch)
|
|
||||||
# upstream https://github.com/Infiziert90/ChatTwo.git (push)
|
|
||||||
```
|
|
||||||
|
|
||||||
`upstream` is read-only. Never push to it.
|
- The `upstream` git remote was removed locally on 2026-05-08.
|
||||||
|
Anyone setting up a fresh clone does **not** add it back.
|
||||||
|
- New commits will not carry `(cherry picked from commit ...)`
|
||||||
|
trailers. Anything that originates from Chat 2 from this point
|
||||||
|
forward will be a hand-port at most, and it gets called out as
|
||||||
|
such in its own commit message and in the relevant source comments.
|
||||||
|
- The existing cherry-pick trail stays in the git history exactly as
|
||||||
|
it is. Every `(cherry picked from commit ...)` line that was added
|
||||||
|
with `-x` in earlier releases remains intact; that is the
|
||||||
|
attribution paper trail and removing it would be wrong.
|
||||||
|
|
||||||
## Reviewing What Is New Upstream
|
## What Does Not Change
|
||||||
|
|
||||||
Before any feature cycle I run a quick check:
|
- **EUPL-1.2 anchor lines in source files.** Files that originated
|
||||||
|
from Chat 2 keep their licence headers and any "based on
|
||||||
|
Infiziert90/ChatTwo" notice exactly as they are. The licence
|
||||||
|
obligations under EUPL-1.2 do not lapse because cherry-picking
|
||||||
|
stopped.
|
||||||
|
- **NOTICE.md** stays canonical. Attribution to Infi and Anna for the
|
||||||
|
message store, channel logic, hook system, ImGui chat window and
|
||||||
|
the localisation infrastructure remains the foundation statement of
|
||||||
|
this fork.
|
||||||
|
- **README acknowledgements.** The Acknowledgements section in
|
||||||
|
`README.md`, the maintainer thanks in the About tab, and the
|
||||||
|
`Language.*.resx` Crowdin translator credit list all stay as they
|
||||||
|
are.
|
||||||
|
- **The original `Language.*.resx` files** remain in the source tree
|
||||||
|
in their last upstream-sync state. They are the work of the Chat 2
|
||||||
|
Crowdin community and the existing translations stay valuable. They
|
||||||
|
will not receive automatic upstream updates anymore — see
|
||||||
|
CONTRIBUTING.md for what that means for translators.
|
||||||
|
|
||||||
```bash
|
## What Could Re-Open Later
|
||||||
git fetch upstream
|
|
||||||
git log --oneline main..upstream/main | head -30
|
|
||||||
```
|
|
||||||
|
|
||||||
That shows every commit Infi or contributors landed since the last
|
If Chat 2's rework lands and stabilises, and there is a piece of
|
||||||
sync. I read the messages and decide which ones apply to HellionChat.
|
upstream code that I genuinely want in HellionChat, the path forward
|
||||||
|
is **study and re-implement**, not cherry-pick. That means:
|
||||||
|
|
||||||
## What I Cherry-Pick
|
- Read the upstream change, understand the design, port the concept
|
||||||
|
to HellionChat's actual code paths.
|
||||||
|
- Credit the upstream author in the commit message and, if the
|
||||||
|
ported code is non-trivial, in a source-file comment.
|
||||||
|
- Pre-clear with Infi if the port is large enough to warrant a
|
||||||
|
conversation.
|
||||||
|
|
||||||
**Always:** security fixes, Dalamud API compatibility patches,
|
This is heavier than `git cherry-pick -x` and that is the point.
|
||||||
BetterTTV and emote-cache fixes, regression fixes for upstream
|
Cherry-picking was light because both codebases shared structure;
|
||||||
behaviour HellionChat still relies on.
|
once they do not, the proper attribution costs a real conversation
|
||||||
|
rather than a flag on a git command.
|
||||||
**Sometimes:** small bug fixes in `MessageManager.cs`,
|
|
||||||
`MessageStore.cs`, `ChatLogWindow.cs`, the Tabs system. These come in
|
|
||||||
when they touch code I have not heavily modified.
|
|
||||||
|
|
||||||
**Never:** webinterface changes (the entire webinterface tree is gone
|
|
||||||
in HellionChat), changes that conflict with the privacy filter, changes
|
|
||||||
that re-add upstream defaults I deliberately reversed (full-history
|
|
||||||
logging, Tell Exclusive defaults, etc.).
|
|
||||||
|
|
||||||
As HellionChat's UI moves further from the Chat 2 baseline, upstream
|
|
||||||
patches will increasingly require adaptation rather than a clean
|
|
||||||
apply. If a patch cannot be ported without breaking HellionChat
|
|
||||||
behaviour or the privacy model, I skip it rather than force a
|
|
||||||
compromised version in.
|
|
||||||
|
|
||||||
## How I Cherry-Pick
|
|
||||||
|
|
||||||
Always with `-x` so authorship and the original commit hash stay
|
|
||||||
visible:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b sync/upstream-<topic> main
|
|
||||||
git cherry-pick -x <upstream-commit-sha>
|
|
||||||
```
|
|
||||||
|
|
||||||
`-x` appends a `(cherry picked from commit <sha>)` line to the commit
|
|
||||||
message. That preserves upstream-author credit and lets anyone reading
|
|
||||||
`git log` trace the change back to Chat 2. Commit messages stay
|
|
||||||
identical to the upstream original; I do not rewrite them to match the
|
|
||||||
HellionChat format.
|
|
||||||
|
|
||||||
## Conflict Handling
|
|
||||||
|
|
||||||
When a cherry-pick conflicts:
|
|
||||||
|
|
||||||
1. Resolve by hand. Do not rewrite upstream code to match HellionChat
|
|
||||||
conventions; that is what the merge marker showed.
|
|
||||||
2. If the conflict is fundamental (touches code that no longer exists
|
|
||||||
in HellionChat), abort the cherry-pick and note why in the
|
|
||||||
relevant GitHub issue or backlog item. Some upstream patches are
|
|
||||||
simply not portable and that is fine.
|
|
||||||
3. After a clean resolve the commit message stays as-is, with the
|
|
||||||
`-x` footer Git appends automatically.
|
|
||||||
|
|
||||||
## Pushing the Sync
|
|
||||||
|
|
||||||
Cherry-picked commits go through the same review as any other change.
|
|
||||||
The sync branch lands in `main` via a no-fast-forward merge, then gets
|
|
||||||
a release tag if user-visible behaviour changed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout main
|
|
||||||
git merge --no-ff sync/upstream-<topic> -m "merge: upstream sync — <topic>"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing Back
|
## Contributing Back
|
||||||
|
|
||||||
@@ -138,17 +116,12 @@ A few things to note about that process:
|
|||||||
not push that decision onto his codebase.
|
not push that decision onto his codebase.
|
||||||
- This is not guaranteed for every change, only where it makes sense
|
- This is not guaranteed for every change, only where it makes sense
|
||||||
and where I am confident the fix is clean and self-contained.
|
and where I am confident the fix is clean and self-contained.
|
||||||
|
- Whether it gets accepted is Infi's call, and a "no" is fine.
|
||||||
## When Upstream Goes Silent
|
|
||||||
|
|
||||||
If Chat 2 stops receiving updates the remote stays configured and this
|
|
||||||
workflow stays documented. The moment maintenance picks back up I am
|
|
||||||
ready to pull again.
|
|
||||||
|
|
||||||
## When Upstream Takes a Direction I Cannot Follow
|
## When Upstream Takes a Direction I Cannot Follow
|
||||||
|
|
||||||
If a future Chat 2 release breaks compatibility with the HellionChat
|
If a future Chat 2 release breaks compatibility with the HellionChat
|
||||||
privacy philosophy in a way that cannot be resolved (mandatory cloud
|
privacy philosophy in a way that cannot be resolved (mandatory cloud
|
||||||
sync, removal of the local message store, an incompatible license
|
sync, removal of the local message store, an incompatible licence
|
||||||
change), HellionChat continues from the last compatible cherry-pick.
|
change), HellionChat continues from where it is. The inherited
|
||||||
The inherited history stays under EUPL-1.2 and stays attributed.
|
history stays under EUPL-1.2 and stays attributed.
|
||||||
|
|||||||
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended",
|
||||||
|
":dependencyDashboard",
|
||||||
|
":semanticCommits",
|
||||||
|
":timezone(Europe/Berlin)",
|
||||||
|
"schedule:weekly"
|
||||||
|
],
|
||||||
|
"labels": [
|
||||||
|
"dependencies",
|
||||||
|
"renovate"
|
||||||
|
],
|
||||||
|
"assignees": [
|
||||||
|
"JonKazama-Hellion"
|
||||||
|
],
|
||||||
|
"prHourlyLimit": 10,
|
||||||
|
"prConcurrentLimit": 20,
|
||||||
|
"rebaseWhen": "behind-base-branch",
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"description": "Group all minor and patch updates per ecosystem in one PR",
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"minor",
|
||||||
|
"patch"
|
||||||
|
],
|
||||||
|
"groupName": "minor and patch updates ({{manager}})"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Major updates always get their own PR with breaking-change label",
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"major"
|
||||||
|
],
|
||||||
|
"labels": [
|
||||||
|
"dependencies",
|
||||||
|
"major-update",
|
||||||
|
"breaking-change"
|
||||||
|
],
|
||||||
|
"addLabels": [
|
||||||
|
"needs-review"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "TypeScript type definitions stay grouped with each other",
|
||||||
|
"matchPackagePrefixes": [
|
||||||
|
"@types/"
|
||||||
|
],
|
||||||
|
"groupName": "type definitions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Dev dependencies in their own group",
|
||||||
|
"matchDepTypes": [
|
||||||
|
"devDependencies"
|
||||||
|
],
|
||||||
|
"groupName": "dev dependencies"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Pin GitHub Action versions by SHA for supply-chain hygiene",
|
||||||
|
"matchManagers": [
|
||||||
|
"github-actions"
|
||||||
|
],
|
||||||
|
"pinDigests": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vulnerabilityAlerts": {
|
||||||
|
"labels": [
|
||||||
|
"security",
|
||||||
|
"vulnerability"
|
||||||
|
],
|
||||||
|
"schedule": [
|
||||||
|
"at any time"
|
||||||
|
],
|
||||||
|
"prPriority": 10
|
||||||
|
},
|
||||||
|
"lockFileMaintenance": {
|
||||||
|
"enabled": true,
|
||||||
|
"schedule": [
|
||||||
|
"before 6am on monday"
|
||||||
|
],
|
||||||
|
"commitMessageAction": "Refresh"
|
||||||
|
},
|
||||||
|
"osvVulnerabilityAlerts": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# preflight.sh — pre-push gate. Blocks A/B/C verify config drift; Block D is a
|
||||||
|
# headless `dotnet build` to catch compile-time API drift. Test execution lives
|
||||||
|
# in the local Build-Suite repo and is NOT part of this preflight.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
echo "==> preflight: Block A — version consistency"
|
||||||
|
./scripts/verify-version-consistency.sh
|
||||||
|
|
||||||
|
echo "==> preflight: Block B — manifest shape"
|
||||||
|
./scripts/verify-manifest-shape.sh
|
||||||
|
|
||||||
|
echo "==> preflight: Block C — changelog sync"
|
||||||
|
./scripts/verify-changelog-sync.sh
|
||||||
|
|
||||||
|
echo "==> preflight: Block D — plugin compile health"
|
||||||
|
dotnet build HellionChat/HellionChat.csproj --configuration Release --nologo --verbosity quiet
|
||||||
|
|
||||||
|
echo "==> preflight: ALL GREEN"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# setup-hooks.sh — installs pre-push hook via core.hooksPath. Idempotent.
|
||||||
|
# Note: NO pre-commit hook — test execution is local-only in the Build-Suite repo,
|
||||||
|
# so the plugin repo's pre-commit cannot run tests. Versions/manifest/changelog
|
||||||
|
# checks happen on pre-push only.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
chmod +x .githooks/pre-push
|
||||||
|
echo "setup-hooks: core.hooksPath set to .githooks. pre-push live."
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-changelog-sync.sh — Block C.
|
||||||
|
# Strips .0 suffix from repo.json AssemblyVersion to derive the 3-digit tag/version.
|
||||||
|
# yaml.changelog is a single multi-line block with **Hellion Chat X.Y.Z** subblocks.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
YAML="$ROOT/HellionChat/HellionChat.yaml"
|
||||||
|
REPO_JSON="$ROOT/repo.json"
|
||||||
|
FORGE_DIR="$ROOT/.github/forge-posts"
|
||||||
|
|
||||||
|
fail() { echo "verify-changelog-sync: FAIL — $1" >&2; exit 1; }
|
||||||
|
ok() { echo "verify-changelog-sync: OK — $1"; }
|
||||||
|
|
||||||
|
VER="$(jq -r '.[0].AssemblyVersion' "$REPO_JSON" | sed -E 's/\.0$//')"
|
||||||
|
TAG="v$VER"
|
||||||
|
|
||||||
|
grep -qE "^[[:space:]]*\*\*Hellion Chat ${VER}" "$YAML" \
|
||||||
|
|| fail "$YAML changelog missing **Hellion Chat ${VER}** subblock. Fix: add the v${VER} block at the top of the changelog field."
|
||||||
|
|
||||||
|
jq -r '.[0].Changelog' "$REPO_JSON" | grep -qE "^[[:space:]]*\*\*Hellion Chat ${VER}" \
|
||||||
|
|| fail "$REPO_JSON Changelog missing **Hellion Chat ${VER}** subblock. Fix: copy the yaml changelog over."
|
||||||
|
|
||||||
|
FORGE_FILE="$FORGE_DIR/${TAG}.md"
|
||||||
|
[ -f "$FORGE_FILE" ] || fail "$FORGE_FILE missing. Fix: create from previous tag's file as template."
|
||||||
|
|
||||||
|
SUBTITLE="$(awk '/^---$/{f=!f; next} f && /^subtitle:/' "$FORGE_FILE" | sed -E 's/^subtitle:[[:space:]]*//' | tr -d '\"')"
|
||||||
|
[ -n "$SUBTITLE" ] || fail "$FORGE_FILE frontmatter missing 'subtitle'."
|
||||||
|
[ "${#SUBTITLE}" -le 60 ] || fail "$FORGE_FILE subtitle is ${#SUBTITLE} chars (max 60)."
|
||||||
|
|
||||||
|
NATUR="$(awk '/^---$/{f=!f; next} f && /^versionsnatur:/' "$FORGE_FILE" | sed -E 's/^versionsnatur:[[:space:]]*//' | tr -d '\"')"
|
||||||
|
[ -n "$NATUR" ] || fail "$FORGE_FILE frontmatter missing 'versionsnatur'."
|
||||||
|
[ "${#NATUR}" -le 40 ] || fail "$FORGE_FILE versionsnatur is ${#NATUR} chars (max 40)."
|
||||||
|
|
||||||
|
BODY="$(awk '/^---$/{f++; next} f==2' "$FORGE_FILE")"
|
||||||
|
TITLE_LEN=$((${#VER} + 16 + ${#SUBTITLE}))
|
||||||
|
FOOTER_LEN=80
|
||||||
|
TOTAL=$((TITLE_LEN + ${#BODY} + FOOTER_LEN))
|
||||||
|
[ "$TOTAL" -le 5500 ] || fail "Forge embed total ~${TOTAL} chars > 5500 cap."
|
||||||
|
|
||||||
|
YAML_VERSIONS="$(grep -cE '^[[:space:]]*\*\*Hellion Chat' "$YAML" || true)"
|
||||||
|
[ "$YAML_VERSIONS" -le 4 ] || fail "$YAML changelog has $YAML_VERSIONS version subblocks (max 4). Fix: move oldest to docs/CHANGELOG.md."
|
||||||
|
|
||||||
|
ok "yaml/repo.json/forge-post in sync for $TAG, embed-total ~${TOTAL}/5500, $YAML_VERSIONS/4 subblocks"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-manifest-shape.sh — Block B. Required fields in lowercase yaml,
|
||||||
|
# DalamudApiLevel sanity in repo.json, no own DalamudPackager.targets,
|
||||||
|
# Icon AND every ImageUrl reachable.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
YAML="$ROOT/HellionChat/HellionChat.yaml"
|
||||||
|
REPO_JSON="$ROOT/repo.json"
|
||||||
|
TARGETS_OWN="$ROOT/HellionChat/DalamudPackager.targets"
|
||||||
|
|
||||||
|
fail() { echo "verify-manifest-shape: FAIL — $1" >&2; exit 1; }
|
||||||
|
ok() { echo "verify-manifest-shape: OK — $1"; }
|
||||||
|
|
||||||
|
for FIELD in name author punchline description repo_url icon_url image_urls tags changelog; do
|
||||||
|
grep -qE "^${FIELD}:" "$YAML" || fail "$YAML missing required field: $FIELD"
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -f "$TARGETS_OWN" ] && fail "Own DalamudPackager.targets at $TARGETS_OWN strips Icon/ImageUrls. DELETE it; SDK default works."
|
||||||
|
|
||||||
|
API_LEVEL="$(jq -r '.[0].DalamudApiLevel' "$REPO_JSON")"
|
||||||
|
case "$API_LEVEL" in
|
||||||
|
''|null) fail "$REPO_JSON missing DalamudApiLevel" ;;
|
||||||
|
esac
|
||||||
|
[[ "$API_LEVEL" =~ ^[0-9]+$ ]] || fail "$REPO_JSON DalamudApiLevel must be integer, got: $API_LEVEL"
|
||||||
|
[ "$API_LEVEL" -ge 12 ] || fail "$REPO_JSON DalamudApiLevel=$API_LEVEL is below SDK 15 floor (12)."
|
||||||
|
|
||||||
|
if [ "${HOOKS_OFFLINE:-0}" != "1" ]; then
|
||||||
|
ICON_URL="$(jq -r '.[0].IconUrl' "$REPO_JSON")"
|
||||||
|
curl -fsI "$ICON_URL" > /dev/null || fail "IconUrl unreachable: $ICON_URL"
|
||||||
|
ok "IconUrl reachable: $ICON_URL"
|
||||||
|
|
||||||
|
COUNT="$(jq -r '.[0].ImageUrls | length' "$REPO_JSON")"
|
||||||
|
[ "$COUNT" -ge 1 ] || fail "repo.json ImageUrls is empty"
|
||||||
|
for i in $(seq 0 $((COUNT - 1))); do
|
||||||
|
URL="$(jq -r ".[0].ImageUrls[$i]" "$REPO_JSON")"
|
||||||
|
curl -fsI "$URL" > /dev/null || fail "ImageUrls[$i] unreachable: $URL"
|
||||||
|
done
|
||||||
|
ok "$COUNT ImageUrl(s) reachable, DalamudApiLevel=$API_LEVEL"
|
||||||
|
else
|
||||||
|
ok "skipped URL reachability (HOOKS_OFFLINE=1), DalamudApiLevel=$API_LEVEL"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-version-consistency.sh — Block A of preflight.
|
||||||
|
# csproj <Version> is 3-digit SemVer; repo.json AssemblyVersion is 4-digit (.0 suffix).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
CSPROJ="$ROOT/HellionChat/HellionChat.csproj"
|
||||||
|
REPO_JSON="$ROOT/repo.json"
|
||||||
|
|
||||||
|
fail() { echo "verify-version-consistency: FAIL — $1" >&2; exit 1; }
|
||||||
|
ok() { echo "verify-version-consistency: OK — $1"; }
|
||||||
|
|
||||||
|
CSPROJ_VER="$(grep -oE '<Version>[^<]+</Version>' "$CSPROJ" | head -1 | sed -E 's/<[^>]+>//g')"
|
||||||
|
[ -n "$CSPROJ_VER" ] || fail "$CSPROJ has no <Version> element"
|
||||||
|
|
||||||
|
EXPECTED_4DIGIT="${CSPROJ_VER}.0"
|
||||||
|
|
||||||
|
REPO_VER="$(jq -r '.[0].AssemblyVersion' "$REPO_JSON")"
|
||||||
|
[ "$REPO_VER" = "$EXPECTED_4DIGIT" ] \
|
||||||
|
|| fail "csproj=$CSPROJ_VER expects repo.json AssemblyVersion=$EXPECTED_4DIGIT but got $REPO_VER. Fix: align in $REPO_JSON."
|
||||||
|
|
||||||
|
TEST_VER="$(jq -r '.[0].TestingAssemblyVersion' "$REPO_JSON")"
|
||||||
|
[ "$TEST_VER" = "$EXPECTED_4DIGIT" ] \
|
||||||
|
|| fail "TestingAssemblyVersion=$TEST_VER must match $EXPECTED_4DIGIT. Fix: align in $REPO_JSON."
|
||||||
|
|
||||||
|
TAG="v$CSPROJ_VER"
|
||||||
|
for KEY in DownloadLinkInstall DownloadLinkUpdate DownloadLinkTesting; do
|
||||||
|
URL="$(jq -r ".[0].$KEY" "$REPO_JSON")"
|
||||||
|
case "$URL" in
|
||||||
|
*"/$TAG/"*) ;;
|
||||||
|
*) fail "$KEY=$URL does not contain tag $TAG. Fix: update $REPO_JSON $KEY to releases/download/$TAG/latest.zip." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ok "csproj=$CSPROJ_VER, repo.json=$EXPECTED_4DIGIT, tag $TAG present in DownloadLinks"
|
||||||