diff --git a/HellionChat/Configuration.cs b/HellionChat/Configuration.cs index 7d33dda..f1f9c85 100755 --- a/HellionChat/Configuration.cs +++ b/HellionChat/Configuration.cs @@ -145,6 +145,7 @@ public class Configuration : IPluginConfiguration public bool HideWhenUiHidden = true; public bool HideInLoadingScreens; public bool HideInBattle; + public bool HideInNewGamePlusMenu; public bool HideWhenInactive; public int InactivityHideTimeout = 10; public bool InactivityHideActiveDuringBattle = true; @@ -221,6 +222,7 @@ public class Configuration : IPluginConfiguration public float TooltipOffset; public float WindowAlpha = 100f; public Dictionary ChatColours = new(); + public bool ColorSelectedInputChannelButton = true; public List Tabs = []; public bool OverrideStyle; @@ -241,6 +243,7 @@ public class Configuration : IPluginConfiguration HideWhenUiHidden = other.HideWhenUiHidden; HideInLoadingScreens = other.HideInLoadingScreens; HideInBattle = other.HideInBattle; + HideInNewGamePlusMenu = other.HideInNewGamePlusMenu; HideWhenInactive = other.HideWhenInactive; InactivityHideTimeout = other.InactivityHideTimeout; InactivityHideActiveDuringBattle = other.InactivityHideActiveDuringBattle; @@ -288,6 +291,7 @@ public class Configuration : IPluginConfiguration TooltipOffset = other.TooltipOffset; WindowAlpha = other.WindowAlpha; ChatColours = other.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value); + ColorSelectedInputChannelButton = other.ColorSelectedInputChannelButton; // Hellion Chat — Auto-Tell-Tabs are session-only and therefore // never present in a disk-loaded copy. Keep the live temp tabs of diff --git a/HellionChat/GameFunctions/GameFunctions.cs b/HellionChat/GameFunctions/GameFunctions.cs index 31b8970..c05b8f7 100755 --- a/HellionChat/GameFunctions/GameFunctions.cs +++ b/HellionChat/GameFunctions/GameFunctions.cs @@ -20,6 +20,8 @@ namespace HellionChat.GameFunctions; internal unsafe class GameFunctions : IDisposable { + internal const string NewGamePlusAddonName = "QuestRedo"; + #region Hooks [Signature("E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B D0 49 8D 4F", DetourName = nameof(ResolveTextCommandPlaceholderDetour))] private Hook? ResolveTextCommandPlaceholderHook = null!; diff --git a/HellionChat/HellionChat.csproj b/HellionChat/HellionChat.csproj index 8251761..dc7f418 100644 --- a/HellionChat/HellionChat.csproj +++ b/HellionChat/HellionChat.csproj @@ -4,7 +4,7 @@ 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 derives from. --> - 1.0.1 + 1.0.2 enable diff --git a/HellionChat/HellionChat.yaml b/HellionChat/HellionChat.yaml index bbfffc0..a8b26a9 100755 --- a/HellionChat/HellionChat.yaml +++ b/HellionChat/HellionChat.yaml @@ -49,6 +49,24 @@ tags: - Replacement - Privacy changelog: |- + **Hellion Chat 1.0.2 — Polish patch** + + - New: optionally hide chat (and every other plugin window) while the + New Game+ menu is open. Toggle in Settings → Window → Frame, default + off. Closing the menu restores all windows. + - New: optionally tint the channel selector button next to the input + field with the currently active channel's colour. Toggle in + Settings → Appearance → Colours, default on. Matches the existing + input-text tint and respects ExtraChat overrides. + - Fix: status, item and other inline hover icons keep their original + aspect ratio. Debuff icons with non-square dimensions are no longer + visually squished into a 32×32 box. + - Diagnostic: hide-state transitions (battle, cutscene, user-hide, + cutscene override) are now logged on Verbose level for easier bug + reports — off by default, enable with `/xllog set HellionChat verbose`. + + Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). + **Hellion Chat 1.0.1 — Window Position Recovery** - Automatic bounds check on the first draw after plugin load. @@ -214,76 +232,6 @@ changelog: |- Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). - **Hellion Chat 0.6.0 — UX Polish: Pop-Out Input + Colour Presets** - - Two opt-in UX features land in the same release. Existing users see - no change unless they enable the new toggles. - - Pop-out input bar: - - - New global master switch in Settings → Window → Frame: "Enable input - in pop-outs". Default OFF so existing behaviour is preserved - - When enabled, every pop-out window grows a compact input bar at the - bottom (channel-coloured icon button left, text input right). The - auto-translate picker is intentionally not part of the compact bar - in v0.6.0 — typical pop-out workflows (FC greeter, club hostess) - rarely need it there - - Each pop-out keeps an independent text buffer and history cursor; - channel changes still apply globally because that is how the FFXIV - channel API works - - Up/Down navigates a shared input history singleton across the main - window and every open pop-out - - First pop-out opening after the upgrade shows a one-time hint - banner pointing users to the new toggle - - Chat colour presets: - - - Seven built-in presets above the per-channel colour list in - Settings → Appearance → Colours: ChatTwo Default, High-Contrast, - Pastell, Dark-Mode-Tuned, Hellion (brand-coloured, blue/orange - Arctic Cyan + Ember Glow palette from the Hellion Online Media - branding spec), plus two bonus mood presets — Night Blue (royal - blue, classic-cool) and Indigo Violet (royal violet, glitter-mystic) - - Apply is immediate and overwrites the channels covered by the - preset; battle-channel colours are left alone so combat tuning - stays intact - - Configuration migrates from v10 to v11 with a diagnostic log entry; - no data is reset. Bilingual (English/German) for both new sections. - - Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). - - **Hellion Chat 0.5.4 — WrapText hardening** - - Replaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with - Span- and index-based control flow. Closes the persistent CodeQL - Critical alert "unvalidated local pointer arithmetic" that kept - re-firing on every shape of the previous fix. - - Hardening: - - - WrapText now allocates a buffer sized by Encoding.UTF8.GetMaxByteCount - via ArrayPool, validates the actual encoded length against that - ceiling, and threads the rest of the algorithm through int offsets - instead of raw byte pointers - - Pointer arithmetic only happens inside two small private helpers - (CalcWordWrap and DrawText) that take the pinned base pointer plus - int offsets sourced from the plugin's own logic, not from any - virtual-method return - - Added a 16 KiB upper bound on the buffer rent to prevent a - pathological input from triggering an unbounded ArrayPool allocation - - No user-visible behaviour change. Word-wrap output is byte-identical - to v0.5.3. - - Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). - - **Hellion Chat 0.5.3 — Pointer arithmetic hardening** - - Closed CodeQL Critical alert in ImGuiUtil.WrapText by validating the - encoded byte buffer length via GetByteCount before pointer - arithmetic. Single-fix patch on top of v0.5.2. - --- Earlier history: https://github.com/JonKazama-Hellion/HellionChat/releases diff --git a/HellionChat/PayloadHandler.cs b/HellionChat/PayloadHandler.cs index 380c09a..83d430c 100755 --- a/HellionChat/PayloadHandler.cs +++ b/HellionChat/PayloadHandler.cs @@ -332,10 +332,19 @@ public sealed class PayloadHandler atkBase->SetPosition((short) x, (short) y); } + private const float MaxInlineIconSize = 32f; + private static void InlineIcon(IDalamudTextureWrap icon) { + if (icon.Size.X <= 0 || icon.Size.Y <= 0) + return; + + var width = (float) icon.Size.X; + var height = (float) icon.Size.Y; + var scale = Math.Min(1f, Math.Min(MaxInlineIconSize / width, MaxInlineIconSize / height)); + var size = ImGuiHelpers.ScaledVector2(width * scale, height * scale); + var cursor = ImGui.GetCursorPos(); - var size = ImGuiHelpers.ScaledVector2(32, 32); ImGui.Image(icon.Handle, size); ImGui.SameLine(); ImGui.SetCursorPos(cursor + new Vector2(size.X + 4, size.Y - ImGui.GetTextLineHeightWithSpacing())); diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index 4bc24d8..62052c9 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -571,6 +571,16 @@ public sealed class Plugin : IDalamudPlugin return; } + // v1.0.2 — global skip while the New Game+ menu (QuestRedo addon) is + // open. Hides every plugin window in one shot (chat log, pop-outs, + // settings, db viewer, etc.), matching the LoadingScreens pattern. + if (Config.HideInNewGamePlusMenu && GameFunctions.GameFunctions.IsAddonInteractable(GameFunctions.GameFunctions.NewGamePlusAddonName)) + { + ChatLogWindow.FinalizeFrame(); + TypingIpc.Update(); + return; + } + ChatLogWindow.HideStateCheck(); Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden; diff --git a/HellionChat/Resources/Language.Designer.cs b/HellionChat/Resources/Language.Designer.cs index e40d551..bfc59e3 100755 --- a/HellionChat/Resources/Language.Designer.cs +++ b/HellionChat/Resources/Language.Designer.cs @@ -2148,6 +2148,24 @@ namespace HellionChat.Resources { } } + /// + /// Looks up a localized string similar to The channel selector button next to the input field is tinted with the currently active channel's colour. Matches the tinting of the input text itself.. + /// + internal static string Options_ColorSelectedInputChannelButton_Description { + get { + return ResourceManager.GetString("Options_ColorSelectedInputChannelButton_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tint channel selector with channel colour. + /// + internal static string Options_ColorSelectedInputChannelButton_Name { + get { + return ResourceManager.GetString("Options_ColorSelectedInputChannelButton_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Chat colours. /// @@ -2660,7 +2678,25 @@ namespace HellionChat.Resources { return ResourceManager.GetString("Options_HideInBattle_Name", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Hide the chat while the New Game+ menu is open. Closing the menu shows the chat again.. + /// + internal static string Options_HideInNewGamePlusMenu_Description { + get { + return ResourceManager.GetString("Options_HideInNewGamePlusMenu_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hide while New Game+ menu is open. + /// + internal static string Options_HideInNewGamePlusMenu_Name { + get { + return ResourceManager.GetString("Options_HideInNewGamePlusMenu_Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Hide {0} during loading screens.. /// diff --git a/HellionChat/Resources/Language.de.resx b/HellionChat/Resources/Language.de.resx index 29cae9a..a3fbe20 100644 --- a/HellionChat/Resources/Language.de.resx +++ b/HellionChat/Resources/Language.de.resx @@ -208,6 +208,12 @@ Vom Spiel importieren + + Channel-Auswahl-Knopf in Channel-Farbe + + + Der Channel-Auswahl-Knopf neben dem Eingabefeld bekommt die Farbe des aktuell aktiven Channels. Konsistent zur Färbung des Eingabetextes selbst. + Kanäle @@ -1190,6 +1196,12 @@ Sie wurden gewarnt. Blende den Chat während der Kämpfe aus. + + Während des New-Game+ Menüs ausblenden + + + Blendet den Chat aus, solange das New-Game+ Menü geöffnet ist. Schließen des Menüs blendet den Chat wieder ein. + Emote-Statistik diff --git a/HellionChat/Resources/Language.resx b/HellionChat/Resources/Language.resx index c3a3f19..46512ce 100644 --- a/HellionChat/Resources/Language.resx +++ b/HellionChat/Resources/Language.resx @@ -208,6 +208,12 @@ Import from game + + Tint channel selector with channel colour + + + The channel selector button next to the input field is tinted with the currently active channel's colour. Matches the tinting of the input text itself. + Tabs @@ -1189,6 +1195,12 @@ Hide the chat during battles. + + Hide while New Game+ menu is open + + + Hide the chat while the New Game+ menu is open. Closing the menu shows the chat again. + Emote Stats diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs index 64d2db3..ebeb8dc 100644 --- a/HellionChat/Ui/ChatLogWindow.cs +++ b/HellionChat/Ui/ChatLogWindow.cs @@ -278,9 +278,11 @@ public sealed class ChatLogWindow : Window { case "hide": CurrentHideState = HideState.User; + Plugin.Log.Verbose("HideState: → User (chat hide command)"); break; case "show": CurrentHideState = HideState.None; + Plugin.Log.Verbose("HideState: → None (chat show command)"); break; case "toggle": CurrentHideState = CurrentHideState switch @@ -290,6 +292,7 @@ public sealed class ChatLogWindow : Window HideState.None => HideState.User, _ => CurrentHideState, }; + Plugin.Log.Verbose($"HideState: → {CurrentHideState} (chat toggle command)"); break; } } @@ -406,30 +409,48 @@ public sealed class ChatLogWindow : Window { // if the chat has no hide state set, and the player has entered battle, we hide chat if they have configured it if (Plugin.Config.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle) + { CurrentHideState = HideState.Battle; + Plugin.Log.Verbose("HideState: None → Battle"); + } // If the chat is hidden because of battle, we reset it here if (CurrentHideState is HideState.Battle && !Plugin.InBattle) + { CurrentHideState = HideState.None; + Plugin.Log.Verbose("HideState: Battle → None"); + } // if the chat has no hide state and in a cutscene, set the hide state to cutscene if (Plugin.Config.HideDuringCutscenes && CurrentHideState == HideState.None && (Plugin.CutsceneActive || Plugin.GposeActive)) { if (Plugin.Functions.Chat.CheckHideFlags()) + { CurrentHideState = HideState.Cutscene; + Plugin.Log.Verbose("HideState: None → Cutscene"); + } } // if the chat is hidden because of a cutscene and no longer in a cutscene, set the hide state to none if (CurrentHideState is HideState.Cutscene or HideState.CutsceneOverride && !Plugin.CutsceneActive && !Plugin.GposeActive) + { + Plugin.Log.Verbose($"HideState: {CurrentHideState} → None (cutscene/gpose ended)"); CurrentHideState = HideState.None; + } // if the chat is hidden because of a cutscene and the chat has been activated, show chat if (CurrentHideState == HideState.Cutscene && Activate) + { CurrentHideState = HideState.CutsceneOverride; + Plugin.Log.Verbose("HideState: Cutscene → CutsceneOverride (user activate)"); + } // if the user hid the chat and is now activating chat, reset the hide state if (CurrentHideState == HideState.User && Activate) + { CurrentHideState = HideState.None; + Plugin.Log.Verbose("HideState: User → None (activate)"); + } if (CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle || (Plugin.Config.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn)) { @@ -600,9 +621,40 @@ public sealed class ChatLogWindow : Window DrawChannelName(activeTab); } + // v1.0.2 — compute inputColour up front so the channel selector button + // can also tint with it (existing input-text push remains below). + var inputType = activeTab.CurrentChannel.UseTempChannel ? activeTab.CurrentChannel.TempChannel.ToChatType() : activeTab.CurrentChannel.Channel.ToChatType(); + var isCommand = Chat.Trim().StartsWith('/'); + if (isCommand) + { + var command = Chat.Split(' ')[0]; + if (TextCommandChannels.TryGetValue(command, out var channel)) + inputType = channel; + + if (!IsValidCommand(command)) + inputType = ChatType.Error; + } + + var inputColour = Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol) ? inputCol : inputType.DefaultColor(); + + if (!isCommand && Plugin.ExtraChat.ChannelOverride is var (_, overrideColour)) + inputColour = overrideColour; + + if (isCommand && Plugin.ExtraChat.ChannelCommandColours.TryGetValue(Chat.Split(' ')[0], out var ecColour)) + inputColour = ecColour; + var beforeIcon = ImGui.GetCursorPos(); - if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment) && activeTab.Channel is null) - ImGui.OpenPopup(ChatChannelPicker); + + var tintSelector = Plugin.Config.ColorSelectedInputChannelButton && inputColour.HasValue; + var selectorAbgr = tintSelector ? ColourUtil.RgbaToAbgr(inputColour!.Value) : 0u; + + using (ImRaii.PushColor(ImGuiCol.Button, selectorAbgr, tintSelector)) + using (ImRaii.PushColor(ImGuiCol.ButtonHovered, ColourUtil.AdjustBrightness(selectorAbgr, 1.15f), tintSelector)) + using (ImRaii.PushColor(ImGuiCol.ButtonActive, ColourUtil.AdjustBrightness(selectorAbgr, 0.85f), tintSelector)) + { + if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment) && activeTab.Channel is null) + ImGui.OpenPopup(ChatChannelPicker); + } if (activeTab.Channel is not null && ImGui.IsItemHovered()) ImGuiUtil.Tooltip(Language.ChatLog_SwitcherDisabled); @@ -626,27 +678,7 @@ public sealed class ChatLogWindow : Window var buttonsRight = (showNovice ? 1 : 0) + (Plugin.Config.ShowHideButton ? 1 : 0); var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth * (1 + buttonsRight); - var inputType = activeTab.CurrentChannel.UseTempChannel ? activeTab.CurrentChannel.TempChannel.ToChatType() : activeTab.CurrentChannel.Channel.ToChatType(); - var isCommand = Chat.Trim().StartsWith('/'); - if (isCommand) - { - var command = Chat.Split(' ')[0]; - if (TextCommandChannels.TryGetValue(command, out var channel)) - inputType = channel; - - if (!IsValidCommand(command)) - inputType = ChatType.Error; - } - var normalColor = ImGui.GetColorU32(ImGuiCol.Text); - var inputColour = Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol) ? inputCol : inputType.DefaultColor(); - - if (!isCommand && Plugin.ExtraChat.ChannelOverride is var (_, overrideColour)) - inputColour = overrideColour; - - if (isCommand && Plugin.ExtraChat.ChannelCommandColours.TryGetValue(Chat.Split(' ')[0], out var ecColour)) - inputColour = ecColour; - var push = inputColour != null; using (ImRaii.PushColor(ImGuiCol.Text, push ? ColourUtil.RgbaToAbgr(inputColour!.Value) : 0, push)) { diff --git a/HellionChat/Ui/Popout.cs b/HellionChat/Ui/Popout.cs index a2f34f8..01f3c5d 100644 --- a/HellionChat/Ui/Popout.cs +++ b/HellionChat/Ui/Popout.cs @@ -229,30 +229,48 @@ internal class Popout : Window { // if the chat has no hide state set, and the player has entered battle, we hide chat if they have configured it if (Tab.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle) + { CurrentHideState = HideState.Battle; + Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: None → Battle"); + } // If the chat is hidden because of battle, we reset it here if (CurrentHideState is HideState.Battle && !Plugin.InBattle) + { CurrentHideState = HideState.None; + Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Battle → None"); + } // if the chat has no hide state and in a cutscene, set the hide state to cutscene if (Tab.HideDuringCutscenes && CurrentHideState == HideState.None && (Plugin.CutsceneActive || Plugin.GposeActive)) { if (ChatLogWindow.Plugin.Functions.Chat.CheckHideFlags()) + { CurrentHideState = HideState.Cutscene; + Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: None → Cutscene"); + } } // if the chat is hidden because of a cutscene and no longer in a cutscene, set the hide state to none if (CurrentHideState is HideState.Cutscene or HideState.CutsceneOverride && !Plugin.CutsceneActive && !Plugin.GposeActive) + { + Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: {CurrentHideState} → None (cutscene/gpose ended)"); CurrentHideState = HideState.None; + } // if the chat is hidden because of a cutscene and the chat has been activated, show chat if (CurrentHideState == HideState.Cutscene && ChatLogWindow.Activate) + { CurrentHideState = HideState.CutsceneOverride; + Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Cutscene → CutsceneOverride (user activate)"); + } // if the user hid the chat and is now activating chat, reset the hide state if (CurrentHideState == HideState.User && ChatLogWindow.Activate) + { CurrentHideState = HideState.None; + Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: User → None (activate)"); + } return CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle || (Tab.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn); } diff --git a/HellionChat/Ui/SettingsTabs/Appearance.cs b/HellionChat/Ui/SettingsTabs/Appearance.cs index 63eabaf..6517f74 100644 --- a/HellionChat/Ui/SettingsTabs/Appearance.cs +++ b/HellionChat/Ui/SettingsTabs/Appearance.cs @@ -236,6 +236,10 @@ internal sealed class Appearance : ISettingsTab ImGui.Separator(); ImGui.Spacing(); + ImGui.Checkbox(Language.Options_ColorSelectedInputChannelButton_Name, ref Mutable.ColorSelectedInputChannelButton); + ImGuiUtil.HelpMarker(Language.Options_ColorSelectedInputChannelButton_Description); + ImGui.Spacing(); + foreach (var (_, types) in ChatTypeExt.SortOrder) { foreach (var type in types) diff --git a/HellionChat/Ui/SettingsTabs/Window.cs b/HellionChat/Ui/SettingsTabs/Window.cs index 5a7dfdc..6372581 100644 --- a/HellionChat/Ui/SettingsTabs/Window.cs +++ b/HellionChat/Ui/SettingsTabs/Window.cs @@ -56,6 +56,9 @@ internal sealed class Window : ISettingsTab ImGui.Checkbox(Language.Options_HideInBattle_Name, ref Mutable.HideInBattle); ImGuiUtil.HelpMarker(Language.Options_HideInBattle_Description); + + ImGui.Checkbox(Language.Options_HideInNewGamePlusMenu_Name, ref Mutable.HideInNewGamePlusMenu); + ImGuiUtil.HelpMarker(Language.Options_HideInNewGamePlusMenu_Description); } } diff --git a/HellionChat/Util/ColourUtil.cs b/HellionChat/Util/ColourUtil.cs index d74e2f4..63214f0 100755 --- a/HellionChat/Util/ColourUtil.cs +++ b/HellionChat/Util/ColourUtil.cs @@ -48,4 +48,18 @@ internal static class ColourUtil { internal static uint ComponentsToRgba(byte red, byte green, byte blue, byte alpha = 0xFF) => alpha | (uint) (red << 24) | (uint) (green << 16) | (uint) (blue << 8); + + internal static uint AdjustBrightness(uint abgr, float factor) + { + var a = (byte) ((abgr & 0xFF000000) >> 24); + var b = (byte) ((abgr & 0x00FF0000) >> 16); + var g = (byte) ((abgr & 0x0000FF00) >> 8); + var r = (byte) (abgr & 0x000000FF); + + var nr = (byte) Math.Clamp(r * factor, 0f, 255f); + var ng = (byte) Math.Clamp(g * factor, 0f, 255f); + var nb = (byte) Math.Clamp(b * factor, 0f, 255f); + + return ((uint) a << 24) | ((uint) nb << 16) | ((uint) ng << 8) | nr; + } } diff --git a/README.md b/README.md index 8a6cb2b..95e121b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![.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/) -**Version 1.0.0** — DSGVO-bewusster Chat-Ersatz für FINAL FANTASY XIV / Dalamud, basierend auf [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2). +**Version 1.0.2** — DSGVO-bewusster Chat-Ersatz für FINAL FANTASY XIV / Dalamud, basierend auf [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2). Hellion Chat ergänzt das ursprüngliche Chat-2-Fundament um Datenschutz- und Daten-Handling-Kontrollen, die mit den Datenschutz-Regeln in der EU, den USA und Japan im Einklang sind. Alle aus Chat 2 übernommenen Funktionen, Befehle und Tastenkürzel funktionieren unverändert. Eigenständiger Plugin-Slot, eigene Konfiguration, eigene Datenbank. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0f05605..15b37b7 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,33 @@ und verlinkt für Details auf die Release-Pages. --- +## [1.0.2] — 2026-05-04 — Polish patch + +Vier kleine Polish-Items aus dem Backlog gebündelt: + +- **Hide bei New Game+ Menü**: Optionaler globaler Toggle der Hellion + Chat (und alle weiteren Plugin-Fenster wie Settings, DB-Viewer, + Pop-Outs) ausblendet, solange das NG+-Menü offen ist. Settings → + Fenster → Rahmen, Default aus. Skipt analog zum bestehenden + LoadingScreens-Pattern den gesamten `WindowSystem.Draw()`-Pfad. +- **Channel-Selector-Färbung**: Optionales Tinting des + Channel-Auswahl-Knopfs (Comment-Icon) neben dem Eingabefeld in der + aktuellen Channel-Farbe. Settings → Aussehen → Chat-Farben, Default + an. Konsistent zur bestehenden Eingabetext-Färbung, ExtraChat-Override + wird übernommen. +- **(De)Buff-Icon Aspect-Ratio-Fix**: `PayloadHandler.InlineIcon` quetschte + alle Hover-Icons auf 32×32. Status-Icons mit nicht-quadratischen + Dimensionen (Debuffs mit Pfeil-Indikator) sind jetzt aspekt-erhaltend + geshrinkt. Eigenständige Float-Math-Implementierung mit Zero-Size-Guard + statt Cherry-Pick aus dem offenen ChatTwo PR #157 (der hatte eine + int-Division-Falle). +- **HideState-Logging-Sweep**: Alle HideState-Transitions + (Battle/Cutscene/User/Override plus die Pop-Out-Spiegelung) loggen sich + auf Verbose-Level. Aus by default, Aktivierung via + `/xllog set HellionChat verbose` für Bug-Report-Diagnose. + +[Release-Notes 1.0.2](https://github.com/JonKazama-Hellion/HellionChat/releases/tag/v1.0.2) + ## [1.0.1] — 2026-05-04 — Window Position Recovery Fixes an off-screen-window scenario the user could end up in after a diff --git a/repo.json b/repo.json index 0f1b4de..7f5ed56 100644 --- a/repo.json +++ b/repo.json @@ -3,7 +3,7 @@ "Author": "JonKazama-Hellion", "Name": "Hellion Chat", "InternalName": "HellionChat", - "AssemblyVersion": "1.0.1.0", + "AssemblyVersion": "1.0.2.0", "Description": "Hellion Chat is a privacy-focused chat replacement for FINAL FANTASY XIV based on the Chat 2 codebase (EUPL-1.2). One feature is intentionally removed (the optional webinterface) and a stack of privacy controls is added on top. Tabs, channel filters, RGB colours, emotes, screenshot mode, IPC integration and the chat replacement window itself work the same. The webinterface is intentionally not part of Hellion Chat because it serves a different use case from the smaller default footprint this plugin is built around.\n\nOn top of that, Hellion Chat adds privacy and data-handling controls designed to align with the modern data protection rules that apply across the EU, the United States and Japan. By default only your own conversations are stored; messages from strangers, NPCs and system spam stay out of the database. Retention windows are configurable per channel, history can be wiped retroactively, and stored data can be exported on demand.\n\nKey privacy and data-handling features:\n\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with a Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with three preset profiles (Privacy-First, Casual, Full History)\n- Bilingual UI (English and German) with live language switching\n- Independent plugin state — own config file and database directory, so Hellion Chat does not share state with upstream Chat 2\n\nBased on Chat 2 by Infi and Anna, licensed under EUPL-1.2.\n\nModding & support: join the Hellion Forge Discord at https://discord.gg/X9V7Kcv5gR — community for Hellion Chat and other Hellion Online Media plugins/tools.", "ApplicableVersion": "any", "RepoUrl": "https://github.com/JonKazama-Hellion/HellionChat", @@ -20,12 +20,12 @@ "CanUnloadAsync": false, "LoadPriority": 0, "Punchline": "Chat replacement with privacy controls aligned to EU, US and JP rules — based on Chat 2 (EUPL-1.2)", - "Changelog": "**Hellion Chat 1.0.1 — Window Position Recovery**\n\n- Automatic bounds check on the first draw after plugin load. When the persisted window position has no overlap with the primary viewport, the window snaps to a safe top-left default. Helpful after a monitor disconnect, resolution change or multi-monitor layout switch between sessions.\n- New \"Reset Window Position\" button in Settings → Window → Frame as a manual escape hatch for edge cases the automatic check doesn't catch.\n\nTested on Linux/Wayland with a hard-cut three-monitor reduction; window recovers cleanly without manual JSON editing.\n\nHousekeeping carried over since v1.0.0:\n\n- Documentation restructured into docs/ folder. New CHANGELOG, CONTRIBUTORS, LEARNING-JOURNEY and ROADMAP added\n- Stale ChatTwo/* paths in repo configs updated to HellionChat/*\n- Pidgin parser library bumped from 3.3.0 to 3.5.1 (CIString Unicode fix relevant for non-ASCII channel/tab names)\n- GitHub Actions: actions/setup-dotnet bumped 4 → 5, github/codeql-action bumped 3 → 4\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 1.0.0 — Standalone Major Release**\n\nFirst fully standalone release. Internal cleanup plus a sweep of\npre-existing correctness, security, threading and resource-leak\nfixes carried over from the upstream codebase. No user action\nrequired — auto-update applies cleanly, configuration and database\npaths unchanged.\n\nStandalone identity:\n\n- Code namespace consolidated from ChatTwo.* to HellionChat.* across\n all source files\n- IPC channels migrated from ChatTwo.* to HellionChat.* (6 channels:\n Register, Available, Unregister, Invoke, GetChatInputState,\n ChatInputStateChanged) — third-party plugins that bound to the old\n channels need to be updated; none known at release time\n- ImGui popup ID renamed to hellionchat-context-popup\n- Repository folder restructured (ChatTwo/ → HellionChat/), all CI\n and build paths updated accordingly\n- Public-facing descriptions reworded from upstream-fork framing to\n standalone framing (Chat 2 attribution preserved per EUPL-1.2)\n- Colour preset 'ChatTwo Default' is now 'Klassik (Chat 2 Default)'\n\nSafety:\n\n- Plugin now refuses to load when upstream Chat 2 is also active —\n bilingual conflict message in EN/DE, throw before any subsystem\n initialization, prevents the runtime crash that previously occurred\n when both plugins replaced the same chat window in parallel\n- SQLite native binary bumped to 3.50.3 (CVE-2025-6965 memory\n corruption from aggregate-term overflow, CVE-2025-7709)\n- NuGet restore now honors packages.lock.json so transitive\n dependencies don't drift between machines or CI runs\n\nDefault tab layout sharpened (one-time tab reset on first start):\n\nThe first-run tab layout is reorganized into five thematic tabs\nbased on external tester feedback. General contains only Say,\nYell and Shout (immediate-surroundings public chat). System\nabsorbs the gameplay-event streams (NpcDialogue, Loot, Crafting,\nGathering, PF recruitment pings) and announcement noise\n(BattleSystem, FreeCompanyAnnouncement, PvpTeamAnnouncement)\nthat previously lived in General. FreeCompany, Group and\nLinkshell each own their channel set. The static Tell tab is\ngone — Auto-Tell-Tabs spawns per-conversation tabs on demand.\nThe Beginner / Novice-Network preset is no longer added by\ndefault but is still available via Settings, Tabs.\n\nThis is a one-time tab-layout reset for users on config version\n12 or older. Privacy, Retention, Theme and every other setting\nis preserved. Your previous tab configuration is written to\npluginConfigs/HellionChat.json.pre-v13-backup so you can restore\nit manually if you prefer the old layout.\n\nCrash-class fixes (formerly latent in upstream):\n\n- MathUtil.HasOverlap now uses a correct AABB test; identical or\n edge-touching rectangles are no longer reported as non-overlapping\n- ChatCode.Equals compares fields directly instead of GetHashCode;\n removes the hash-collision anti-pattern\n- IpcManager.Dispose uses UnregisterAction to match the matching\n RegisterAction call; previous mismatch leaked the action\n subscription on every plugin reload\n- ExtraChat.Dispose now unsubscribes all three IPC subscriptions\n (was only the first); leaks closed\n- TellTarget.FromTarget guards against a zero IPlayerCharacter.Address\n before dereferencing the unsafe Character* cast\n- GameFunctions ResolveTextCommandPlaceholderDetour null-checks the\n Hook reference instead of using the null-forgiving operator\n- Popout.cs and SettingsTabs/Tabs.cs bounds-check list indexing so\n a tab drop or empty-worlds list no longer crashes the UI\n- Debugger.cs now declares IDisposable so the existing Dispose runs\n\nCorrectness fixes:\n\n- GlobalParametersCache.GetValue captures Cache into a local before\n the bounds check, so a concurrent Refresh can't slip a different\n array between check and read\n- IconUtil binary search bounds initialized to entries.Length-1 and\n reset on redirect-restart; entries.Length==0 short-circuits\n- Sheets.WorldsOnDatacenter now compares DataCenter.RowId (was\n Region.RowId) so it actually returns same-DC worlds\n- Message.cs back-reference loop iterates the processed Sender/Content\n properties so chunks added by CheckMessageContent get Message set\n- Language.zh-Hans Webinterface_Start_Success corrected to\n \"网页界面已启动\" (was \"网页界面已停止\")\n\nThreading and async:\n\n- AutoTranslate Entries/ValidEntries are now serialized behind a\n single lock; the preload worker thread and main thread no longer\n race on the underlying dictionary/hash set\n- Privacy retention and cleanup workers bound their framework-refresh\n waits to 5 seconds with a logged timeout; a hung framework tick can\n no longer deadlock the background worker\n\nResource handling:\n\n- EmoteCache reuses the static HttpClient instead of allocating a new\n one per call (closed socket leak)\n- FontManager wraps HttpClient/HttpResponseMessage in using-blocks\n and adds EnsureSuccessStatusCode; failed downloads no longer\n silently produce a zero-byte font file\n- SearchSelector mixes the row index into the ImGui ID stack so\n selectables don't collapse to a single ambiguous ID\n- SettingsTabs/Chat blocked-emote add-button now opens its selector\n popup on left-click\n\nPerformance:\n\n- DbViewer text export caches filteredHistory.Count once instead of\n re-enumerating the IEnumerable on every batch (O(N) instead of\n O(N²) on large histories)\n\nLicense attribution (NOTICE.md, COPYRIGHT, THIRD_PARTY_NOTICES.md\nand the Credits section in README) is unchanged.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.6.1 — Pop-Out Discoverability & /tell Auto-Pop-Out**\n\n- Pop-out button now visible in the chat header (no more hunting through the right-click menu)\n- One-time hint banner explains pop-out tabs and the right-click shortcut\n- New setting: open new /tell tabs directly as pop-out windows (Settings → Chat → Auto-Tell-Tabs)\n- Pop-out input is now enabled by default — closing a pop-out still returns the tab to the sidebar\n- Bugfix: dropping or logging out with an LRU/popped auto-tell tab now also closes its pop-out window (no more ghost windows)\n- Bugfix: dead zone below the chat input bar when the v0.6.0 pop-out hint banner was visible (also fixed retroactively for the v0.6.0 banner inside pop-outs)\n\nModding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.6.0 — UX Polish: Pop-Out Input + Colour Presets**\n\nTwo opt-in UX features land in the same release. Existing users see\nno change unless they enable the new toggles.\n\nPop-out input bar:\n\n- New global master switch in Settings → Window → Frame: \"Enable input\n in pop-outs\". Default OFF so existing behaviour is preserved\n- When enabled, every pop-out window grows a compact input bar at the\n bottom (channel-coloured icon button left, text input right). The\n auto-translate picker is intentionally not part of the compact bar\n in v0.6.0 — typical pop-out workflows (FC greeter, club hostess)\n rarely need it there\n- Each pop-out keeps an independent text buffer and history cursor;\n channel changes still apply globally because that is how the FFXIV\n channel API works\n- Up/Down navigates a shared input history singleton across the main\n window and every open pop-out\n- First pop-out opening after the upgrade shows a one-time hint\n banner pointing users to the new toggle\n\nChat colour presets:\n\n- Seven built-in presets above the per-channel colour list in\n Settings → Appearance → Colours: ChatTwo Default, High-Contrast,\n Pastell, Dark-Mode-Tuned, Hellion (brand-coloured, blue/orange\n Arctic Cyan + Ember Glow palette from the Hellion Online Media\n branding spec), plus two bonus mood presets — Night Blue (royal\n blue, classic-cool) and Indigo Violet (royal violet, glitter-mystic)\n- Apply is immediate and overwrites the channels covered by the\n preset; battle-channel colours are left alone so combat tuning\n stays intact\n\nConfiguration migrates from v10 to v11 with a diagnostic log entry;\nno data is reset. Bilingual (English/German) for both new sections.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.5.4 — WrapText hardening**\n\nReplaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with\nSpan- and index-based control flow. Closes the persistent CodeQL\nCritical alert \"unvalidated local pointer arithmetic\" that kept\nre-firing on every shape of the previous fix.\n\nHardening:\n\n- WrapText now allocates a buffer sized by Encoding.UTF8.GetMaxByteCount\n via ArrayPool, validates the actual encoded length against that\n ceiling, and threads the rest of the algorithm through int offsets\n instead of raw byte pointers\n- Pointer arithmetic only happens inside two small private helpers\n (CalcWordWrap and DrawText) that take the pinned base pointer plus\n int offsets sourced from the plugin's own logic, not from any\n virtual-method return\n- Added a 16 KiB upper bound on the buffer rent to prevent a\n pathological input from triggering an unbounded ArrayPool allocation\n\nNo user-visible behaviour change. Word-wrap output is byte-identical\nto v0.5.3.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.5.3 — Pointer arithmetic hardening**\n\nClosed CodeQL Critical alert in ImGuiUtil.WrapText by validating the\nencoded byte buffer length via GetByteCount before pointer\narithmetic. Single-fix patch on top of v0.5.2.\n\n---\n\nEarlier history: https://github.com/JonKazama-Hellion/HellionChat/releases", + "Changelog": "**Hellion Chat 1.0.2 — Polish patch**\n\n- New: optionally hide chat (and every other plugin window) while the New Game+ menu is open. Toggle in Settings → Window → Frame, default off. Closing the menu restores all windows.\n- New: optionally tint the channel selector button next to the input field with the currently active channel's colour. Toggle in Settings → Appearance → Colours, default on. Matches the existing input-text tint and respects ExtraChat overrides.\n- Fix: status, item and other inline hover icons keep their original aspect ratio. Debuff icons with non-square dimensions are no longer visually squished into a 32×32 box.\n- Diagnostic: hide-state transitions (battle, cutscene, user-hide, cutscene override) are now logged on Verbose level for easier bug reports — off by default, enable with `/xllog set HellionChat verbose`.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 1.0.1 — Window Position Recovery**\n\n- Automatic bounds check on the first draw after plugin load. When the persisted window position has no overlap with the primary viewport, the window snaps to a safe top-left default. Helpful after a monitor disconnect, resolution change or multi-monitor layout switch between sessions.\n- New \"Reset Window Position\" button in Settings → Window → Frame as a manual escape hatch for edge cases the automatic check doesn't catch.\n\nTested on Linux/Wayland with a hard-cut three-monitor reduction; window recovers cleanly without manual JSON editing.\n\nHousekeeping carried over since v1.0.0:\n\n- Documentation restructured into docs/ folder. New CHANGELOG, CONTRIBUTORS, LEARNING-JOURNEY and ROADMAP added\n- Stale ChatTwo/* paths in repo configs updated to HellionChat/*\n- Pidgin parser library bumped from 3.3.0 to 3.5.1 (CIString Unicode fix relevant for non-ASCII channel/tab names)\n- GitHub Actions: actions/setup-dotnet bumped 4 → 5, github/codeql-action bumped 3 → 4\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 1.0.0 — Standalone Major Release**\n\nFirst fully standalone release. Internal cleanup plus a sweep of\npre-existing correctness, security, threading and resource-leak\nfixes carried over from the upstream codebase. No user action\nrequired — auto-update applies cleanly, configuration and database\npaths unchanged.\n\nStandalone identity:\n\n- Code namespace consolidated from ChatTwo.* to HellionChat.* across\n all source files\n- IPC channels migrated from ChatTwo.* to HellionChat.* (6 channels:\n Register, Available, Unregister, Invoke, GetChatInputState,\n ChatInputStateChanged) — third-party plugins that bound to the old\n channels need to be updated; none known at release time\n- ImGui popup ID renamed to hellionchat-context-popup\n- Repository folder restructured (ChatTwo/ → HellionChat/), all CI\n and build paths updated accordingly\n- Public-facing descriptions reworded from upstream-fork framing to\n standalone framing (Chat 2 attribution preserved per EUPL-1.2)\n- Colour preset 'ChatTwo Default' is now 'Klassik (Chat 2 Default)'\n\nSafety:\n\n- Plugin now refuses to load when upstream Chat 2 is also active —\n bilingual conflict message in EN/DE, throw before any subsystem\n initialization, prevents the runtime crash that previously occurred\n when both plugins replaced the same chat window in parallel\n- SQLite native binary bumped to 3.50.3 (CVE-2025-6965 memory\n corruption from aggregate-term overflow, CVE-2025-7709)\n- NuGet restore now honors packages.lock.json so transitive\n dependencies don't drift between machines or CI runs\n\nDefault tab layout sharpened (one-time tab reset on first start):\n\nThe first-run tab layout is reorganized into five thematic tabs\nbased on external tester feedback. General contains only Say,\nYell and Shout (immediate-surroundings public chat). System\nabsorbs the gameplay-event streams (NpcDialogue, Loot, Crafting,\nGathering, PF recruitment pings) and announcement noise\n(BattleSystem, FreeCompanyAnnouncement, PvpTeamAnnouncement)\nthat previously lived in General. FreeCompany, Group and\nLinkshell each own their channel set. The static Tell tab is\ngone — Auto-Tell-Tabs spawns per-conversation tabs on demand.\nThe Beginner / Novice-Network preset is no longer added by\ndefault but is still available via Settings, Tabs.\n\nThis is a one-time tab-layout reset for users on config version\n12 or older. Privacy, Retention, Theme and every other setting\nis preserved. Your previous tab configuration is written to\npluginConfigs/HellionChat.json.pre-v13-backup so you can restore\nit manually if you prefer the old layout.\n\nCrash-class fixes (formerly latent in upstream):\n\n- MathUtil.HasOverlap now uses a correct AABB test; identical or\n edge-touching rectangles are no longer reported as non-overlapping\n- ChatCode.Equals compares fields directly instead of GetHashCode;\n removes the hash-collision anti-pattern\n- IpcManager.Dispose uses UnregisterAction to match the matching\n RegisterAction call; previous mismatch leaked the action\n subscription on every plugin reload\n- ExtraChat.Dispose now unsubscribes all three IPC subscriptions\n (was only the first); leaks closed\n- TellTarget.FromTarget guards against a zero IPlayerCharacter.Address\n before dereferencing the unsafe Character* cast\n- GameFunctions ResolveTextCommandPlaceholderDetour null-checks the\n Hook reference instead of using the null-forgiving operator\n- Popout.cs and SettingsTabs/Tabs.cs bounds-check list indexing so\n a tab drop or empty-worlds list no longer crashes the UI\n- Debugger.cs now declares IDisposable so the existing Dispose runs\n\nCorrectness fixes:\n\n- GlobalParametersCache.GetValue captures Cache into a local before\n the bounds check, so a concurrent Refresh can't slip a different\n array between check and read\n- IconUtil binary search bounds initialized to entries.Length-1 and\n reset on redirect-restart; entries.Length==0 short-circuits\n- Sheets.WorldsOnDatacenter now compares DataCenter.RowId (was\n Region.RowId) so it actually returns same-DC worlds\n- Message.cs back-reference loop iterates the processed Sender/Content\n properties so chunks added by CheckMessageContent get Message set\n- Language.zh-Hans Webinterface_Start_Success corrected to\n \"网页界面已启动\" (was \"网页界面已停止\")\n\nThreading and async:\n\n- AutoTranslate Entries/ValidEntries are now serialized behind a\n single lock; the preload worker thread and main thread no longer\n race on the underlying dictionary/hash set\n- Privacy retention and cleanup workers bound their framework-refresh\n waits to 5 seconds with a logged timeout; a hung framework tick can\n no longer deadlock the background worker\n\nResource handling:\n\n- EmoteCache reuses the static HttpClient instead of allocating a new\n one per call (closed socket leak)\n- FontManager wraps HttpClient/HttpResponseMessage in using-blocks\n and adds EnsureSuccessStatusCode; failed downloads no longer\n silently produce a zero-byte font file\n- SearchSelector mixes the row index into the ImGui ID stack so\n selectables don't collapse to a single ambiguous ID\n- SettingsTabs/Chat blocked-emote add-button now opens its selector\n popup on left-click\n\nPerformance:\n\n- DbViewer text export caches filteredHistory.Count once instead of\n re-enumerating the IEnumerable on every batch (O(N) instead of\n O(N²) on large histories)\n\nLicense attribution (NOTICE.md, COPYRIGHT, THIRD_PARTY_NOTICES.md\nand the Credits section in README) is unchanged.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.6.1 — Pop-Out Discoverability & /tell Auto-Pop-Out**\n\n- Pop-out button now visible in the chat header (no more hunting through the right-click menu)\n- One-time hint banner explains pop-out tabs and the right-click shortcut\n- New setting: open new /tell tabs directly as pop-out windows (Settings → Chat → Auto-Tell-Tabs)\n- Pop-out input is now enabled by default — closing a pop-out still returns the tab to the sidebar\n- Bugfix: dropping or logging out with an LRU/popped auto-tell tab now also closes its pop-out window (no more ghost windows)\n- Bugfix: dead zone below the chat input bar when the v0.6.0 pop-out hint banner was visible (also fixed retroactively for the v0.6.0 banner inside pop-outs)\n\nModding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n---\n\nEarlier history: https://github.com/JonKazama-Hellion/HellionChat/releases", "AcceptsFeedback": true, "DownloadLinkInstall": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v1.0.1/latest.zip", "DownloadLinkUpdate": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v1.0.1/latest.zip", "DownloadLinkTesting": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v1.0.1/latest.zip", - "TestingAssemblyVersion": "1.0.1.0", + "TestingAssemblyVersion": "1.0.2.0", "IconUrl": "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/icon.png", "ImageUrls": [ "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/chatWindow.png",