diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8fdc316..ef49340 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,7 +5,7 @@ updates:
# noise down while still catching transitive security advisories within
# a few days of disclosure.
- package-ecosystem: nuget
- directory: /ChatTwo
+ directory: /HellionChat
schedule:
interval: weekly
day: monday
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index af16057..c579df5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -42,15 +42,15 @@ jobs:
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
- name: Restore
- run: dotnet restore ChatTwo/ChatTwo.csproj
+ run: dotnet restore HellionChat/HellionChat.csproj
- name: Build (Release)
- run: dotnet build ChatTwo/ChatTwo.csproj --configuration Release --no-restore
+ run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
- name: Upload build output
uses: actions/upload-artifact@v7
with:
name: HellionChat-build-${{ github.run_number }}
- path: ChatTwo/bin/Release/**/HellionChat/**
+ path: HellionChat/bin/Release/**/HellionChat/**
if-no-files-found: warn
retention-days: 14
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index c05a8e2..228d646 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -62,10 +62,10 @@ jobs:
queries: security-extended
- name: Restore
- run: dotnet restore ChatTwo/ChatTwo.csproj
+ run: dotnet restore HellionChat/HellionChat.csproj
- name: Build (Release)
- run: dotnet build ChatTwo/ChatTwo.csproj --configuration Release --no-restore
+ run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1e1cf33..e8dd56d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,7 +8,7 @@ name: Release
# - the tag name (filtered by on.tags = v*, validated again at runtime
# against ^v\d+\.\d+\.\d+$ before being used in any string)
# All other values are either repo-controlled (paths under
-# ChatTwo/bin/Release derived from Get-ChildItem) or pinned URLs to
+# HellionChat/bin/Release derived from Get-ChildItem) or pinned URLs to
# goatcorp / GitHub. Nothing from a webhook event payload (issue/PR
# titles, commit messages, etc.) flows into a run-step.
@@ -60,16 +60,16 @@ jobs:
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
- name: Build (Release)
- run: dotnet build ChatTwo/ChatTwo.csproj --configuration Release
+ run: dotnet build HellionChat/HellionChat.csproj --configuration Release
- name: Locate latest.zip
id: locate
shell: pwsh
run: |
- $zip = Get-ChildItem -Path ChatTwo\bin\Release -Recurse -Filter latest.zip | Select-Object -First 1
+ $zip = Get-ChildItem -Path HellionChat\bin\Release -Recurse -Filter latest.zip | Select-Object -First 1
if (-not $zip)
{
- throw "latest.zip not found under ChatTwo\bin\Release"
+ throw "latest.zip not found under HellionChat\bin\Release"
}
Write-Host "Found: $($zip.FullName)"
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
@@ -100,7 +100,7 @@ jobs:
}
$version = $tag.Substring(1)
- $yamlPath = "ChatTwo/HellionChat.yaml"
+ $yamlPath = "HellionChat/HellionChat.yaml"
$raw = Get-Content -Path $yamlPath -Raw
$marker = "changelog: |-"
diff --git a/ChatTwo/Properties/AssemblyInfo.cs b/ChatTwo/Properties/AssemblyInfo.cs
deleted file mode 100644
index 123e4be..0000000
--- a/ChatTwo/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1 +0,0 @@
-[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ChatTwo.Tests")]
diff --git a/ChatTwo.sln b/HellionChat.sln
similarity index 51%
rename from ChatTwo.sln
rename to HellionChat.sln
index 3f09dbc..fa2740a 100755
--- a/ChatTwo.sln
+++ b/HellionChat.sln
@@ -1,8 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatTwo", "ChatTwo\ChatTwo.csproj", "{739F75E6-B65F-41EF-9D90-F7BC519E4875}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatTwo.Tests", "ChatTwo.Tests\ChatTwo.Tests.csproj", "{A9FE423A-240C-4EDA-ACC6-21474B562128}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HellionChat", "HellionChat\HellionChat.csproj", "{739F75E6-B65F-41EF-9D90-F7BC519E4875}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -14,9 +12,5 @@ Global
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|Any CPU.Build.0 = Debug|Any CPU
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.ActiveCfg = Release|Any CPU
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.Build.0 = Release|Any CPU
- {A9FE423A-240C-4EDA-ACC6-21474B562128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A9FE423A-240C-4EDA-ACC6-21474B562128}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A9FE423A-240C-4EDA-ACC6-21474B562128}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A9FE423A-240C-4EDA-ACC6-21474B562128}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/ChatTwo/AutoTellTabsService.cs b/HellionChat/AutoTellTabsService.cs
similarity index 99%
rename from ChatTwo/AutoTellTabsService.cs
rename to HellionChat/AutoTellTabsService.cs
index 5234b0c..4d16d27 100644
--- a/ChatTwo/AutoTellTabsService.cs
+++ b/HellionChat/AutoTellTabsService.cs
@@ -1,14 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using ChatTwo.Code;
-using ChatTwo.GameFunctions.Types;
-using ChatTwo.Resources;
-using ChatTwo.Util;
+using HellionChat.Code;
+using HellionChat.GameFunctions.Types;
+using HellionChat.Resources;
+using HellionChat.Util;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
-namespace ChatTwo;
+namespace HellionChat;
// Hellion Chat — Auto-Tell-Tabs.
//
diff --git a/HellionChat/ChatTwoConflictDetector.cs b/HellionChat/ChatTwoConflictDetector.cs
new file mode 100644
index 0000000..ad53edb
--- /dev/null
+++ b/HellionChat/ChatTwoConflictDetector.cs
@@ -0,0 +1,27 @@
+using System.Linq;
+using HellionChat.Resources;
+using Dalamud.Plugin;
+
+namespace HellionChat;
+
+internal static class ChatTwoConflictDetector
+{
+ private const string UpstreamInternalName = "ChatTwo";
+
+ public static void ThrowIfChatTwoIsLoaded(IDalamudPluginInterface pluginInterface)
+ {
+ var conflict = pluginInterface.InstalledPlugins
+ .FirstOrDefault(p =>
+ p.InternalName == UpstreamInternalName &&
+ p.IsLoaded);
+
+ if (conflict is null)
+ return;
+
+ var message = HellionStrings.ChatTwoConflictTitle + "\n\n" +
+ HellionStrings.ChatTwoConflictBody + "\n\n" +
+ HellionStrings.ChatTwoConflictAction;
+
+ throw new System.InvalidOperationException(message);
+ }
+}
diff --git a/ChatTwo/Chunk.cs b/HellionChat/Chunk.cs
similarity index 98%
rename from ChatTwo/Chunk.cs
rename to HellionChat/Chunk.cs
index bfde9a7..e155ed1 100755
--- a/ChatTwo/Chunk.cs
+++ b/HellionChat/Chunk.cs
@@ -1,8 +1,8 @@
-using ChatTwo.Code;
+using HellionChat.Code;
using Dalamud.Game.Text.SeStringHandling;
using MessagePack;
-namespace ChatTwo;
+namespace HellionChat;
[Union(0, typeof(TextChunk))]
[Union(1, typeof(IconChunk))]
diff --git a/ChatTwo/Code/ChatCode.cs b/HellionChat/Code/ChatCode.cs
similarity index 96%
rename from ChatTwo/Code/ChatCode.cs
rename to HellionChat/Code/ChatCode.cs
index 94a06f0..32b717a 100755
--- a/ChatTwo/Code/ChatCode.cs
+++ b/HellionChat/Code/ChatCode.cs
@@ -1,6 +1,6 @@
using Dalamud.Game.Text;
-namespace ChatTwo.Code;
+namespace HellionChat.Code;
public class ChatCode
{
@@ -91,13 +91,10 @@ public class ChatCode
public override bool Equals(object? obj)
{
- if (obj == null)
- return false;
-
if (obj is not ChatCode code)
return false;
- return GetHashCode() == code.GetHashCode();
+ return Type == code.Type && Source == code.Source && Target == code.Target;
}
public override int GetHashCode()
diff --git a/ChatTwo/Code/ChatSource.cs b/HellionChat/Code/ChatSource.cs
similarity index 98%
rename from ChatTwo/Code/ChatSource.cs
rename to HellionChat/Code/ChatSource.cs
index d2f4fe5..73e9701 100755
--- a/ChatTwo/Code/ChatSource.cs
+++ b/HellionChat/Code/ChatSource.cs
@@ -1,6 +1,6 @@
using Dalamud.Game.Text;
-namespace ChatTwo.Code;
+namespace HellionChat.Code;
[Flags]
public enum ChatSource : ushort
diff --git a/ChatTwo/Code/ChatSourceExt.cs b/HellionChat/Code/ChatSourceExt.cs
similarity index 95%
rename from ChatTwo/Code/ChatSourceExt.cs
rename to HellionChat/Code/ChatSourceExt.cs
index eeb7908..d3fa5d3 100755
--- a/ChatTwo/Code/ChatSourceExt.cs
+++ b/HellionChat/Code/ChatSourceExt.cs
@@ -1,6 +1,6 @@
-using ChatTwo.Resources;
+using HellionChat.Resources;
-namespace ChatTwo.Code;
+namespace HellionChat.Code;
internal static class ChatSourceExt
{
diff --git a/ChatTwo/Code/ChatType.cs b/HellionChat/Code/ChatType.cs
similarity index 98%
rename from ChatTwo/Code/ChatType.cs
rename to HellionChat/Code/ChatType.cs
index 727d722..ab591a6 100755
--- a/ChatTwo/Code/ChatType.cs
+++ b/HellionChat/Code/ChatType.cs
@@ -1,4 +1,4 @@
-namespace ChatTwo.Code;
+namespace HellionChat.Code;
public enum ChatType : ushort
{
diff --git a/ChatTwo/Code/ChatTypeExt.cs b/HellionChat/Code/ChatTypeExt.cs
similarity index 99%
rename from ChatTwo/Code/ChatTypeExt.cs
rename to HellionChat/Code/ChatTypeExt.cs
index 5d54d30..962ef40 100755
--- a/ChatTwo/Code/ChatTypeExt.cs
+++ b/HellionChat/Code/ChatTypeExt.cs
@@ -1,8 +1,8 @@
-using ChatTwo.Resources;
-using ChatTwo.Util;
+using HellionChat.Resources;
+using HellionChat.Util;
using Dalamud.Game.Config;
-namespace ChatTwo.Code;
+namespace HellionChat.Code;
internal static class ChatTypeExt
{
diff --git a/ChatTwo/Code/InputChannel.cs b/HellionChat/Code/InputChannel.cs
similarity index 96%
rename from ChatTwo/Code/InputChannel.cs
rename to HellionChat/Code/InputChannel.cs
index 10e7846..c18424e 100755
--- a/ChatTwo/Code/InputChannel.cs
+++ b/HellionChat/Code/InputChannel.cs
@@ -1,4 +1,4 @@
-namespace ChatTwo.Code;
+namespace HellionChat.Code;
public enum InputChannel : uint
{
diff --git a/ChatTwo/Code/InputChannelExt.cs b/HellionChat/Code/InputChannelExt.cs
similarity index 99%
rename from ChatTwo/Code/InputChannelExt.cs
rename to HellionChat/Code/InputChannelExt.cs
index 3339c1d..6fd89e5 100755
--- a/ChatTwo/Code/InputChannelExt.cs
+++ b/HellionChat/Code/InputChannelExt.cs
@@ -1,6 +1,6 @@
using Lumina.Excel.Sheets;
-namespace ChatTwo.Code;
+namespace HellionChat.Code;
internal static class InputChannelExt
{
diff --git a/ChatTwo/Commands.cs b/HellionChat/Commands.cs
similarity index 98%
rename from ChatTwo/Commands.cs
rename to HellionChat/Commands.cs
index a3b6386..20d6aa9 100755
--- a/ChatTwo/Commands.cs
+++ b/HellionChat/Commands.cs
@@ -1,6 +1,6 @@
using Dalamud.Game.Command;
-namespace ChatTwo;
+namespace HellionChat;
internal sealed class Commands : IDisposable
{
diff --git a/ChatTwo/Configuration.cs b/HellionChat/Configuration.cs
similarity index 99%
rename from ChatTwo/Configuration.cs
rename to HellionChat/Configuration.cs
index 531d12d..7d33dda 100755
--- a/ChatTwo/Configuration.cs
+++ b/HellionChat/Configuration.cs
@@ -1,8 +1,8 @@
using System.Collections;
-using ChatTwo.Code;
-using ChatTwo.GameFunctions.Types;
-using ChatTwo.Resources;
-using ChatTwo.Util;
+using HellionChat.Code;
+using HellionChat.GameFunctions.Types;
+using HellionChat.Resources;
+using HellionChat.Util;
using Dalamud;
using Dalamud.Configuration;
using Dalamud.Game.ClientState.Keys;
@@ -10,7 +10,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Bindings.ImGui;
-namespace ChatTwo;
+namespace HellionChat;
[Serializable]
public class ConfigKeyBind
diff --git a/ChatTwo/EmoteCache.cs b/HellionChat/EmoteCache.cs
similarity index 98%
rename from ChatTwo/EmoteCache.cs
rename to HellionChat/EmoteCache.cs
index a2c58bf..f2c818c 100644
--- a/ChatTwo/EmoteCache.cs
+++ b/HellionChat/EmoteCache.cs
@@ -8,7 +8,7 @@ using Dalamud.Bindings.ImGui;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
-namespace ChatTwo;
+namespace HellionChat;
public static class EmoteCache
{
@@ -192,7 +192,7 @@ public static class EmoteCache
}
else
{
- var content = await new HttpClient().GetAsync(EmotePath.Format(emote.Id));
+ var content = await Client.GetAsync(EmotePath.Format(emote.Id));
RawData = await content.Content.ReadAsByteArrayAsync();
await using var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
diff --git a/ChatTwo/Export/MessageExporter.cs b/HellionChat/Export/MessageExporter.cs
similarity index 99%
rename from ChatTwo/Export/MessageExporter.cs
rename to HellionChat/Export/MessageExporter.cs
index 3cadaa4..d7e9ea2 100644
--- a/ChatTwo/Export/MessageExporter.cs
+++ b/HellionChat/Export/MessageExporter.cs
@@ -1,8 +1,8 @@
using System.Globalization;
using System.Text;
-using ChatTwo.Code;
+using HellionChat.Code;
-namespace ChatTwo.Export;
+namespace HellionChat.Export;
internal enum ExportFormat
{
diff --git a/ChatTwo/FontManager.cs b/HellionChat/FontManager.cs
similarity index 91%
rename from ChatTwo/FontManager.cs
rename to HellionChat/FontManager.cs
index aa3f25f..df83ef0 100644
--- a/ChatTwo/FontManager.cs
+++ b/HellionChat/FontManager.cs
@@ -6,7 +6,7 @@ using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.Utility;
using Dalamud.Bindings.ImGui;
-namespace ChatTwo;
+namespace HellionChat;
public class FontManager
{
@@ -39,11 +39,18 @@ public class FontManager
}
else
{
- GameSymFont = new HttpClient().GetAsync("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")
- .Result
- .Content
- .ReadAsByteArrayAsync()
- .Result;
+ // Dispose HttpClient and HttpResponseMessage to avoid socket
+ // exhaustion on repeated cold-start downloads. GetAwaiter().GetResult()
+ // unwraps AggregateException so failures surface cleanly. A full
+ // async refactor of the constructor would be cleaner but is out of
+ // scope for v1.0.0 — tracked in the backlog.
+ using var client = new HttpClient();
+ using var response = client
+ .GetAsync("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")
+ .GetAwaiter()
+ .GetResult();
+ response.EnsureSuccessStatusCode();
+ GameSymFont = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
Dalamud.Utility.FilesystemUtil.WriteAllBytesSafe(filePath, GameSymFont);
}
diff --git a/ChatTwo/GameFunctions/Chat.cs b/HellionChat/GameFunctions/Chat.cs
similarity index 99%
rename from ChatTwo/GameFunctions/Chat.cs
rename to HellionChat/GameFunctions/Chat.cs
index b6f3b5f..20790ab 100755
--- a/ChatTwo/GameFunctions/Chat.cs
+++ b/HellionChat/GameFunctions/Chat.cs
@@ -1,8 +1,8 @@
using System.Text;
-using ChatTwo.Code;
-using ChatTwo.GameFunctions.Types;
-using ChatTwo.Resources;
-using ChatTwo.Util;
+using HellionChat.Code;
+using HellionChat.GameFunctions.Types;
+using HellionChat.Resources;
+using HellionChat.Util;
using Dalamud.Game.Config;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
@@ -22,7 +22,7 @@ using Lumina.Text.ReadOnly;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
-namespace ChatTwo.GameFunctions;
+namespace HellionChat.GameFunctions;
internal sealed unsafe class Chat : IDisposable
{
diff --git a/ChatTwo/GameFunctions/ChatBox.cs b/HellionChat/GameFunctions/ChatBox.cs
similarity index 94%
rename from ChatTwo/GameFunctions/ChatBox.cs
rename to HellionChat/GameFunctions/ChatBox.cs
index 3cbc6e7..1730e56 100644
--- a/ChatTwo/GameFunctions/ChatBox.cs
+++ b/HellionChat/GameFunctions/ChatBox.cs
@@ -1,10 +1,10 @@
using System.Text;
-using ChatTwo.Resources;
+using HellionChat.Resources;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
-namespace ChatTwo.GameFunctions;
+namespace HellionChat.GameFunctions;
public unsafe class ChatBox
{
diff --git a/ChatTwo/GameFunctions/Context.cs b/HellionChat/GameFunctions/Context.cs
similarity index 95%
rename from ChatTwo/GameFunctions/Context.cs
rename to HellionChat/GameFunctions/Context.cs
index aa64a5d..e1d3cb6 100755
--- a/ChatTwo/GameFunctions/Context.cs
+++ b/HellionChat/GameFunctions/Context.cs
@@ -1,9 +1,9 @@
-using ChatTwo.Util;
+using HellionChat.Util;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
-namespace ChatTwo.GameFunctions;
+namespace HellionChat.GameFunctions;
internal sealed unsafe class Context
{
diff --git a/ChatTwo/GameFunctions/GameFunctions.cs b/HellionChat/GameFunctions/GameFunctions.cs
similarity index 95%
rename from ChatTwo/GameFunctions/GameFunctions.cs
rename to HellionChat/GameFunctions/GameFunctions.cs
index ad7bff7..31b8970 100755
--- a/ChatTwo/GameFunctions/GameFunctions.cs
+++ b/HellionChat/GameFunctions/GameFunctions.cs
@@ -16,7 +16,7 @@ using Lumina.Excel;
using Lumina.Excel.Sheets;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
-namespace ChatTwo.GameFunctions;
+namespace HellionChat.GameFunctions;
internal unsafe class GameFunctions : IDisposable
{
@@ -249,9 +249,15 @@ internal unsafe class GameFunctions : IDisposable
private nint ResolveTextCommandPlaceholderDetour(nint a1, byte* placeholderText, byte a3, byte a4)
{
+ // The detour is only invoked through the hook, so the hook should
+ // never be null here, but the nullable field declaration forces us
+ // to handle the theoretical race during teardown.
+ if (ResolveTextCommandPlaceholderHook is null)
+ return nint.Zero;
+
var placeholder = MemoryHelper.ReadStringNullTerminated((nint) placeholderText);
if (ReplacementName == null || placeholder != Placeholder)
- return ResolveTextCommandPlaceholderHook!.Original(a1, placeholderText, a3, a4);
+ return ResolveTextCommandPlaceholderHook.Original(a1, placeholderText, a3, a4);
MemoryHelper.WriteString(PlaceholderNamePtr, ReplacementName);
ReplacementName = null;
diff --git a/ChatTwo/GameFunctions/KeybindManager.cs b/HellionChat/GameFunctions/KeybindManager.cs
similarity index 99%
rename from ChatTwo/GameFunctions/KeybindManager.cs
rename to HellionChat/GameFunctions/KeybindManager.cs
index d15bd73..2aa202d 100644
--- a/ChatTwo/GameFunctions/KeybindManager.cs
+++ b/HellionChat/GameFunctions/KeybindManager.cs
@@ -1,16 +1,16 @@
using System.Numerics;
-using ChatTwo.Code;
-using ChatTwo.GameFunctions.Types;
-using ChatTwo.Util;
+using HellionChat.Code;
+using HellionChat.GameFunctions.Types;
+using HellionChat.Util;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Config;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using Dalamud.Bindings.ImGui;
-using ModifierFlag = ChatTwo.GameFunctions.Types.ModifierFlag;
+using ModifierFlag = HellionChat.GameFunctions.Types.ModifierFlag;
-namespace ChatTwo.GameFunctions;
+namespace HellionChat.GameFunctions;
internal enum KeyboardSource {
Game,
diff --git a/ChatTwo/GameFunctions/Party.cs b/HellionChat/GameFunctions/Party.cs
similarity index 95%
rename from ChatTwo/GameFunctions/Party.cs
rename to HellionChat/GameFunctions/Party.cs
index f92945a..8326fb8 100755
--- a/ChatTwo/GameFunctions/Party.cs
+++ b/HellionChat/GameFunctions/Party.cs
@@ -1,10 +1,10 @@
-using ChatTwo.Resources;
-using ChatTwo.Util;
+using HellionChat.Resources;
+using HellionChat.Util;
using Dalamud.Interface.ImGuiNotification;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
-namespace ChatTwo.GameFunctions;
+namespace HellionChat.GameFunctions;
internal static unsafe class Party
{
diff --git a/ChatTwo/GameFunctions/Types/ChannelSwitchInfo.cs b/HellionChat/GameFunctions/Types/ChannelSwitchInfo.cs
similarity index 86%
rename from ChatTwo/GameFunctions/Types/ChannelSwitchInfo.cs
rename to HellionChat/GameFunctions/Types/ChannelSwitchInfo.cs
index 343ac7b..d419715 100755
--- a/ChatTwo/GameFunctions/Types/ChannelSwitchInfo.cs
+++ b/HellionChat/GameFunctions/Types/ChannelSwitchInfo.cs
@@ -1,6 +1,6 @@
-using ChatTwo.Code;
+using HellionChat.Code;
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
internal class ChannelSwitchInfo {
internal InputChannel? Channel { get; }
diff --git a/ChatTwo/GameFunctions/Types/ChatActivatedArgs.cs b/HellionChat/GameFunctions/Types/ChatActivatedArgs.cs
similarity index 92%
rename from ChatTwo/GameFunctions/Types/ChatActivatedArgs.cs
rename to HellionChat/GameFunctions/Types/ChatActivatedArgs.cs
index ba4a377..2297dc0 100755
--- a/ChatTwo/GameFunctions/Types/ChatActivatedArgs.cs
+++ b/HellionChat/GameFunctions/Types/ChatActivatedArgs.cs
@@ -1,4 +1,4 @@
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
internal sealed class ChatActivatedArgs
{
diff --git a/ChatTwo/GameFunctions/Types/Keybind.cs b/HellionChat/GameFunctions/Types/Keybind.cs
similarity index 85%
rename from ChatTwo/GameFunctions/Types/Keybind.cs
rename to HellionChat/GameFunctions/Types/Keybind.cs
index 1218184..98e7fad 100755
--- a/ChatTwo/GameFunctions/Types/Keybind.cs
+++ b/HellionChat/GameFunctions/Types/Keybind.cs
@@ -1,6 +1,6 @@
using Dalamud.Game.ClientState.Keys;
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
internal class Keybind
{
diff --git a/ChatTwo/GameFunctions/Types/ModifierFlag.cs b/HellionChat/GameFunctions/Types/ModifierFlag.cs
similarity index 71%
rename from ChatTwo/GameFunctions/Types/ModifierFlag.cs
rename to HellionChat/GameFunctions/Types/ModifierFlag.cs
index 3a8c459..1669bc9 100755
--- a/ChatTwo/GameFunctions/Types/ModifierFlag.cs
+++ b/HellionChat/GameFunctions/Types/ModifierFlag.cs
@@ -1,4 +1,4 @@
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
[Flags]
public enum ModifierFlag
diff --git a/ChatTwo/GameFunctions/Types/RotateMode.cs b/HellionChat/GameFunctions/Types/RotateMode.cs
similarity index 60%
rename from ChatTwo/GameFunctions/Types/RotateMode.cs
rename to HellionChat/GameFunctions/Types/RotateMode.cs
index 366fc72..0c83953 100755
--- a/ChatTwo/GameFunctions/Types/RotateMode.cs
+++ b/HellionChat/GameFunctions/Types/RotateMode.cs
@@ -1,4 +1,4 @@
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
internal enum RotateMode
{
diff --git a/ChatTwo/GameFunctions/Types/TellHistoryInfo.cs b/HellionChat/GameFunctions/Types/TellHistoryInfo.cs
similarity index 87%
rename from ChatTwo/GameFunctions/Types/TellHistoryInfo.cs
rename to HellionChat/GameFunctions/Types/TellHistoryInfo.cs
index 14bfa7c..4831a1c 100755
--- a/ChatTwo/GameFunctions/Types/TellHistoryInfo.cs
+++ b/HellionChat/GameFunctions/Types/TellHistoryInfo.cs
@@ -1,4 +1,4 @@
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
internal sealed class TellHistoryInfo
{
diff --git a/ChatTwo/GameFunctions/Types/TellReason.cs b/HellionChat/GameFunctions/Types/TellReason.cs
similarity index 69%
rename from ChatTwo/GameFunctions/Types/TellReason.cs
rename to HellionChat/GameFunctions/Types/TellReason.cs
index f779633..9546064 100755
--- a/ChatTwo/GameFunctions/Types/TellReason.cs
+++ b/HellionChat/GameFunctions/Types/TellReason.cs
@@ -1,4 +1,4 @@
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
public enum TellReason
{
diff --git a/ChatTwo/GameFunctions/Types/TellTarget.cs b/HellionChat/GameFunctions/Types/TellTarget.cs
similarity index 91%
rename from ChatTwo/GameFunctions/Types/TellTarget.cs
rename to HellionChat/GameFunctions/Types/TellTarget.cs
index 03be83d..b6151c2 100755
--- a/ChatTwo/GameFunctions/Types/TellTarget.cs
+++ b/HellionChat/GameFunctions/Types/TellTarget.cs
@@ -1,7 +1,7 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
-namespace ChatTwo.GameFunctions.Types;
+namespace HellionChat.GameFunctions.Types;
[Serializable]
public class TellTarget
@@ -30,6 +30,9 @@ public class TellTarget
public unsafe void FromTarget(IPlayerCharacter target)
{
+ if (target.Address == nint.Zero)
+ return;
+
Name = target.Name.TextValue;
World = target.HomeWorld.RowId;
ContentId = ((Character*)target.Address)->ContentId;
diff --git a/ChatTwo/ChatTwo.csproj b/HellionChat/HellionChat.csproj
similarity index 72%
rename from ChatTwo/ChatTwo.csproj
rename to HellionChat/HellionChat.csproj
index 69b12b1..bfae8dc 100644
--- a/ChatTwo/ChatTwo.csproj
+++ b/HellionChat/HellionChat.csproj
@@ -4,20 +4,29 @@
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. -->
- 0.6.1
+ 1.0.0enable
-
+
+ true
+
HellionChat
- ChatTwo
+ HellionChat
+
+
diff --git a/ChatTwo/HellionChat.yaml b/HellionChat/HellionChat.yaml
similarity index 50%
rename from ChatTwo/HellionChat.yaml
rename to HellionChat/HellionChat.yaml
index 1d916fe..d264e52 100755
--- a/ChatTwo/HellionChat.yaml
+++ b/HellionChat/HellionChat.yaml
@@ -1,14 +1,15 @@
name: Hellion Chat
author: JonKazama-Hellion
-punchline: Chat 2 with privacy controls aligned to EU, US and JP rules
+punchline: Chat replacement with privacy controls aligned to EU, US and JP rules — based on Chat 2 (EUPL-1.2)
description: |-
- Hellion Chat is built on top of Chat 2 with one removal and a stack
- of privacy controls on top. Tabs, channel filters, RGB colours,
- emotes, screenshot mode, IPC integration and the chat replacement
- window itself work the same. The optional webinterface that Chat 2
- ships is intentionally not part of this fork because it serves a
- different use case from the smaller default footprint Hellion Chat
- is built around.
+ 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.
On top of that, Hellion Chat adds privacy and data-handling controls
designed to align with the modern data protection rules that apply
@@ -18,7 +19,7 @@ description: |-
channel, history can be wiped retroactively, and stored data can be
exported on demand.
- Key additions on top of Chat 2:
+ Key privacy and data-handling features:
- Channel whitelist with a Privacy-First default
- Per-channel retention with a daily background sweep
@@ -28,7 +29,7 @@ description: |-
Full History)
- Bilingual UI (English and German) with live language switching
- Independent plugin state — own config file and database directory,
- so Hellion Chat does not share state with the upstream plugin
+ so Hellion Chat does not share state with upstream Chat 2
Based on Chat 2 by Infi and Anna, licensed under EUPL-1.2.
@@ -37,10 +38,10 @@ description: |-
other Hellion Online Media plugins/tools.
repo_url: https://github.com/JonKazama-Hellion/HellionChat
accepts_feedback: true
-icon_url: https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/icon.png
+icon_url: https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/icon.png
image_urls:
- - https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/chatWindow.png
- - https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/withSimpleTweaks.png
+ - https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/chatWindow.png
+ - https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/withSimpleTweaks.png
tags:
- Social
- UI
@@ -48,6 +49,105 @@ tags:
- Replacement
- Privacy
changelog: |-
+ **Hellion Chat 1.0.0 — Standalone Major Release**
+
+ First fully standalone release. Internal cleanup plus a sweep of
+ pre-existing correctness, security, threading and resource-leak
+ fixes carried over from the upstream codebase. No user action
+ required — auto-update applies cleanly, configuration and database
+ paths unchanged.
+
+ Standalone identity:
+
+ - Code namespace consolidated from ChatTwo.* to HellionChat.* across
+ all source files
+ - IPC channels migrated from ChatTwo.* to HellionChat.* (6 channels:
+ Register, Available, Unregister, Invoke, GetChatInputState,
+ ChatInputStateChanged) — third-party plugins that bound to the old
+ channels need to be updated; none known at release time
+ - ImGui popup ID renamed to hellionchat-context-popup
+ - Repository folder restructured (ChatTwo/ → HellionChat/), all CI
+ and build paths updated accordingly
+ - Public-facing descriptions reworded from upstream-fork framing to
+ standalone framing (Chat 2 attribution preserved per EUPL-1.2)
+ - Colour preset 'ChatTwo Default' is now 'Klassik (Chat 2 Default)'
+
+ Safety:
+
+ - Plugin now refuses to load when upstream Chat 2 is also active —
+ bilingual conflict message in EN/DE, throw before any subsystem
+ initialization, prevents the runtime crash that previously occurred
+ when both plugins replaced the same chat window in parallel
+ - SQLite native binary bumped to 3.50.3 (CVE-2025-6965 memory
+ corruption from aggregate-term overflow, CVE-2025-7709)
+ - NuGet restore now honors packages.lock.json so transitive
+ dependencies don't drift between machines or CI runs
+
+ Crash-class fixes (formerly latent in upstream):
+
+ - MathUtil.HasOverlap now uses a correct AABB test; identical or
+ edge-touching rectangles are no longer reported as non-overlapping
+ - ChatCode.Equals compares fields directly instead of GetHashCode;
+ removes the hash-collision anti-pattern
+ - IpcManager.Dispose uses UnregisterAction to match the matching
+ RegisterAction call; previous mismatch leaked the action
+ subscription on every plugin reload
+ - ExtraChat.Dispose now unsubscribes all three IPC subscriptions
+ (was only the first); leaks closed
+ - TellTarget.FromTarget guards against a zero IPlayerCharacter.Address
+ before dereferencing the unsafe Character* cast
+ - GameFunctions ResolveTextCommandPlaceholderDetour null-checks the
+ Hook reference instead of using the null-forgiving operator
+ - Popout.cs and SettingsTabs/Tabs.cs bounds-check list indexing so
+ a tab drop or empty-worlds list no longer crashes the UI
+ - Debugger.cs now declares IDisposable so the existing Dispose runs
+
+ Correctness fixes:
+
+ - GlobalParametersCache.GetValue captures Cache into a local before
+ the bounds check, so a concurrent Refresh can't slip a different
+ array between check and read
+ - IconUtil binary search bounds initialized to entries.Length-1 and
+ reset on redirect-restart; entries.Length==0 short-circuits
+ - Sheets.WorldsOnDatacenter now compares DataCenter.RowId (was
+ Region.RowId) so it actually returns same-DC worlds
+ - Message.cs back-reference loop iterates the processed Sender/Content
+ properties so chunks added by CheckMessageContent get Message set
+ - Language.zh-Hans Webinterface_Start_Success corrected to
+ "网页界面已启动" (was "网页界面已停止")
+
+ Threading and async:
+
+ - AutoTranslate Entries/ValidEntries are now serialized behind a
+ single lock; the preload worker thread and main thread no longer
+ race on the underlying dictionary/hash set
+ - Privacy retention and cleanup workers bound their framework-refresh
+ waits to 5 seconds with a logged timeout; a hung framework tick can
+ no longer deadlock the background worker
+
+ Resource handling:
+
+ - EmoteCache reuses the static HttpClient instead of allocating a new
+ one per call (closed socket leak)
+ - FontManager wraps HttpClient/HttpResponseMessage in using-blocks
+ and adds EnsureSuccessStatusCode; failed downloads no longer
+ silently produce a zero-byte font file
+ - SearchSelector mixes the row index into the ImGui ID stack so
+ selectables don't collapse to a single ambiguous ID
+ - SettingsTabs/Chat blocked-emote add-button now opens its selector
+ popup on left-click
+
+ Performance:
+
+ - DbViewer text export caches filteredHistory.Count once instead of
+ re-enumerating the IEnumerable on every batch (O(N) instead of
+ O(N²) on large histories)
+
+ License attribution (NOTICE.md, COPYRIGHT, THIRD_PARTY_NOTICES.md
+ and the Credits section in README) is unchanged.
+
+ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
+
**Hellion Chat 0.6.1 — Pop-Out Discoverability & /tell Auto-Pop-Out**
- Pop-out button now visible in the chat header (no more hunting
diff --git a/ChatTwo/InputHistoryService.cs b/HellionChat/InputHistoryService.cs
similarity index 98%
rename from ChatTwo/InputHistoryService.cs
rename to HellionChat/InputHistoryService.cs
index bc65431..cc653db 100644
--- a/ChatTwo/InputHistoryService.cs
+++ b/HellionChat/InputHistoryService.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace ChatTwo;
+namespace HellionChat;
// Hellion Chat — v0.6.0 shared input history. Replaces the embedded
// ChatLogWindow.InputBacklog so that pop-out windows with their own
diff --git a/ChatTwo/Ipc/ExtraChat.cs b/HellionChat/Ipc/ExtraChat.cs
similarity index 94%
rename from ChatTwo/Ipc/ExtraChat.cs
rename to HellionChat/Ipc/ExtraChat.cs
index 70061da..b04521e 100644
--- a/ChatTwo/Ipc/ExtraChat.cs
+++ b/HellionChat/Ipc/ExtraChat.cs
@@ -1,6 +1,6 @@
using Dalamud.Plugin.Ipc;
-namespace ChatTwo.Ipc;
+namespace HellionChat.Ipc;
public sealed class ExtraChat : IDisposable
{
@@ -49,6 +49,8 @@ public sealed class ExtraChat : IDisposable
public void Dispose()
{
OverrideChannelGate.Unsubscribe(OnOverrideChannel);
+ ChannelCommandColoursGate.Unsubscribe(OnChannelCommandColours);
+ ChannelNamesGate.Unsubscribe(OnChannelNames);
}
private void OnOverrideChannel(OverrideInfo info)
diff --git a/ChatTwo/Ipc/TypingIpc.cs b/HellionChat/Ipc/TypingIpc.cs
similarity index 91%
rename from ChatTwo/Ipc/TypingIpc.cs
rename to HellionChat/Ipc/TypingIpc.cs
index a367da6..51991fd 100644
--- a/ChatTwo/Ipc/TypingIpc.cs
+++ b/HellionChat/Ipc/TypingIpc.cs
@@ -1,7 +1,7 @@
-using ChatTwo.Code;
+using HellionChat.Code;
using Dalamud.Plugin.Ipc;
-namespace ChatTwo.Ipc;
+namespace HellionChat.Ipc;
using ChatInputState = (bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType);
@@ -19,8 +19,8 @@ internal sealed class TypingIpc : IDisposable
{
Plugin = plugin;
- StateQueryGate = Plugin.Interface.GetIpcProvider("ChatTwo.GetChatInputState");
- StateChangedGate = Plugin.Interface.GetIpcProvider("ChatTwo.ChatInputStateChanged");
+ StateQueryGate = Plugin.Interface.GetIpcProvider("HellionChat.GetChatInputState");
+ StateChangedGate = Plugin.Interface.GetIpcProvider("HellionChat.ChatInputStateChanged");
StateQueryGate.RegisterFunc(GetState);
}
diff --git a/ChatTwo/IpcManager.cs b/HellionChat/IpcManager.cs
similarity index 86%
rename from ChatTwo/IpcManager.cs
rename to HellionChat/IpcManager.cs
index 06d653b..5db49a7 100755
--- a/ChatTwo/IpcManager.cs
+++ b/HellionChat/IpcManager.cs
@@ -2,7 +2,7 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin.Ipc;
-namespace ChatTwo;
+namespace HellionChat;
internal sealed class IpcManager : IDisposable
{
@@ -15,15 +15,15 @@ internal sealed class IpcManager : IDisposable
public IpcManager()
{
- RegisterGate = Plugin.Interface.GetIpcProvider("ChatTwo.Register");
+ RegisterGate = Plugin.Interface.GetIpcProvider("HellionChat.Register");
RegisterGate.RegisterFunc(Register);
- AvailableGate = Plugin.Interface.GetIpcProvider