Closes the remaining gaps in GitHub's community-standards check, adds
explicit privacy and dependency documentation matching the plugin's
"DSGVO-by-design" claim, and removes the stale upstream Crowdin
artefact so the repo no longer suggests it ships its own translation
pipeline.
New community-health files:
- CODE_OF_CONDUCT.md: project-specific, short and direct, single
reporting path to kontakt@hellion-media.de
- CONTRIBUTING.md: scope, accepted vs declined contributions, build
and test instructions, EUPL-1.2 contribution terms, translation
policy split between Hellion-specific (here) and upstream strings
(Chat 2 repo)
- SUPPORT.md: routing for bugs, security, privacy and casual feedback
- .github/PULL_REQUEST_TEMPLATE.md: summary, change-type checklist,
testing notes, compatibility notes for migrations and manifest
fields, contribution checklist
- .github/FUNDING.yml: comments-only file, no platforms enabled,
points donors at the upstream Chat 2 maintainers' Ko-fi pages
New privacy and compliance documentation:
- PRIVACY.md: what the plugin stores locally (config, SQLite,
EmoteCacheV1), retention defaults, the two outbound network calls
(BetterTTV API+CDN with ShowEmotes opt-out, Square Enix Lodestone
font once-off), explicit no-telemetry statement, GDPR
Art. 15/17/18/20/21 rights mapped to plugin features, third-party
privacy-policy links
- THIRD_PARTY_NOTICES.md: direct NuGet dependencies with versions
pinned to v0.5.4 (MessagePack, Microsoft.Data.Sqlite, morelinq,
Pidgin, SixLabors.ImageSharp under Six Labors Split License 1.0),
Dalamud SDK and .NET tooling, bundled Exo 2 font (OFL-1.1) and
plugin icon, network-touch status per component, re-audit commands
Crowdin cleanup:
- crowdin.yml deleted (was upstream Chat 2's project_id 663694,
pointed at /ChatTwo/Resources/Language.resx, never wired to
HellionChat strings)
- README, CONTRIBUTING and CODE_OF_CONDUCT no longer suggest
HellionChat operates a Crowdin project; remaining mentions are
explicitly framed as upstream Chat 2's workflow
Contact and version consistency:
- Maintainer email switched from maintainer@hellion-media.de to
kontakt@hellion-media.de in SECURITY.md and NOTICE.md
- README version references updated to 0.5.4 (header, project status
block) and the update-tag pattern generalised from v0.1.x to v0.X.Y
- bug_report.yml version placeholder bumped to 0.5.4
- Project-documents table added to README footer linking all health
and reference files in one place
Release-body automation:
- .github/workflows/release.yml now extracts the matching version
block from ChatTwo/HellionChat.yaml's changelog and combines it
with a static install / docs footer (custom-repo URL, project
document links, licence) before passing the result to
softprops/action-gh-release@v3 via body_path
- Workflow fails fast if no changelog block exists for the tagged
version, automating the existing "yaml + repo.json + release body
kept in sync" rule
- Tag value passed via env: TAG_NAME with strict ^v\d+\.\d+\.\d+$
validation before any string concatenation, so the tag input cannot
break out into shell evaluation
The pointer-arithmetic CodeQL alert kept re-firing on each shape of
the previous shallow fix because Encoding.GetBytes is virtual and
every length value derived from its return inherited the taint.
Refactor the routine to thread int offsets through index-based
control flow and only compute pointers inside two small helpers
(CalcWordWrap and DrawText) that take an already-pinned base pointer
plus offsets sourced from local logic, not from any virtual return.
Buffer is now allocated against Encoding.UTF8.GetMaxByteCount via
ArrayPool with a real 16 KiB upper bound, and the encoded length
returned by GetBytes is validated against that ceiling before
anything touches the pointer. Behaviour is byte-identical to v0.5.3,
verified locally with the same input shapes the previous code path
handled.
Slim changelog: trimmed the per-version blocks down to v0.5.1-v0.5.4
plus a link to GitHub releases for older history. The previous block
ran ~9000 characters and was dragging the manifest payload down for
no benefit; users see the latest release block first anyway.
Single-fix patch to close the CodeQL pointer-arithmetic alert that
v0.5.2 left open. v0.5.2 already shipped, so we tag forward instead
of moving the published tag.
CodeQL re-opened the unvalidated-pointer-arithmetic alert at the new
textEnd line because Encoding.GetBytes is a virtual method on
Encoding and the returned array's Length is therefore tracked as
untrusted input for pointer arithmetic.
Compute the expected byte count from the same encoder via
GetByteCount and bail out if the actual buffer length does not match.
That is a real consistency check that would catch a maliciously
swapped Encoding.UTF8 instance, not a dead defensive guard. The
empty-split early-out from the previous fix is folded into the same
condition.
Three packaging defects rolled into one fix:
- The custom DalamudPackager.targets override forced HandleImages and
ImagesPath through the legacy code path. SDK 15 handles images by
default and the override produced an output manifest with neither
IconUrl nor ImageUrls populated. Removed.
- The csproj only included images/icon.png explicitly via
<None Include>, so chatWindow.png and withSimpleTweaks.png never
reached the build output and never made it into the release ZIP
either. Switched to a glob include.
- HellionChat.yaml carried no icon_url / image_urls, so even after
the SDK started writing the manifest correctly, both fields stayed
unset. Added them pointing at the public raw.githubusercontent
URLs that already work for the repo.json IconUrl.
Net effect on a fresh release: Dalamud picks up the icon next to the
DLL on dev installs, the plugin-installer card shows the proper
HellionChat logo for users coming through the custom repo, and the
two screenshot images are listed alongside the description so the
plugin installer carousel works the way other Dalamud plugins look.
Two CodeQL alerts opened against the codeql-manual-build workflow's
first scan. Both real, both small fixes.
#1 Medium / Workflow does not contain permissions
build.yml runs read-only against the repo (no push, no release
creation, no API mutations) but never declared a permissions
block, so the default GITHUB_TOKEN scope applied. Pin to
contents: read at workflow level. Release and CodeQL workflows
already have their explicit minimal scopes.
#2 Critical / Unvalidated local pointer arithmetic
ImGuiUtil.WrappedTextWithPos splits its input on newlines and
passes each part through Encoding.UTF8.GetBytes inside a fixed
block. Empty splits (consecutive newlines, blank lines) produced
a zero-length byte array, fixed gave us a valid pointer, and
textEnd = text + bytes.Length collapsed onto text. The downstream
ImGuiNative.CalcWordWrapPositionA calls received identical start
and end pointers, which is undefined behaviour at the native
boundary even if it happens to no-op on the current ImGui build.
Bail before entering the fixed block when bytes.Length == 0 and
render an empty line for the gap, which is what the original
text == null guard was trying to do but could never reach inside
a fixed block over a non-null array.
Three real-world adjustments to the default config that ships with a
fresh install:
- HellionThemeWindowOpacity 0.92 -> 0.5 so a fresh install lands at
the more glass-like default the maintainer uses daily
- Use24HourClock false -> true to match a German / European locale.
Works correctly thanks to the v0.5.1 strict-format fix that uses
CultureInfo.InvariantCulture instead of the host culture
- HellionParty preset Channel: InputChannel.Party -> null. Auto-
routing /party into a tab that also collects /alliance and /pvpteam
surprises the user when they wanted to type into the other ones;
the tab stays as a read surface
LoadPreviousSession and FilterIncludePreviousSessions stay false to
keep the privacy-strict 'every session starts fresh' line. The
maintainer's personal settings flip them on, but that's an
opt-in choice, not a default we should ship to every fresh install.
RetentionEnabled also stays false for the same opt-in reason.
Four defaults now match what a daily-driver Hellion install ends up at
anyway, so a fresh install does not feel like the wrong product:
- HellionThemeWindowOpacity 0.92 -> 0.5 (more glass-like)
- LoadPreviousSession + FilterIncludePreviousSessions false -> true
(tabs pick up where they left off after a crash or restart). The
privacy filter still gates what goes into the store; loading what
is already in there is not an additional privacy cost.
- Use24HourClock false -> true (matches a German / European locale,
works with the strict CultureInfo.InvariantCulture format from the
v0.5.1 fix).
RetentionEnabled stays at false because that one is a documented
opt-in privacy line, not a UX default. The persistent retention sweep
should require an explicit user gesture even though my own install
has it on.
The default GitHub-managed CodeQL setup builds C# without the Dalamud
assemblies (they live in user AppData, not in the repo or in NuGet),
so call-target resolution sits at 64% and the analysis tile reports
'Low C# analysis quality'. This workflow runs the same Dalamud staging
download we use for the regular build before the CodeQL build step,
which gives the analyser a fully-resolved compilation and pushes both
quality metrics above the 85% thresholds.
Two jobs:
- analyze-csharp on windows-latest with build-mode: manual and the
security-extended query suite, so we get the full SQL-injection,
path-traversal and crypto-misuse rule set on a clean compilation
- analyze-actions on ubuntu-latest with build-mode: none, scans the
workflow files in .github for action-injection patterns
Schedule runs Mondays at 06:17 UTC (low-traffic window).
The repo's CodeQL default setup needs to be switched to advanced in
Settings -> Code security before this workflow takes over, otherwise
both run in parallel and we waste runner minutes.
The live tell that triggers an Auto-Tell-Tab spawn is already in the
message store by the time MessageProcessed fires, because
MessageManager calls Store.UpsertMessage on line 266 before invoking
the event on line 277. PreloadHistory therefore picked up the live
tell as the youngest historic message and the separator landed below
it instead of above.
Pass the live message id through SpawnTempTab into PreloadHistory and
filter it out of the result. Pull one extra row so a successful
exclude does not cost the user a preload-budget slot.
Seven badges covering build status, CodeQL scanning, license, latest
release, Dalamud API level, .NET version and FFXIV expansion. Quick
visual indicator that the plugin is healthy and which tooling
generation it targets, plus shortcut links to the workflow runs and
security findings.
LICENSE now starts with the EUPL-1.2 standard header so github-linguist
detects the licence correctly in the repo header. The dual-copyright
block (upstream ChatTwo authors plus Hellion Online Media) moves into a
new COPYRIGHT file referenced from the README. NOTICE.md and
UPSTREAM_SYNC.md stay as-is.
New files under .github:
- workflows/build.yml: validates every push to main and every PR
against the current Dalamud staging branch on a Windows runner
- workflows/release.yml: builds Release on every v* tag, locates the
DalamudPackager latest.zip and attaches it to the matching GitHub
Release via softprops/action-gh-release
- dependabot.yml: weekly NuGet sweeps and monthly GitHub Actions
sweeps with conventional-commit prefixes, grouped patch and minor
PRs to cut review noise
- ISSUE_TEMPLATE/bug_report.yml + feature_request.yml + config.yml:
structured intake that pushes security reports through the private
advisory flow and routes upstream-only issues to ChatTwo
- SECURITY.md: documents the vulnerability reporting channels, scope,
and target disclosure window
The release workflow replaces the previous manual upload step. Tag a
release and the ZIP shows up on the release page automatically.
Drop the test project from version control. Upstream Chat 2 also
keeps ChatTwo.Tests outside the public repo, and the test sources
need a Dalamud assembly bundle that only resolves on a configured
Windows dev box anyway. The files stay on disk for local runs but
no longer ship with the source.
The LICENSE file added in ad2feb5 carries the dual-copyright block
(upstream ChatTwo authors plus Hellion Online Media) and is the one
the README now points at. The original Upstream-only LICENCE was a
verbatim copy of the EUPL text without our Hellion attribution and
became redundant the moment LICENSE landed. GitHub also prefers the
LICENSE filename for its license-detection in the repo header.
Three new top-level files plus README update in preparation for
leaving the GitHub fork network:
- LICENSE: full EUPL-1.2 text plus dual copyright notice (upstream
ChatTwo authors and Hellion Online Media). README previously
pointed at a non-existent LICENCE file, fixing that compliance
gap was overdue regardless of the fork-network decision.
- NOTICE.md: acknowledgements addressed directly to Infi and Anna,
honest framing of why the fork exists alongside upstream rather
than trying to displace it, plus maintainer contact channels for
attribution or takedown questions.
- UPSTREAM_SYNC.md: documents the manual cherry-pick workflow with
-x authorship preservation, the conflict-handling policy, and
what we will and will not pull from upstream. Replaces the
GitHub-Fork sync UI we will lose after detaching.
- README.md: version bump to 0.5.1, fork-network detach note, link
to NOTICE.md and LICENSE, fixed the LICENCE / LICENSE typo.
Both PRAGMA call sites take values that SQLite does not accept as
bound parameters. ColumnExists takes a hardcoded table name, the
migration call takes a compile-time int from the version sequence.
Comments now state both facts so future readers don't try to wedge a
defensive whitelist into a path that cannot be reached from anywhere
user-controlled.
Per-channel WHERE tuples and the catch-all default-clause now bind
ChatType and cutoff via named parameters instead of being inlined as
literals. Combines BindIntList for the explicit-types exclusion with
explicit AddWithValue for each (type, cutoff) tuple. Behavioural diff
against v0.5.0: none — same retention windows, same cutoff math, just
parameterised.
Migrate CleanupRetainOnly, StreamForExport, CountDateRange, GetDateRange
and GetPagedDateRange from interpolated IN lists onto BindIntList.
Eliminates the string-interpolation pattern for SQL value lists in the
IN-clause sites. Behavioural diff against v0.5.0: none — same enum/byte
values, just bound under named parameters instead of inlined.
Centralised builder for dynamic IN-clauses that binds each value as a
named parameter and returns the comma-joined placeholder string. Used
by the upcoming MessageStore migrations away from string-interpolated
SQL.
The text-disabled colour alone made greeted tabs visually weak in the
sidebar. Push dimmed Header and HeaderHovered values alongside the
existing Text push so the selected and hovered states match the
greeted state too. Idle state stays untouched because ImGui Selectable
has no idle background slot.
The preview block caches the deletion estimate from the last refresh.
When the user toggles whitelist channels afterwards the cached number
no longer reflects the current selection. Snapshot the whitelist on
refresh and detect drift on every frame; on drift, grey out the counts
and surface a stale hint plus an emphasised refresh button. Sits
alongside the existing Cleanup_Help_SavedNote, which warns about a
different mismatch (mutable vs saved) and stays as-is.
Three FontChooser ContinueWith handlers wrote Mutable.* directly from
the threadpool. Wrap the result-write in Plugin.Framework.Run so the
mutation lands on the same thread that owns the rest of the UI state.
Matches the marshalling pattern already used by Database.cs and
Privacy.cs background work.
async void Task.Run() is a no-op for awaiting purposes since the void
returns immediately. Switch LoadData to async Task and have the two
callers fire-and-forget the task directly. Exceptions still go through
the existing try/catch inside LoadData.
Both tab classes were the last two settings tabs still pulling their
display name from the upstream Language resource bundle. Move them
into HellionStrings so all eight settings tabs share one i18n source.
The unused Language.Options_*_Tab keys stay around for backwards
compat with cherry-picked upstream tabs.
Drop the inline wall-of-text description in favour of the standard
HelpMarker tooltip used across the rest of the v0.5.0 settings UX.
Visual consistency for the General tab.
Mark Emote.Id, Top100.Id, Top100.Code and Top100.ImageType as required
so the JSON deserializer enforces the contract instead of relying on
default-null semantics. Removes the four CS8618 warnings the build has
been carrying since v0.4.0.
Twelve organic settings tabs collapsed into eight themed ones (General,
Appearance, Window, Chat, Tabs, Privacy, Database, Information). Wipe
migration v9→v10 with HellionChat.json.pre-v10-backup safety net.
Default tab layout now spawns six themed tabs out of the box (General,
System, Free Company, Party, Beginner when Novice Network is on,
Linkshell, Tell Exclusive). HelpMarker pattern across every section,
disabled tooltips remain visible. Pre-release polish from full
codebase review covered race-conditions, EmoteCache retry, Allman
bracing, and dead i18n keys.
Split the technical/notification streams (System, Error, Echo, Debug,
NPC announcements, login/logout, retainer sales, gathering system,
glamour notifications, sign messages, alarms, orchestrion, message
book, random number, progress) out of the General tab into their own
System tab. General now shows player conversation plus the active
gameplay events (loot rolls, crafting, gathering, NPC dialogue, party
finder pings) without burying chat under technical chatter.