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
Two opt-in UX features. Existing users see no change unless they
enable the new toggles.
- Pop-out input: global master switch in Settings → Window → Frame.
When enabled, every pop-out window grows a compact input bar
(channel-coloured icon + text input). Independent text buffer and
history cursor per pop-out; channel changes apply globally.
- Chat colour presets: seven built-ins above the per-channel colour
list — ChatTwo Default, High-Contrast, Pastell, Dark-Mode-Tuned,
Hellion (brand), Night Blue (bonus), Indigo Violet (bonus).
Configuration migrates from v10 to v11 with a diagnostic log.