The FontManager constructor downloaded FFXIV_Lodestone_SSF.ttf from
img.finalfantasyxiv.com on first start (or read it from a local
cache) into a GameSymFont byte array. Both historical readers of
that field are gone:
- BuildFonts() used to feed the bytes into AddFontFromMemory; that
path was replaced by the Dalamud-provided AddGameSymbol helper.
- The upstream webinterface server wrote the bytes through a
BinaryWriter to serve them to the Svelte frontend; the entire
webinterface was intentionally removed in HellionChat.
With no live consumer left, the field, the constructor block, the
HttpClient call and the disk cache are all dead code. Removing them:
- eliminates the synchronous HTTP request on the plugin-load thread
(no more multi-second startup hang on slow networks)
- closes the implicit "no timeout, no size guard" exposure on that
request
- removes one outbound network endpoint (Square Enix Lodestone CDN)
from the privacy footprint
PRIVACY.md and THIRD_PARTY_NOTICES.md updated to reflect that
HellionChat now talks to BetterTTV only (opt-out via setting). Cached
TTF files left over from earlier versions stay in pluginConfigs/
HellionChat/ until a user removes them; they are simply no longer
read.
Build: 0 warnings, 0 errors. No behavioural change for users — symbol
glyphs (job icons, item glyphs, status effects) keep rendering through
Dalamud's built-in symbol font.
General code-quality and robustness pass across the plugin: thread-
safety on IPC state, resource-disposal cleanups, input validation,
defensive null-checks and a few small UX glitches. Compliance docs
(THIRD_PARTY_NOTICES, PRIVACY, COPYRIGHT) refreshed to v1.0.3.
Highlights
- ExtraChat IPC state synchronised across threads
- ChatLogWindow autocomplete no longer leaks the unmanaged
ImGuiListClipper allocation
- ChatLogWindow + Popout style stack stays balanced when config
toggles mid-frame
- Retention sweep and privacy cleanup wait for the actual filter
pass instead of the fire-and-forget Task that started it
- Configuration.LatestVersion bumped to 13 to match the active
migration path
- GameFunctions placeholder buffer guarded against oversized
replacement names
- TellTarget.IsSet, ResolveTempInputChannel, InputPreview, IconUtil,
Lender, Payloads, ExtraPayload all hardened against null / empty /
EOF / cycle inputs
- FontManager Lodestone download stays in scope for a follow-up
(timeout + lazy init pending)
- AutoTranslate replaced the msvcrt.dll memcmp P/Invoke with a
managed Span comparison
- Privacy cleanup worker thread marked IsBackground = true
- Database cleanup now removes both legacy files in one click
- Tell-target name redacted in the verbose debug log
Compliance
- THIRD_PARTY_NOTICES: last-reviewed bumped to v1.0.3, Pidgin 3.5.1,
SQLitePCLRaw.lib.e_sqlite3 3.50.3 listed as direct dependency with
CVE-2025-6965 / CVE-2025-7709 rationale
- PRIVACY: last-reviewed bumped to v1.0.3, BetterTTV trigger wording
clarified (list fetch at startup vs. on-demand image fetch)
- COPYRIGHT: upstream attribution range widened
Build: 0 warnings, 0 errors. No behavioural changes that would alter
existing user configuration or stored chat history.
v1.0.2 tag was claimed before the DownloadLink fix shipped, so the
content moves to v1.0.3. No code changes — manifest, repo.json,
CHANGELOG and README version refs roll forward; DownloadLink* URLs
now point to v1.0.3/latest.zip.
Manifest-bump in 8e9332a missed the three DownloadLink* entries.
Plugin-Manager fetched v1.0.1 zip (AssemblyVersion 1.0.1.0) against
the new repo.json (AssemblyVersion 1.0.2.0), tripping the version-
match guard.
Four small backlog items bundled:
- New: hide chat (and every other plugin window) while the New Game+
menu is open. Settings -> Window -> Frame, default off. Skips the
whole WindowSystem.Draw() pass while QuestRedo is visible, mirroring
the existing HideInLoadingScreens pattern.
- New: tint the channel selector button in the active channel colour.
Settings -> Appearance -> Colours, default on. Reuses the existing
inputColour computation (incl. ExtraChat override) and adds an
ImGuiCol.Button push around the selector. New ColourUtil helper
AdjustBrightness derives hover/active variants.
- Fix: PayloadHandler.InlineIcon hardcoded all hover icons to 32x32.
Replaced with float-based aspect-ratio-preserving shrink, single
scale-factor, zero-size guard, named MaxInlineIconSize constant.
Affects six call sites (status, item, achievement and other inline
hover paths).
- Diagnostic: HideState transitions log on Verbose level for both
ChatLogWindow and Popout.
Manifest bumped to 1.0.2 across csproj, yaml, repo.json. CHANGELOG
entry added, README version line updated. yaml + repo.json changelog
trimmed to the slim 4-version window (1.0.2, 1.0.1, 1.0.0, 0.6.1).
Bumps version to 1.0.1.0 and aligns the user-facing changelog across
HellionChat.csproj, HellionChat.yaml, repo.json and docs/CHANGELOG.md.
Headline fix: off-screen window recovery (one-shot bounds check on
plugin load + manual reset button under Settings -> Window -> Frame).
Bundled housekeeping since v1.0.0: docs restructured into docs/, stale
ChatTwo/* paths cleaned up across configs, Pidgin 3.3.0 -> 3.5.1,
actions/setup-dotnet 4 -> 5, github/codeql-action 3 -> 4.
DLL build verified locally; release.yml workflow generates the release
body from HellionChat.yaml on tag push.
Persisted ImGui window position can end up off-screen when the user
disconnects a monitor or changes display resolution between sessions.
The chat log window then renders outside the visible viewport with no
drag handles available, and the only recovery path is editing the JSON
config by hand.
This commit adds two layers of safety:
- Automatic one-shot bounds check on the first draw after plugin load.
If less than 100x40 pixels of the saved window position overlap the
primary viewport, the window snaps to a safe default offset
(top-left + 50px). Logged at INF level so users can verify the
recovery happened.
- Manual "Reset Window Position" button in Settings -> Window -> Frame
as a deliberate escape hatch when anything else slips past the
automatic check (different DPI scaling, viewport edge cases).
Pop-outs are intentionally not part of this recovery path: they are
non-persistent (cleared on plugin reload) and therefore cannot survive
a session boundary in an off-screen state.
Tested on Linux/Wayland (KAZAMA, Plasma, 3-monitor setup): hard-cut
test with both auxiliary monitors physically disconnected between
sessions reproduces the off-screen window before the patch and
recovers cleanly with this fix in place.
Catches up the only direct NuGet dependency that drifted behind on
the v1.0.0 standalone cut. The bump includes:
- 3.4.0: AnyCharExcept performance optimisation for single-char inputs
- 3.5.0: incremental parsing API in Pidgin.Incremental, public Expected
constructors, SequenceTokenParser performance improvement
- 3.5.1: CIString Unicode handling fix (relevant for non-ASCII
channel/tab names)
No security advisory drove this; rolling forward to align v1.0.0 with
the current upstream of every direct dependency. dotnet restore +
Release build verified locally, packages.lock.json regenerated.
- .gitattributes: linguist-generated path was still pointing at the
pre-v1.0.0 ChatTwo/Resources/ tree, which silently let the renamed
HellionChat/Resources/Language.*.resx files leak into Linguist's
language statistics
- bug_report.yml: drop the "or ChatTwo" filter hint; the plugin only
emits HellionChat.* into /xllog since the v1.0.0 standalone cut
- Move AI_DISCLOSURE, THIRD_PARTY_NOTICES, UPSTREAM_SYNC, ipc.md
into docs/ (ipc.md renamed to IPC.md for consistency)
- Add docs/ROADMAP.md, docs/CHANGELOG.md, docs/CONTRIBUTORS.md,
docs/LEARNING-JOURNEY.md
- Update README to reflect the v1.0.0 standalone state, drop the
development section, refresh the architecture tree, add a
release-cadence block linking to LEARNING-JOURNEY
- Fix stale ChatTwo/* source paths to HellionChat/* across docs
- Update cross-links in PRIVACY, CONTRIBUTING and .github/* so they
point at the new docs/ paths
Pure documentation pass, no code changes.
Adds a "Default tab layout sharpened" block between the Safety and
Crash-class sections in both yaml and repo.json. Explains the new
five-tab structure, calls out that the reset is one-time, that all
non-tab settings are preserved, and that the live config is backed
up to pluginConfigs/HellionChat.json.pre-v13-backup before the wipe
so users can restore manually.
The actual code change shipped in the previous commit; this commit
is purely the user-facing communication so the in-game migration
notification has matching written context.
Aligns the first-run tab layout with the sharpened defaults that
external testers asked for. Three changes in one commit:
1. VanillaGeneral now contains only Say/Yell/Shout. The previous
30-channel kitchen-sink (party, FC, every linkshell, all gameplay
events) buried the actual immediate-surroundings conversation
under loot rolls, crafting and PF pings.
2. HellionSystem absorbs the gameplay-event streams that used to live
in General — NpcDialogue, LootNotice, LootRoll, Crafting, Gathering,
PeriodicRecruitmentNotification — plus the announcement and battle-
system noise (BattleSystem, FreeCompanyAnnouncement,
PvpTeamAnnouncement) that previously had no fixed home.
3. The first-run / wipe default no longer adds HellionBeginner
conditionally and no longer adds a static VanillaTellExclusive tab.
Auto-Tell-Tabs spawns per-conversation tabs on demand, the static
tell-bucket is redundant. NoviceNetwork users can still add the
Beginner preset from Settings -> Tabs.
A new v12 -> v13 migration triggers a hard tab-wipe on existing
installs because per-channel mapping from the old General preset to
the new General/System split is ambiguous. The wipe scope is
narrow: only Config.Tabs is cleared, every other knob (Privacy,
Retention, Theme, etc.) keeps its current value. A pre-v13 backup
of the live config is written alongside it for manual restore.
Users see the existing SettingsRefactor migration notification.
Replaces the inherited Chat-2 IPC guide with a guide that follows the
Hellion README structure: top-level intro, a dedicated "Compatibility
with Chat 2" section that calls out exactly what the v1.0.0 rename did
(and did not) change, a channel-reference table, per-surface sections
with separate "Migration from Chat 2" diff blocks, and a closing
license/attribution paragraph that points back to NOTICE.md.
Content additions on top of the previous version:
- Explicit statement that the IPC surface is the Chat-2 surface re-
published under the Hellion name. Tuple shapes, lifecycle and call
semantics are unchanged; only the channel-string prefix differs
- Channel-reference table at the top so integrators can see the full
surface at a glance instead of reading two long code samples
- Tuple-payload field table for the Typing State IPC with a short
meaning per field
- Behavior notes for ChatInputStateChanged (fires once on subscribe,
then only on real changes) so integrators don't need to read the
source to learn the contract
- Explicit migration-diff blocks for both surfaces
Existing example code is preserved with the same identifiers; only
the host-class names are aligned to "HellionChat..." instead of
the old "ChatTwoIpc"/"TypingIntegration" placeholders.
ipc.md guides third-party plugin authors who want to bind to our
context-menu IPC and our typing-state IPC. After the v1.0.0 channel
rename (ChatTwo.* → HellionChat.*) the example code in this file no
longer matched the channels the plugin actually exposes — third-party
authors who copy-pasted from the doc would see silent no-op subscriptions.
Updated:
- All 6 channel string literals in code samples (Register, Unregister,
Invoke, Available, GetChatInputState, ChatInputStateChanged)
- Code-path references in the Typing State IPC explanation block
(HellionChat.Code.ChatType, HellionChat/Configuration.cs etc.)
- Class/variable name in the example (ChatTwoIpc → HellionChatIpc)
- Prose references to the plugin name
Added a short migration note at the top so existing integrators see
the rename in one paragraph instead of having to diff the file.
Brings the feature/hellionchat-rebrand-v1.0.0 branch into main as the
first fully standalone Hellion Chat release.
Includes:
- Rebrand from ChatTwo.* fork identity to standalone HellionChat
identity (namespace, repo folder, IPC channels, ImGui IDs, build
pipeline)
- Runtime detector that refuses to load the plugin when upstream
Chat 2 is also active, preventing the parallel-load FFXIV crash
- Three critical and twenty-one major pre-existing defects fixed
in the same release (CodeRabbit-flagged): AABB overlap correctness,
Equals/GetHashCode anti-pattern, IPC dispose mismatch, IDisposable
contract on DebuggerWindow, ExtraChat / GameFunctions null-deref
guards, AutoTranslate concurrent-access lock, Privacy retention
bounded waits, EmoteCache and FontManager HttpClient leaks,
SearchSelector ImGui ID collision, DbViewer count caching, plus
Sheets/Tabs/Popout bounds checks and the IconUtil binary-search
off-by-one
- SQLite native binary pinned to 3.50.3 (CVE-2025-6965, CVE-2025-7709)
- packages.lock.json now enforced via RestorePackagesWithLockFile
- Public-facing branding text aligned to standalone framing while
EUPL-1.2 license attribution is preserved unchanged
Verified locally on KAZAMA via dotnet build and manual FFXIV
smoketests A/B/C plus the post-fix sweep test.
The original v1.0.0 changelog only documented the rebrand. After the
CodeRabbit pass added 9 follow-up fix commits (3 critical bugs plus
21 major findings, grouped into safety / crash-class / correctness /
threading / resource / performance categories), the changelog needs
to reflect what users are actually receiving.
yaml + repo.json synchronized.
The DB export loop called filteredHistory.Count twice per 5000-message
batch — once for the progress fraction, once for the status text.
filteredHistory is built lazily by Filter() and re-enumerates on every
.Count access, so on a 2M-message history each batch was paying for
two full passes through the IEnumerable. Materializing the count once
at the top reduces the export to a single O(N) traversal as intended.
The RunOnTick + delayTicks pacing is intentional (keeps SeString
encoding on the framework thread and rate-limits the export to avoid
laggy frames during long exports), so the rest of the loop stays put.
Two pre-existing build/security defects flagged by CodeRabbit:
- HellionChat.csproj sets RestorePackagesWithLockFile=true so dotnet
restore honors the committed packages.lock.json. Floating version
ranges in the lockfile previously could drift between machines or
CI runs, producing builds with subtly different transitive
dependencies
- HellionChat.csproj pins SQLitePCLRaw.lib.e_sqlite3 to 3.50.3 to
override the older 2.1.11 native build that
Microsoft.Data.Sqlite 10.0.7 transitively pulls in. Ships SQLite
3.50.3 which contains the fixes for CVE-2025-6965 (memory
corruption from aggregate-term overflow) and CVE-2025-7709. The
managed Microsoft.Data.Sqlite wrapper stays on 10.0.7 — only the
native binary is bumped, no API breakage. Verified via the NuGet
spec: "the first three numbers in the version number of this
package indicate the version of SQLite that was used to build it"
- EmoteCache.cs replaces the per-call "new HttpClient()" with the
existing static Client field. The static instance already exists
for two other endpoints in the same file and reuses connection
pooling; the third call site was a stray that leaked a socket
on every emote download
- FontManager.cs wraps both the HttpClient and the HttpResponseMessage
in using-blocks, replaces the .Result/AggregateException sandwich
with GetAwaiter().GetResult() for clean exception propagation, and
adds EnsureSuccessStatusCode so failed downloads don't silently
produce a zero-byte font file. Full async refactor of the FontManager
constructor is tracked separately
- Util/AutoTranslate.cs introduces a single EntriesLock object and
serializes every read and write of the static Entries dictionary
and ValidEntries hash set behind it. PreloadCache spawns a worker
thread that fills both while the main thread reads them via the
Matching / ReplaceWithPayload / StartsWithCommand entry points;
without the lock the underlying collection access was undefined.
AllEntries() splits into a thin lock wrapper plus a private
BuildEntriesLocked() helper that runs under the lock
- Ui/SettingsTabs/Privacy.cs bounds the .Wait() on the framework
refresh after a manual retention sweep and after the privacy
cleanup. A hung framework tick previously could deadlock the
background worker thread. Five-second timeout, log on miss
- Util/SearchSelector.cs ImRaii.PushId(id) collapsed every row in the
filtered list to the same ImGui ID, leaving the ID stack ambiguous
for click resolution. Mix the row index into the pushed id so every
Selectable has a distinct ImGui identifier
- Ui/SettingsTabs/Chat.cs blocked-emote add-button never opened the
selector popup because SearchSelector.SelectorPopup is wrapped in
ImRaii.ContextPopupItem (right-click semantics). Detect the
IsItemClicked() event after the button and call ImGui.OpenPopup
explicitly so left-click opens the picker too
- Ui/Debugger.cs DebuggerWindow now declares IDisposable so the
existing Dispose() method participates in disposal patterns
(using-blocks, container cleanup). Previously the method existed
but the type didn't advertise it, so callers had no way to invoke
it correctly and the command-handler subscription leaked
- Resources/Language.zh-Hans.resx Webinterface_Start_Success
contained "网页界面已停止。" (web interface stopped) for what is
semantically the start-success message; corrected to
"网页界面已启动。" (web interface started). String is unused in
the Hellion fork (webinterface removed) but remains in the
resource bundle for upstream compatibility
Two pre-existing upstream defects fixed in v1.0.0:
- Ui/Popout.cs PopOutDocked[Idx] now bounds-checks Idx against
ChatLogWindow.PopOutDocked.Count before reading or writing. A
popout instance can outlive a list resize when AddPopOutsToDraw()
rebuilds the docked-state list while a draw frame is in flight,
which previously produced an out-of-range crash on tab drop
- Ui/SettingsTabs/Tabs.cs guards against an empty worlds list before
indexing worlds[selectedWorld]. Empty lists can occur briefly when
switching characters or before the datacenter sheet finishes
loading — the previous code would crash with an
ArgumentOutOfRangeException
Four pre-existing upstream defects fixed in v1.0.0:
- Util/GlobalParametersCache.cs GetValue captures Cache into a local
before the bounds check, so the check and the indexed read operate
on the same array reference even when Refresh reassigns Cache from
the main thread between the two operations
- Util/IconUtil.cs binary search bounds: hi initialized to
entries.Length-1 (was Length), and reset on redirect-restart;
added entries.Length==0 short-circuit to prevent indexing into
empty arrays
- Sheets.cs WorldsOnDatacenter compared Region.RowId, which groups
by region instead of datacenter — now compares DataCenter.RowId
directly so the result actually reflects same-DC worlds
- Message.cs back-reference loop iterates the processed Sender/Content
properties rather than the raw constructor parameters, so chunks
added or replaced by CheckMessageContent also get Message set
Three pre-existing upstream defects flagged by CodeRabbit, fixed in the
v1.0.0 standalone cut where we own the codebase:
- Ipc/ExtraChat.cs Dispose now unsubscribes all three IPC subscriptions
(OverrideChannelGate, ChannelCommandColoursGate, ChannelNamesGate)
instead of only the first; previously the latter two leaked their
subscriptions on every plugin reload
- GameFunctions/Types/TellTarget.cs FromTarget guards against a zero
IPlayerCharacter.Address before dereferencing the unsafe Character*
cast; previously a missing/destroyed target object would crash the
game on /tell construction
- GameFunctions/GameFunctions.cs ResolveTextCommandPlaceholderDetour
null-checks the Hook reference before calling .Original instead of
using the null-forgiving operator; defensive guard for teardown races
UnregisterGate is registered via RegisterAction(Unregister) on
construction (Unregister returns void), but Dispose was calling
UnregisterFunc() instead of UnregisterAction(). The mismatched
unregister call leaks the action subscription on plugin reload —
subsequent Dispose/Init cycles would accumulate orphan handlers
in the Dalamud IPC layer.
Pre-existing upstream issue (CodeRabbit critical finding); fixed in
v1.0.0 standalone cut where we own the codebase.
Equals(object?) was delegating to GetHashCode() comparison, which is
the textbook hash-collision anti-pattern: two distinct ChatCode values
could in principle share a hash and be wrongly reported as equal. The
current GetHashCode implementation packs Type/Source/Target into 24
bits and happens to be collision-free, but the contract is fragile —
any future change to GetHashCode silently breaks Equals.
Replaced with direct field-by-field comparison of Type, Source, Target.
GetHashCode is left unchanged so dictionary/HashSet behavior stays
identical.
Pre-existing upstream issue (CodeRabbit critical finding); fixed in
v1.0.0 standalone cut where we own the codebase.
The previous implementation nested a ValueInRange helper that used
strict inequalities at both ends. That dropped identical rectangles
and shared-edge cases as false negatives — two rectangles with
a.X == b.X would miss the overlap on the X axis even when they
clearly overlapped.
Replaced with the standard AABB test: rectangles overlap iff they
overlap on both axes (a.Min < b.Max && a.Max > b.Min per axis).
Pre-existing upstream issue (CodeRabbit critical finding); fixed in
v1.0.0 standalone cut where we own the codebase.
AssemblyVersion and TestingAssemblyVersion bumped to 1.0.0.0,
DownloadLinks (Install/Update/Testing) all point to
releases/download/v1.0.0/latest.zip, Changelog block mirrors
HellionChat.yaml.
Updates the project root namespace and replaces the historical comment
about cherry-pick compatibility — that compatibility was the rationale
for keeping ChatTwo.* in source while AssemblyName was already
HellionChat. With v1.0.0 the standalone cut is complete and both
identifiers match.
Note: this commit also fixes the runtime MissingManifestResourceException
that the previous Task 6 commit caused — the embedded resource prefix
is derived from RootNamespace at build time, so Designer.cs string
arguments and the actual resource names only line up once both are set
to HellionChat.
81 namespace declarations and 100 using directives converted via sed,
plus two FQN-aliases (ChatTwoPartyFinderPayload in PayloadHandler.cs and
ModifierFlag in KeybindManager.cs) updated. Critical: Language.Designer.cs
and HellionStrings.Designer.cs ResourceManager string arguments updated
synchronously — these are runtime reflection lookups not caught by the
C# compiler.
Two intentional ChatTwo references remain: the legacy migration path
'ChatTwo.json' in Plugin.cs (still points to upstream Chat 2's config
file by design) and the InternalsVisibleTo declaration in
AssemblyInfo.cs (handled in the upcoming repo-folder rename task).
The local alias names 'ChatTwoPartyFinderPayload' and 'ChatTwoConflictDetector'
are preserved as local symbols; only their target namespaces and references
changed.
README, repo.json and HellionChat.yaml describe the plugin as a
chat-replacement based on Chat 2 rather than as a Chat-2 fork. License
attribution (NOTICE.md, COPYRIGHT, THIRD_PARTY_NOTICES.md, README
credits section) is left untouched per EUPL-1.2 obligations.
Architecture section in README updated to reflect that the
HellionChat namespace is now consolidated (post-v1.0.0).
When the user has both Hellion Chat and upstream Chat 2 installed and
loaded, the parallel chat-window replacement and Hook collisions can
crash FFXIV at frame boundaries. The new detector inspects
IDalamudPluginInterface.InstalledPlugins and throws an
InvalidOperationException with a localized message if Chat 2 is loaded,
which Dalamud surfaces cleanly instead of letting the load proceed
into a runtime crash.
Bilingual messages (EN/DE) follow the existing HellionStrings pattern.
All 6 inter-plugin communication channels are renamed for the v1.0.0
standalone cut. Prevents Dalamud IPC registration conflicts when a user
has both Hellion Chat and upstream Chat 2 installed.
- IpcManager: Register, Available, Unregister, Invoke
- TypingIpc: GetChatInputState, ChatInputStateChanged
Breaking change for third-party plugins that bound to ChatTwo.* — none
known at the time of this commit.
- Visible pop-out icon button in chat header toolbar (right-aligned)
- One-time hint banner introduces toolbar + right-click and the v0.6.1 default flip
- Settings → Chat → Auto-Tell-Tabs → "Open new /tell tabs directly as pop-out"
- PopOutInputEnabled hard-flipped to true via v11 → v12 migration
- Bugfix: pop-out windows of LRU-dropped or logout-stripped temp tabs are now properly torn down (no more ghost windows)
- Bugfix: dead zone below chat input bar when v0.6.0 hint banner was visible (also fixes Jin's report on the v0.6.0 in-pop-out banner)
- CI: fix release.yml YAML parse failure (heredoc footer extracted to .github/release-footer.md), add workflow_dispatch recovery trigger
- README + SUPPORT.md + repo.json + yaml: Hellion Forge Discord link