Compare commits

..
7 Commits
Author SHA1 Message Date
JonKazama-Hellion c1d6ca3bbd docs(readme): carry the version badge and header to 2.0.3
Security Scan (reusable) / Security Scan (push) Successful in 24s
Security / scan (push) Successful in 23s
Build / Build (Release) (push) Successful in 32s
Forge Announce / Post changelog to Hellion Forge (push) Successful in 6s
Release / Build and attach release ZIP (push) Successful in 29s
2026-08-20 07:58:45 +02:00
JonKazama-Hellion 4ab9202533 Merge branch 'feature/v2.0.3' into main
Pop-out context menu, screenshot mode toggle, and colours with no alpha
byte. Smoke-tested in game on all three before the merge.
2026-08-20 07:58:18 +02:00
JonKazama-Hellion 5e15d34eeb chore(release): 2.0.3 -- pop-out context menu, screenshot mode
Three fixes, all of them things that simply did not work: the context menu
in pop-out windows, the screenshot mode toggle, and text the game colours
without an alpha byte.

The 1.5.6 changelog block moves out of the plugin manifest to stay inside
the four-subblock limit; it remains in docs/CHANGELOG.md.
2026-08-20 07:58:13 +02:00
JonKazama-Hellion b9feb8650f chore(comments): drop the spec task codes the last pass missed
Codes like POP-1c or B4b-2 name a task in a planning document, not
anything in the code. A reader has no way to resolve them and they age
into noise the moment the document is closed. Where a code was used as a
reference, the sentence now names the function it meant.
2026-08-20 07:54:45 +02:00
JonKazama-Hellion 0f9858a3d3 fix(screenshot-mode): the toggle never wrote its change to disk
Screenshot mode is a persisted setting, but neither of the two toggles
saved the config. Turning it on only stuck when some unrelated save
happened to run afterwards -- and once it was stored, turning it off never
reached the file at all, so it came back on with every plugin load.

Both toggles save now. An install currently stuck on it needs one click.
2026-08-20 07:54:38 +02:00
JonKazama-Hellion a3379818eb fix(popouts): the context menu never opened outside the main window
Right-clicking a name or an item inside a pop-out did nothing at all.

One payload handler is shared by the main window, every pop-out and the
input preview, and it holds a single popup state. The main window is
registered first, so it draws first, finds no open popup in its own scope,
reads that as "closed" and clears the state -- before the window that
actually opened the popup gets its turn.

The popup now belongs to the surface that opened it. The others leave its
state alone instead of dropping it. The rule itself sits in its own helper
because the handler pulls in Dalamud and cannot be loaded from a test.
2026-08-20 07:54:32 +02:00
JonKazama-Hellion d5c7db9f43 fix(colours): text the game colours without an alpha byte rendered invisible
The game hands out some of its configured colours as ARGB with the alpha
byte left at zero. The byte swap then moved that zero into the alpha slot
and the text drew fully transparent.

