docs: Fix the last comments i think now
Security / scan (push) Successful in 12s

This commit is contained in:
2026-05-11 08:11:30 +02:00
parent 7d73def53d
commit b1b6402827
10 changed files with 70 additions and 199 deletions
+11 -26
View File
@@ -17,10 +17,9 @@ internal static class AutoTranslate
private static readonly Dictionary<ClientLanguage, List<AutoTranslateEntry>> Entries = new();
private static readonly HashSet<(uint, uint)> ValidEntries = [];
// Serializes all reads and writes against Entries / ValidEntries.
// PreloadCache spawns a worker thread that fills both, while the main
// thread reads them via Matching / ReplaceWithPayload / StartsWithCommand
// — without this lock the HashSet/Dictionary access is undefined.
// Serialises all reads/writes against Entries and ValidEntries.
// PreloadCache fills both from a worker thread while the main thread
// reads via Matching/ReplaceWithPayload/StartsWithCommand.
private static readonly object EntriesLock = new();
private static Parser<char, (string name, Maybe<IEnumerable<ISelectorPart>> selector)> Parser()
@@ -54,13 +53,8 @@ internal static class AutoTranslate
return Map((name, sel) => (name, sel), sheetName, selector.Optional());
}
/// <summary>
/// Preloads auto-translate entries into the cache for the current game
/// language. Without this, the first message will take a long time to send
/// (which causes a hitch in the main thread).
///
/// This spawns a new thread.
/// </summary>
// Warms the auto-translate cache on a background thread so the first
// message send doesn't hitch the main thread.
internal static void PreloadCache()
{
new Thread(() =>
@@ -104,7 +98,7 @@ internal static class AutoTranslate
{
if (lookup is not ("" or "@"))
{
// SE added whitespace to the newest additions, but ParseOrThrow doesn't see them as valid
// SE added whitespace to newer entries; strip it before parsing.
lookup = lookup.Replace(" ", "");
var (sheetName, selector) = parser.ParseOrThrow(lookup);
@@ -144,19 +138,13 @@ internal static class AutoTranslate
columns.Add(0);
if (rows.Count == 0)
// We can't use an "index from end" (like `^0`) here because
// we're iterating over integers, not an array directly.
// Previously, we were setting `0..^0` which caused these
// sheets to be completely skipped due to this bug.
// See below.
// Can't use index-from-end here because we iterate over integers,
// not an array directly. `0..^0` would silently skip the sheet.
rows.Add(..Index.FromStart((int)sheet.GetRowAt(sheet.Count - 1).RowId + 1));
foreach (var range in rows)
{
// We iterate over the range by numerical values here, so
// we can't use an "index from end" otherwise nothing will
// happen.
// See above.
// Integer iteration -- can't use index-from-end (see above).
for (var i = range.Start.Value; i < range.End.Value; i++)
{
if (!sheet.TryGetRow((uint)i, out var rowParser))
@@ -261,7 +249,6 @@ internal static class AutoTranslate
if (bytes.Length <= search.Length)
return;
// populate the list of valid entries
bool needBuild;
lock (EntriesLock)
needBuild = ValidEntries.Count == 0;
@@ -308,9 +295,8 @@ internal static class AutoTranslate
start = -1;
}
// Pure managed comparison via Span avoids the msvcrt.dll P/Invoke,
// which is fragile under Wine and triggered an extra managed-to-
// unmanaged copy per check.
// Span comparison avoids the msvcrt.dll P/Invoke which is fragile
// under Wine and caused an extra managed-to-unmanaged copy per check.
if (
i + search.Length < bytes.Length
&& bytes.AsSpan(i, search.Length).SequenceEqual(search)
@@ -325,7 +311,6 @@ internal static class AutoTranslate
if (bytes.Length <= search.Length)
return false;
// populate the list of valid entries
bool needBuild;
lock (EntriesLock)
needBuild = ValidEntries.Count == 0;
+3 -9
View File
@@ -10,9 +10,8 @@ public static class GlobalParametersCache
public static int GetValue(int index)
{
// Capture the array reference once so the bounds check and the
// indexed read operate on the same instance, even if Refresh
// reassigns Cache between the two operations.
// Capture the array reference once so bounds check and read operate
// on the same instance if Refresh reassigns Cache between the two.
var cache = Cache;
if (index < 0 || index >= cache.Length)
return 0;
@@ -20,12 +19,7 @@ public static class GlobalParametersCache
return cache[index];
}
/// <summary>
/// Refresh the cache of global parameters from RaptureTextModule.
/// </summary>
/// <remarks>
/// This should be called in the main thread when updates are necessary.
/// </remarks>
// Refreshes the cache from RaptureTextModule. Must be called on the main thread.
public static unsafe void Refresh()
{
if (!ThreadSafety.IsMainThread)
+9 -37
View File
@@ -11,8 +11,7 @@ public readonly unsafe ref struct GfdFileView
private readonly ReadOnlySpan<byte> Span;
private readonly bool DirectLookup;
/// <summary>Initializes a new instance of the <see cref="GfdFileView"/> struct.</summary>
/// <param name="span">The data.</param>
// span: raw .gfd file bytes
public GfdFileView(ReadOnlySpan<byte> span)
{
Span = span;
@@ -27,18 +26,13 @@ public readonly unsafe ref struct GfdFileView
DirectLookup &= i + 1 == entries[i].Id;
}
/// <summary>Gets the header.</summary>
private ref readonly GfdHeader Header => ref MemoryMarshal.AsRef<GfdHeader>(Span);
/// <summary>Gets the entries.</summary>
private ReadOnlySpan<GfdEntry> Entries =>
MemoryMarshal.Cast<byte, GfdEntry>(Span[sizeof(GfdHeader)..]);
/// <summary>Attempts to get an entry.</summary>
/// <param name="iconId">The icon ID.</param>
/// <param name="entry">The entry.</param>
/// <param name="followRedirect">Whether to follow redirects.</param>
/// <returns><c>true</c> if found.</returns>
// Returns true if the entry was found.
// followRedirect: whether to chase redirect chains.
public bool TryGetEntry(uint iconId, out GfdEntry entry, bool followRedirect = true)
{
if (iconId == 0)
@@ -50,9 +44,8 @@ public readonly unsafe ref struct GfdFileView
var entries = Entries;
if (DirectLookup)
{
// Resolve redirects on the direct-lookup path too — the binary-search
// path follows them, and skipping them here was inconsistent for
// contiguous ID sets.
// Follow redirects on the direct-lookup path for consistency with
// the binary-search path.
var visited = 0;
while (iconId <= entries.Length)
{
@@ -107,49 +100,28 @@ public readonly unsafe ref struct GfdFileView
return false;
}
/// <summary>Header of a .gfd file.</summary>
// .gfd file header
[StructLayout(LayoutKind.Sequential)]
public struct GfdHeader
{
/// <summary>Signature: "gftd0100".</summary>
public fixed byte Signature[8];
/// <summary>Number of entries.</summary>
public fixed byte Signature[8]; // "gftd0100"
public int Count;
/// <summary>Unused/unknown.</summary>
public fixed byte Padding[4];
}
/// <summary>An entry of a .gfd file.</summary>
// .gfd file entry -- one icon slot
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct GfdEntry
{
/// <summary>ID of the entry.</summary>
public ushort Id;
/// <summary>The left offset of the entry.</summary>
public ushort Left;
/// <summary>The top offset of the entry.</summary>
public ushort Top;
/// <summary>The width of the entry.</summary>
public ushort Width;
/// <summary>The height of the entry.</summary>
public ushort Height;
/// <summary>Unknown/unused.</summary>
public ushort Unk0A;
/// <summary>The redirected entry, maybe.</summary>
public ushort Redirect;
/// <summary>Unknown/unused.</summary>
public ushort Redirect; // non-zero = redirects to another entry
public ushort Unk0E;
/// <summary>Gets a value indicating whether this entry is effectively empty.</summary>
public bool IsEmpty => Width == 0 || Height == 0;
}
}
+2 -10
View File
@@ -31,18 +31,10 @@ public static class MathUtil
public override string ToString() => $"X: {X} Y: {Y} Width: {Width} Height: {Height}";
}
/// <summary>
/// Checks if two rectangles overlap at any point.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns>True if overlapping</returns>
// Standard AABB overlap test. Inclusive on both axes to catch shared
// edges and identical rectangles (previous ValueInRange approach missed these).
public static bool HasOverlap(this Rectangle a, Rectangle b)
{
// Standard AABB overlap test: two rectangles overlap iff they
// overlap on both axes. The previous nested ValueInRange approach
// used strict inequalities at both ends, which dropped identical
// rectangles and shared-edge cases as false negatives.
return a.X < b.X + b.Width
&& a.X + a.Width > b.X
&& a.Y < b.Y + b.Height
+11 -40
View File
@@ -13,15 +13,10 @@ internal class PartyFinderPayload : Payload
Id = id;
}
protected override byte[] EncodeImpl()
{
throw new NotImplementedException();
}
protected override byte[] EncodeImpl() => throw new NotImplementedException();
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
{
protected override void DecodeImpl(BinaryReader reader, long endOfStream) =>
throw new NotImplementedException();
}
}
internal class AchievementPayload : Payload
@@ -35,15 +30,10 @@ internal class AchievementPayload : Payload
Id = id;
}
protected override byte[] EncodeImpl()
{
throw new NotImplementedException();
}
protected override byte[] EncodeImpl() => throw new NotImplementedException();
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
{
protected override void DecodeImpl(BinaryReader reader, long endOfStream) =>
throw new NotImplementedException();
}
}
internal class UriPayload(Uri uri) : Payload
@@ -55,20 +45,14 @@ internal class UriPayload(Uri uri) : Payload
private const string DefaultScheme = "https";
private static readonly string[] ExpectedSchemes = ["http", "https"];
/// <summary>
/// Create a URIPayload from a raw URI string. If the URI does not have a
/// scheme, it will default to https://.
/// </summary>
/// <exception cref="UriFormatException">
/// If the URI is invalid, or if the scheme is not supported.
/// </exception>
// Parses a raw URI string. Defaults to https:// if no scheme is present.
// Throws UriFormatException for empty input or unsupported schemes.
public static UriPayload ResolveUri(string rawUri)
{
ArgumentNullException.ThrowIfNull(rawUri);
if (string.IsNullOrWhiteSpace(rawUri))
throw new UriFormatException("URI cannot be empty or whitespace.");
// Check for an expected scheme '://', if not add 'https://'
if (ExpectedSchemes.Any(scheme => rawUri.StartsWith($"{scheme}://")))
return new UriPayload(new Uri(rawUri));
@@ -78,15 +62,10 @@ internal class UriPayload(Uri uri) : Payload
return new UriPayload(new Uri($"{DefaultScheme}://{rawUri}"));
}
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
{
protected override void DecodeImpl(BinaryReader reader, long endOfStream) =>
throw new NotImplementedException();
}
protected override byte[] EncodeImpl()
{
throw new NotImplementedException();
}
protected override byte[] EncodeImpl() => throw new NotImplementedException();
}
internal class EmotePayload : Payload
@@ -95,18 +74,10 @@ internal class EmotePayload : Payload
public string Code = string.Empty;
public static EmotePayload ResolveEmote(string code)
{
return new EmotePayload { Code = code };
}
public static EmotePayload ResolveEmote(string code) => new EmotePayload { Code = code };
protected override void DecodeImpl(BinaryReader reader, long endOfStream)
{
protected override void DecodeImpl(BinaryReader reader, long endOfStream) =>
throw new NotImplementedException();
}
protected override byte[] EncodeImpl()
{
throw new NotImplementedException();
}
protected override byte[] EncodeImpl() => throw new NotImplementedException();
}
+9 -18
View File
@@ -14,12 +14,8 @@ public static class TabsUtil
return channels;
}
// Hellion-tuned General preset (v1.0.0 — sharpened defaults).
// Public-chat-only, the bare three channels you encounter in open
// world. Group/FC/Linkshell traffic moves to dedicated tabs, gameplay
// events (loot, crafting, gathering, NPC dialogue, PF pings) move to
// the System tab where they belong — keeps the General view focused
// on actual conversation in the immediate surroundings.
// Public-chat-only: Say, Yell, Shout. Group/FC/Linkshell and gameplay
// events live in their own tabs to keep General focused on open-world chat.
public static Tab VanillaGeneral =>
new()
{
@@ -55,11 +51,8 @@ public static class TabsUtil
AllSenderMessages = true,
};
// Hellion default-tab presets used by the v10 wipe migration. Names are
// kept in HellionStrings (EN+DE) instead of Language.* so the upstream
// resource files stay untouched. Channel selections cover the channels
// a typical Eorzea raider uses without forcing the user to hand-tick
// each box on first start.
// Hellion tab presets. Names live in HellionStrings (EN+DE) so upstream
// resource files stay untouched.
public static Tab HellionFreeCompany =>
new()
{
@@ -88,10 +81,8 @@ public static class TabsUtil
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
},
// No automatic input-channel switch; the Gruppe tab is a read
// surface that pulls in Party, CrossParty, Alliance and PvpTeam
// together. Auto-routing /party into this tab would surprise the
// user when they actually wanted /alliance or /pvpteam.
// No input-channel switch: Party pulls in multiple channel types
// and auto-routing /party would surprise users wanting /alliance or /pvpteam.
};
public static Tab HellionBeginner =>
@@ -112,7 +103,7 @@ public static class TabsUtil
Name = HellionStrings.Tabs_Presets_System,
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
{
// Plain system noise
// System noise
[ChatType.Debug] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Urgent] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.Notice] = (ChatSourceExt.All, ChatSourceExt.All),
@@ -122,7 +113,7 @@ public static class TabsUtil
[ChatType.GatheringSystem] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.BattleSystem] = (ChatSourceExt.All, ChatSourceExt.All),
// Login / logout / announcement noise
// Login/logout/announcement noise
[ChatType.NpcAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
@@ -130,7 +121,7 @@ public static class TabsUtil
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.RetainerSale] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.PeriodicRecruitmentNotification] = (ChatSourceExt.All, ChatSourceExt.All),
// Gameplay-event streams (moved out of General in v1.0.0)
// Gameplay event streams
[ChatType.NpcDialogue] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
+2 -12
View File
@@ -135,18 +135,8 @@ public static class Tokenizer
public int Precedence { get; set; }
}
/// <summary>
/// URLRegex returns a regex object that matches URLs like:
/// - https://example.com
/// - http://example.com
/// - www.example.com
/// - https://sub.example.com
/// - example.com
/// - sub.example.com
///
/// It matches URLs with www. or https:// prefix, and also matches URLs
/// without a prefix on specific TLDs.
/// </summary>
// Matches URLs with http(s):// or www. prefix, and bare domains on known TLDs.
// Examples: https://example.com, www.sub.example.com, example.com
private static readonly Regex UrlRegex = new(
@"(?<URL>((https?:\/\/|www\.)[a-z0-9-]+(\.[a-z0-9-]+)*|([a-z0-9-]+(\.[a-z0-9-]+)*\.(com|net|org|co|io|app)))(:[\d]{1,5})?(\/[^\s]*)?)",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture