Stop the BetterTTV cache and font atlas from logging on every load

Two long-standing upstream bugs surfaced in our distribution as
red error rows on every plugin load. Neither is caused by the
fork's own code, but both happen under our plugin name in the
log so they had to go before the plugin gets handed to anyone.

EmoteCache.LoadData fed BetterTTV's API responses straight into
Dictionary.TryAdd. The Top100 endpoint occasionally returns rows
with a null Code field, which tripped ArgumentNullException and
killed the entire emote load. Filter null/empty codes out
explicitly before insertion so a single bad row no longer breaks
the cache for everyone else.

FontManager.BuildFonts called fontId.AddToBuildToolkit without
guarding for the case where the configured SystemFontId points at
a font that isn't installed (e.g. "Crimson Text" on a Linux box,
or a font the user uninstalled after picking it). The atlas build
then crashed and dumped a Dalamud DelegateFontHandle error every
single load. Wrap the call in AddFontWithFallback: if the font
isn't found, log a warning, fall back to the bundled
NotoSansCjkRegular asset and continue. Applied to both the regular
and italic font handles.
This commit is contained in:
2026-05-01 22:37:18 +02:00
parent 65e39d4ef7
commit 71aefd30eb
2 changed files with 35 additions and 7 deletions
+7 -2
View File
@@ -78,7 +78,7 @@ public static class EmoteCache
var globalList = await global.Content.ReadAsStringAsync(); var globalList = await global.Content.ReadAsStringAsync();
foreach (var emote in JsonSerializer.Deserialize<Emote[]>(globalList)!) foreach (var emote in JsonSerializer.Deserialize<Emote[]>(globalList)!)
if (!NotWorking.Contains(emote.Code)) if (!string.IsNullOrEmpty(emote.Code) && !NotWorking.Contains(emote.Code))
Cache.TryAdd(emote.Code, emote); Cache.TryAdd(emote.Code, emote);
var lastId = string.Empty; var lastId = string.Empty;
@@ -88,8 +88,13 @@ public static class EmoteCache
var topList = await top.Content.ReadAsStringAsync(); var topList = await top.Content.ReadAsStringAsync();
var jsonList = JsonSerializer.Deserialize<List<Top100>>(topList)!; var jsonList = JsonSerializer.Deserialize<List<Top100>>(topList)!;
// BetterTTV occasionally returns entries with a null Code; the
// upstream code passed those straight into Dictionary.TryAdd
// and tripped ArgumentNullException, killing the whole emote
// load. Skip them defensively so a single bad row no longer
// breaks the cache for everyone else.
foreach (var emote in jsonList) foreach (var emote in jsonList)
if (!NotWorking.Contains(emote.Emote.Code)) if (!string.IsNullOrEmpty(emote.Emote.Code) && !NotWorking.Contains(emote.Emote.Code))
Cache.TryAdd(emote.Emote.Code, emote.Emote); Cache.TryAdd(emote.Emote.Code, emote.Emote);
lastId = jsonList.Last().Id; lastId = jsonList.Last().Id;
+28 -5
View File
@@ -1,4 +1,6 @@
using Dalamud.Interface; using Dalamud;
using Dalamud.Interface;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.GameFonts; using Dalamud.Interface.GameFonts;
using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
@@ -119,11 +121,11 @@ public class FontManager
tk => tk =>
{ {
var config = new SafeFontConfig {SizePt = Plugin.Config.GlobalFontV2.SizePt, GlyphRanges = Ranges}; var config = new SafeFontConfig {SizePt = Plugin.Config.GlobalFontV2.SizePt, GlyphRanges = Ranges};
config.MergeFont = Plugin.Config.GlobalFontV2.FontId.AddToBuildToolkit(tk, config); config.MergeFont = AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt; config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
config.GlyphRanges = JpRange; config.GlyphRanges = JpRange;
Plugin.Config.JapaneseFontV2.FontId.AddToBuildToolkit(tk, config); AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
config.SizePt = Plugin.Config.SymbolsFontSizeV2; config.SizePt = Plugin.Config.SymbolsFontSizeV2;
tk.AddGameSymbol(config); tk.AddGameSymbol(config);
@@ -139,11 +141,11 @@ public class FontManager
tk => tk =>
{ {
var config = new SafeFontConfig {SizePt = Plugin.Config.ItalicFontV2.SizePt, GlyphRanges = Ranges}; var config = new SafeFontConfig {SizePt = Plugin.Config.ItalicFontV2.SizePt, GlyphRanges = Ranges};
config.MergeFont = Plugin.Config.ItalicFontV2.FontId.AddToBuildToolkit(tk, config); config.MergeFont = AddFontWithFallback(tk, Plugin.Config.ItalicFontV2.FontId, config, "italic");
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt; config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
config.GlyphRanges = JpRange; config.GlyphRanges = JpRange;
Plugin.Config.JapaneseFontV2.FontId.AddToBuildToolkit(tk, config); AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
config.SizePt = Plugin.Config.SymbolsFontSizeV2; config.SizePt = Plugin.Config.SymbolsFontSizeV2;
tk.AddGameSymbol(config); tk.AddGameSymbol(config);
@@ -158,6 +160,27 @@ public class FontManager
} }
} }
/// <summary>
/// Try to add a user-configured font to the build toolkit, falling back to
/// the bundled NotoSansCjkRegular asset if the configured font isn't
/// available on the system. Without this guard a stale SystemFontId
/// pointing at a font the user uninstalled or that never existed on
/// Linux (e.g. "Crimson Text") tears down the entire font atlas build.
/// </summary>
private static ImFontPtr AddFontWithFallback(IFontAtlasBuildToolkitPreBuild tk, IFontId fontId, SafeFontConfig config, string slot)
{
try
{
return fontId.AddToBuildToolkit(tk, config);
}
catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException or IOException)
{
Plugin.Log.Warning(e, $"Configured {slot} font unavailable, falling back to NotoSansCjkRegular");
var fallback = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular);
return fallback.AddToBuildToolkit(tk, config);
}
}
public static float SizeInPt(float px) => (float) (px * 3.0 / 4.0); public static float SizeInPt(float px) => (float) (px * 3.0 / 4.0);
public static float SizeInPx(float pt) => (float) (pt * 4.0 / 3.0); public static float SizeInPx(float pt) => (float) (pt * 4.0 / 3.0);
public static float GetFontSize() => Plugin.Config.FontsEnabled ? Plugin.Config.GlobalFontV2.SizePx : SizeInPx(Plugin.Config.FontSizeV2); public static float GetFontSize() => Plugin.Config.FontsEnabled ? Plugin.Config.GlobalFontV2.SizePx : SizeInPx(Plugin.Config.FontSizeV2);