Two guards, and they belong together: forcing alpha on a colour that is
zero everywhere would turn "no colour" into opaque black, which slips
past the invisible-text fallback in ChunkUtil and paints over the colour
the renderer would otherwise inherit.
2026-08-20 07:54:19 +02:00
20 changed files with 108 additions and 39 deletions
+7
View File
@@ -0,0 +1,7 @@
---
subtitle: "Kontextmenü in Pop-outs, Screenshot-Modus"
versionsnatur: "Fehlerbehebung"
---
- **Das Kontextmenü ging in Pop-out-Fenstern nicht auf.** Ein Rechtsklick auf einen Namen oder ein Item hat dort schlicht nichts gemacht. Alle Chat-Flächen teilen sich einen Popup-Zustand, und das zuerst gezeichnete Fenster hat das Menü verworfen, bevor das Pop-out überhaupt an der Reihe war. Das Menü bleibt jetzt bei dem Fenster, aus dem der Klick kam.
- **Der Screenshot-Modus ließ sich nicht ausschalten.** Er wird gespeichert, aber keiner der beiden Schalter hat die Änderung in die Konfiguration geschrieben. Einmal gespeichert kam er bei jedem Laden des Plugins wieder. Beide Schalter speichern jetzt. Wer ihn gerade festhängen hat, klickt ihn einmal aus.
- **Vom Spiel eingefärbter Text konnte unsichtbar werden.** Manche dieser Farben kommen ohne Alpha-Byte an und wurden beim Umsortieren vollständig transparent.
+1 -1
View File
@@ -61,7 +61,7 @@ public sealed class FontManager : IDisposable
// widget gallery exposes it.
internal static float SenderWeight = 1.3f;
// Wired post-build (B4b-3); a Func keeps FontManager off the theme layer.
// Wired post-build; a Func keeps FontManager off the theme layer.
private Func<ThemeTypography?>? _typographySource;
// Lets RebuildDelegateFontsIfChanged skip rebuilds when the size is unchanged.
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
<PropertyGroup>
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
<Version>2.0.2</Version>
<Version>2.0.3</Version>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Use lock file to pin exact versions -->
+8 -14
View File
@@ -35,6 +35,14 @@ tags:
- Replacement
- Privacy
changelog: |-
**v2.0.3 — Pop-out context menu, screenshot mode (2026-08-20)**
- **The context menu never opened in a pop-out window.** Right-clicking a name or an item there did nothing at all. Every chat surface shares one popup state, and whichever window drew first cleared the menu it had not opened before the pop-out got its turn. The menu now stays with the window the click came from.
- **Screenshot mode could not be turned off.** It is a saved setting, but neither of its two toggles wrote the change to the config file, so switching it off never survived a reload and the mode came back on at every plugin load. Both toggles save now. If it is stuck on for you, one click off is enough.
- **Text the game colours itself could render invisible.** Some of those colours arrive without an alpha byte, and the byte order swap then left them fully transparent.
---
**v2.0.2 — Emotes out, placeholders fixed (2026-08-19)**
- **BetterTTV emote support is gone.** Its shared-emote endpoint went behind authentication, and that was where nearly all of them came from — what remained was a 65-entry global set, eleven of which are on the known-broken list, so 54 mostly static images from Twitch's early days. Exactly one was animated, and that one wanted 492 frames and 37 MB of video memory. The plugin now makes no outbound network calls at all. Messages stored with emotes still read fine; they show the code that was typed.
@@ -83,18 +91,4 @@ changelog: |-
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). The two codebases have diverged far enough that they no longer line up.
---
**v1.5.6 — Settings Overhaul + Filter & Notification Polish (2026-05-23)**
- Settings window reorganised: ten tabs down to seven (General, Appearance, Chat, Window, Channels, Data & Privacy, About). Each tab now uses collapsible sections grouped by control type. Sections start collapsed every time you open a tab — less noise, easier to find what you need.
- New sender-name display options under Chat → Messages: separate world-suffix and name-format modes (Full name / First name only / Initials × Never / Other worlds only / Always).
- Plugin-only symbols now show a pre-send warning so other players do not get empty boxes (Chat → Messages → "Warn before sending plugin-only symbols").
- Separate window opacity for focused vs. inactive chat window (Appearance → Window style → "Inactive window opacity"). The slider above sets the focused value.
- Custom notification sound volume slider (General → Sound, and mirrored in Channels → per-tab → Notification). Affects only the three bundled custom sounds; the 16 game sounds are unaffected.
- The per-tab regex filter that briefly shipped earlier in this cycle has been removed — FFXIV's built-in blackword filter covers the same need.
- All 24 locale files updated for the new section labels and the v1.5.6 control labels (machine translation; native review continues via the Hellion Forge Discord).
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
Earlier history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
@@ -29,7 +29,7 @@ internal sealed class ThemeRegistryInitHostedService(
foreach (var _ in registry.AllCustom()) { }
registry.SwitchSilent(Plugin.Config.Theme);
// B4b-3: point font sizes at the active theme's typography, wire future
// Point font sizes at the active theme's typography, wire future
// theme switches to the atlas rebuild, and apply the boot theme's override.
fontManager.SetTypographySource(() => registry.Active.Typography);
registry.SetActiveChangedCallback(() => fontManager.RebuildDelegateFontsIfChanged());
+14 -2
View File
@@ -49,6 +49,7 @@ internal sealed class PayloadHandler
public uint LastHoverCounter;
private (Chunk, Payload?)? _popup;
private object? _popupOwner;
public PayloadHandler(
ThemeRegistry themes,
@@ -69,6 +70,10 @@ internal sealed class PayloadHandler
_logger = logger;
}
// Every chat surface shares this handler, so each one claims it before it
// renders. Without that the popup cannot tell whose click it belongs to.
internal object? ActiveSurface { get; set; }
internal void Draw()
{
DrawPopups();
@@ -84,7 +89,9 @@ internal sealed class PayloadHandler
private void DrawPopups()
{
if (_popup == null)
// A foreign surface finds no open popup here and would otherwise read
// that as "closed" and drop the state before the owner gets to draw.
if (_popup == null || !PopupOwnership.Owns(_popupOwner, ActiveSurface))
return;
var (chunk, payload) = _popup.Value;
@@ -93,6 +100,7 @@ internal sealed class PayloadHandler
if (!popup.Success)
{
_popup = null;
_popupOwner = null;
return;
}
@@ -186,7 +194,10 @@ internal sealed class PayloadHandler
return;
}
ImGui.Checkbox(Language.Context_ScreenshotMode, ref Plugin.Config.ScreenshotMode);
// The flag is persisted, so every toggle has to write it. Without the
// write it only sticks when some unrelated save happens to run after.
if (ImGui.Checkbox(Language.Context_ScreenshotMode, ref Plugin.Config.ScreenshotMode))
Plugin.Instance.SaveConfig();
if (ImGui.Selectable(Language.Context_HideChat))
Plugin.Config.HideChat = true;
@@ -934,6 +945,7 @@ internal sealed class PayloadHandler
private void RightClickPayload(Chunk chunk, Payload? payload)
{
_popup = (chunk, payload);
_popupOwner = ActiveSurface;
ImGui.OpenPopup(PopupId);
}
}
@@ -4,7 +4,7 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests;
// Optional metric capture. Accumulates 1000 steady-state frames of ImGui IO
// counters plus the plugin's full-Draw wall-time (Plugin.LastDrawMs, B5-1),
// counters plus the plugin's full-Draw wall-time (Plugin.LastDrawMs),
// then writes a single perf-baseline.json into the plugin ConfigDirectory so
// the cycle-notes author can copy the baseline figures without a separate
// profiling harness. The step only records — it never fails on a threshold
+1 -1
View File
@@ -13,7 +13,7 @@ internal static class ThemeJsonLoader
// policy from the v2.x style refactor: v1 user themes are not migrated,
// they're silently ignored so the loader stays free of legacy mapping
// code. Any other malformed input still throws FormatException.
// B4b-2: callers must pass the logger or the default-fill warnings go silent.
// Callers must pass the logger or the default-fill warnings go silent.
public static Theme? LoadFromString(string json, ILogger? logger = null)
{
if (string.IsNullOrWhiteSpace(json))
+3
View File
@@ -901,7 +901,10 @@ internal sealed class InputBar
)
)
{
// Persisted flag -- write it here too, or turning it back off
// never reaches the config file.
Plugin.Config.ScreenshotMode = !Plugin.Config.ScreenshotMode;
Plugin.Instance.SaveConfig();
}
if (
+5
View File
@@ -158,6 +158,11 @@ internal sealed class MessageList
return;
}
// Claim the shared handler, so a popup opened here stays with this
// window instead of being cleared by whichever surface draws first.
if (_handler is not null)
_handler.ActiveSurface = this;
// No own ImRaii.Child here — MainWindow already wraps the message
// area in one. Nesting would give the window two stacked scrolls
// and a runaway content-height computation.
+1 -1
View File
@@ -52,7 +52,7 @@ internal sealed class TopTabBar
for (var i = 0; i < tabs.Count; i++)
{
var tab = tabs[i];
// POP-1b: a popped-out tab is owned by its pop-out window, not the main
// A popped-out tab is owned by its pop-out window, not the main
// strip (1.5.6 exclusivity). Gate on the pool, not Tab.PopOut (stale flag).
if (_pool.IsOpen(tab.Identifier))
continue;
+1
View File
@@ -175,6 +175,7 @@ internal sealed class InputPreview : Window
_handlerLender.ResetCounter();
var handler = _handlerLender.Borrow();
handler.ActiveSurface = this;
_chunkRenderer.DrawChunks(
_previewMessage!.Content,
wrap: true,
@@ -155,7 +155,7 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
if (Bound is null)
return;
// POP-1f: the bound tab is live-visible in this pop-out, so it carries no
// The bound tab is live-visible in this pop-out, so it carries no
// unread badge — mirror MainWindow's per-frame zero for the active tab.
// View-state reset only (tab.Messages store is untouched).
Bound.Unread = 0;
+2 -2
View File
@@ -310,9 +310,9 @@ internal sealed class MainWindow : Window, IFocusableChatWindow
TabLifecycleHelpers.OnTabActivated(reseed, active);
}
// POP-1c: a popped-out tab must not stay the main window's active surface
// A popped-out tab must not stay the main window's active surface
// (1.5.6 exclusivity). Re-anchor to the first non-popped tab the moment the
// active one is popped; null when every tab is popped (POP-1d guards Draw).
// active one is popped; null when every tab is popped, which Draw guards for.
// Runs post-seed, before the sidebar/top-tab draw, so the popped tab never
// renders. Idempotent: PickMainActiveTab returns the same reference once
// settled, so OnTabActivated fires only on the pop frame.
+15
View File
@@ -66,8 +66,23 @@ internal static class ColourUtil
);
}
// ---------------------------------------------------------------
// Cherry-picked from ChatTwo upstream a51789b + 5b58513 (Infiziert90,
// 2026-06-12). Both guards belong together -- a51789b alone turns a zero
// colour into opaque black, which then slips past the invisible-text
// guard in ChunkUtil and paints over the inherited colour.
// TEST-MIRROR: ../../../Hellion Build test/Util/ColourUtilTests.cs
// ---------------------------------------------------------------
public static unsafe uint ArgbToRgba(uint x)
{
if (x == 0)
return 0;
// The game omits the alpha byte on some GlobalValue colours, and the
// swap below would leave alpha at 0 -- invisible text.
if (x <= 0x00FFFFFFu)
x |= 0xFF000000u;
var buf = (byte*)&x;
(buf[1], buf[2], buf[3], buf[0]) = (buf[0], buf[1], buf[2], buf[3]);
return x;
+12
View File
@@ -0,0 +1,12 @@
namespace HellionChat.Util;
// Chat surfaces share one payload handler and all render it in the same frame.
// A popup belongs to the surface that opened it; the others must leave its
// state alone instead of reading "no popup here" as "popup was closed".
internal static class PopupOwnership
{
// Reference identity on purpose -- two surfaces can compare equal without
// being the same window.
internal static bool Owns(object? owner, object? surface) =>
owner is not null && ReferenceEquals(owner, surface);
}
+6 -6
View File
@@ -165,7 +165,7 @@ internal static class TabLifecycleHelpers
// Sectioned sidebar render order (1.5.6 parity): persistent → pinned TempTabs →
// unpinned TempTabs. Returns indices into the live tab list so the list order is
// never mutated and DrawRow keeps each tab's ORIGINAL index for PushID. POP-1a:
// never mutated and DrawRow keeps each tab's ORIGINAL index for PushID.
// isPoppedOut excludes tabs bound to a pop-out window — an excluded tab draws no
// row and its pool's section header gates on the first tab actually reached. Pure
// + Dalamud-free so the Build-Suite can pin it.
@@ -215,7 +215,7 @@ internal static class TabLifecycleHelpers
return persistent;
}
// POP-1c: returns the tab the main window should display — the current tab if it
// Returns the tab the main window should display — the current tab if it
// is not popped out, else the FIRST non-popped tab in list order, else null when
// every tab is popped. NOTE: deliberately NOT ResetActiveTabIfRemoved's
// unconditional Tabs[0] — Tabs[0] may itself be popped and would re-trigger the
@@ -243,7 +243,7 @@ internal static class TabLifecycleHelpers
Popout,
}
// POP-1f: the alreadyPopped case is the whole reason this is a function.
// The alreadyPopped case is the whole reason this is a function.
//
// Revealing a popped-out tab in the main window looks like it does nothing,
// and then does something worse: PickMainActiveTab re-anchors on the next
@@ -268,12 +268,12 @@ internal static class TabLifecycleHelpers
_ => switchAlways && !alreadyPopped ? TellReveal.MainWindow : TellReveal.None,
};
// POP-1e: popout-aware sibling of WrapTabIndex for the ChatTabForward/Backward
// Popout-aware sibling of WrapTabIndex for the ChatTabForward/Backward
// keybind. Steps from current by delta's sign (±1), wrapping, and returns the
// first index whose tab is NOT popped out within tabs.Count steps; returns current
// when every other tab is popped (no-op), the list is empty, or a single tab.
// Skipping popped tabs here is what stops the POP-1c re-anchor from making the
// cycle stick (CYCLE-1). Pure + Dalamud-free.
// Skipping popped tabs here is what stops the PickMainActiveTab re-anchor
// from making the cycle stick. Pure + Dalamud-free.
// TEST-MIRROR: ../../../Hellion Build test/_Helpers/NextMainTabIndexTests.cs
internal static int NextMainTabIndex(
int current,
+2 -2
View File
@@ -2,7 +2,7 @@
[![Build](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml/badge.svg?branch=main)](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](LICENSE)
[![Latest release](https://img.shields.io/badge/release-v2.0.2-brightgreen)](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
[![Latest release](https://img.shields.io/badge/release-v2.0.3-brightgreen)](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
[![Dalamud API](https://img.shields.io/badge/Dalamud-API_15-purple)](https://github.com/goatcorp/Dalamud)
[![.NET](https://img.shields.io/badge/.NET-10.0-512BD4)](https://dotnet.microsoft.com/)
[![FFXIV](https://img.shields.io/badge/FFXIV-Dawntrail-c3a37f)](https://www.finalfantasyxiv.com/)
@@ -11,7 +11,7 @@
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
</p>
**Version 2.0.2** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, originally
**Version 2.0.3** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, originally
forked from [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
Hellion Chat is a privacy-first chat plugin that began as a fork of Chat 2. The core it grew from
+20
View File
@@ -11,6 +11,26 @@ releases as an overview and links to the release pages for details.
---
## [2.0.3] — 2026-08-20
### Fixed
- The context menu never opened in a pop-out window. Right-clicking a name or an
item there did nothing at all. One payload handler is shared by the main
window, every pop-out and the input preview, and it carries a single popup
state. The main window draws first, found no open popup in its own scope, read
that as closed and cleared the state before the pop-out had drawn. A popup now
belongs to the surface that opened it and the others leave it alone.
- Screenshot mode could not be turned off. The setting is persisted, but neither
of its two toggles saved the config, so turning it on only stuck when an
unrelated save happened to follow — and once stored, turning it off never
reached the file, so it returned at every plugin load. Both toggles save now;
an install currently stuck on it needs one click.
- Text the game colours itself could render invisible. Some of those colours
arrive without an alpha byte, and the byte order swap left them fully
transparent. A colour that carries no value at all still resolves to the
colour already in effect rather than to opaque black.
## [2.0.2] — 2026-08-19
### Removed
+6 -6
View File
@@ -3,7 +3,7 @@
"Author": "Jon Kazama (Hellion Forge)",
"Name": "Hellion Chat",
"InternalName": "HellionChat",
"AssemblyVersion": "2.0.2.0",
"AssemblyVersion": "2.0.3.0",
"Description": "A Hellion Forge plugin — privacy-focused chat replacement for FINAL FANTASY XIV, built for EU, US and JP data rules.\n\nBy default only your own conversations are stored. Public chat, NPC dialogue, system messages and battle logs are discarded at the storage layer unless you opt in. Retention windows are configurable per channel, history can be wiped retroactively, and everything can be exported on demand.\n\nFeatures:\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with preview and Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with four profiles: Privacy-First, Casual, Roleplay, Full History\n- Multi-language UI (24 locales) with live language switching\n- Own config and database — no shared state with other plugins\n\nBased on Chat 2 by Infi and Anna (EUPL-1.2).\nSupport: https://discord.gg/X9V7Kcv5gR",
"ApplicableVersion": "any",
"RepoUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat",
@@ -20,12 +20,12 @@
"CanUnloadAsync": false,
"LoadPriority": 0,
"Punchline": "A Hellion Forge plugin. Privacy-first chat for FFXIV, built to stay out of your way.",
"Changelog": "**v2.0.2 — Emotes out, placeholders fixed (2026-08-19)**\n\n- **BetterTTV emote support is gone.** Its shared-emote endpoint went behind authentication, and that was where nearly all of them came from — what remained was a 65-entry global set, eleven of which are on the known-broken list, so 54 mostly static images from Twitch's early days. Exactly one was animated, and that one wanted 492 frames and 37 MB of video memory. The plugin now makes no outbound network calls at all. Messages stored with emotes still read fine; they show the code that was typed.\n- **Five settings descriptions printed `{0}` instead of the plugin name.** All 25 languages were affected, English included — it just hid better there, since \"Hide {0} during cutscenes\" still scans as a sentence while German puts the placeholder first. A test now walks every resource string with a placeholder and fails if one reaches a widget unformatted.\n- **New preview images.** The ones in the plugin installer were from 8 May and showed the interface as it looked before any of the window rebuilds.",
"Changelog": "**v2.0.3 \u2014 Pop-out context menu, screenshot mode (2026-08-20)**\n\n- **The context menu never opened in a pop-out window.** Right-clicking a name or an item there did nothing at all. Every chat surface shares one popup state, and whichever window drew first cleared the menu it had not opened before the pop-out got its turn. The menu now stays with the window the click came from.\n- **Screenshot mode could not be turned off.** It is a saved setting, but neither of its two toggles wrote the change to the config file, so switching it off never survived a reload and the mode came back on at every plugin load. Both toggles save now. If it is stuck on for you, one click off is enough.\n- **Text the game colours itself could render invisible.** Some of those colours arrive without an alpha byte, and the byte order swap then left them fully transparent.\n\n---\n\n**v2.0.2 — Emotes out, placeholders fixed (2026-08-19)**\n\n- **BetterTTV emote support is gone.** Its shared-emote endpoint went behind authentication, and that was where nearly all of them came from — what remained was a 65-entry global set, eleven of which are on the known-broken list, so 54 mostly static images from Twitch's early days. Exactly one was animated, and that one wanted 492 frames and 37 MB of video memory. The plugin now makes no outbound network calls at all. Messages stored with emotes still read fine; they show the code that was typed.\n- **Five settings descriptions printed `{0}` instead of the plugin name.** All 25 languages were affected, English included — it just hid better there, since \"Hide {0} during cutscenes\" still scans as a sentence while German puts the placeholder first. A test now walks every resource string with a placeholder and fails if one reaches a widget unformatted.\n- **New preview images.** The ones in the plugin installer were from 8 May and showed the interface as it looked before any of the window rebuilds.",
"AcceptsFeedback": true,
"DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.2/latest.zip",
"DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.2/latest.zip",
"DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.2/latest.zip",
"TestingAssemblyVersion": "2.0.2.0",
"DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.3/latest.zip",
"DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.3/latest.zip",
"DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.3/latest.zip",
"TestingAssemblyVersion": "2.0.3.0",
"IconUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png",
"ImageUrls": [
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/chatWindow.png",