diff --git a/OtterGui b/OtterGui
index c53955cb..d9486ae5 160000
--- a/OtterGui
+++ b/OtterGui
@@ -1 +1 @@
-Subproject commit c53955cb6199dd418c5a9538d3251ac5942e7067
+Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b
diff --git a/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs b/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs
index 2c6f6901..96d9daff 100644
--- a/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs
+++ b/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs
@@ -4,23 +4,26 @@ using Penumbra.Collections;
using Penumbra.GameData.Files;
using Penumbra.GameData.Files.Utility;
using Penumbra.Interop.Hooks.ResourceLoading;
+using Penumbra.Mods.Manager;
+using Penumbra.Services;
using Penumbra.String;
using Penumbra.String.Classes;
-using Penumbra.UI;
namespace Penumbra.Interop.Processing;
///
/// Path pre-processor for shader packages that reverts redirects to known invalid files, as bad ShPks can crash the game.
///
-public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager, ChatWarningService chatWarningService) : IPathPreProcessor
+public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager, MessageService messager, ModManager modManager)
+ : IPathPreProcessor
{
public ResourceType Type
=> ResourceType.Shpk;
- public unsafe FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved)
+ public unsafe FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath originalGamePath, bool nonDefault,
+ FullPath? resolved)
{
- chatWarningService.CleanLastFileWarnings(false);
+ messager.CleanTaggedMessages(false);
if (!resolved.HasValue)
return null;
@@ -31,7 +34,8 @@ public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager,
return resolvedPath;
// If the ShPk is already loaded, it means that it already passed the sanity check.
- var existingResource = resourceManager.FindResource(ResourceCategory.Shader, ResourceType.Shpk, unchecked((uint)resolvedPath.InternalName.Crc32));
+ var existingResource =
+ resourceManager.FindResource(ResourceCategory.Shader, ResourceType.Shpk, unchecked((uint)resolvedPath.InternalName.Crc32));
if (existingResource != null)
return resolvedPath;
@@ -39,8 +43,7 @@ public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager,
if (checkResult == SanityCheckResult.Success)
return resolvedPath;
- Penumbra.Log.Warning($"Refusing to honor file redirection because of failed sanity check (result: {checkResult}). Original path: {originalGamePath} Redirected path: {resolvedPath}");
- chatWarningService.PrintFileWarning(resolvedPath.FullName, originalGamePath, WarningMessageComplement(checkResult));
+ messager.PrintFileWarning(modManager, resolvedPath.FullName, originalGamePath, WarningMessageComplement(checkResult));
return null;
}
@@ -49,8 +52,8 @@ public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager,
{
try
{
- using var file = MmioMemoryManager.CreateFromFile(path);
- var bytes = file.GetSpan();
+ using var file = MmioMemoryManager.CreateFromFile(path);
+ var bytes = file.GetSpan();
return ShpkFile.FastIsLegacy(bytes)
? SanityCheckResult.Legacy
@@ -69,9 +72,9 @@ public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager,
private static string WarningMessageComplement(SanityCheckResult result)
=> result switch
{
- SanityCheckResult.IoError => "cannot read the modded file.",
- SanityCheckResult.NotFound => "the modded file does not exist.",
- SanityCheckResult.Legacy => "this mod is not compatible with Dawntrail. Get an updated version, if possible, or disable it.",
+ SanityCheckResult.IoError => "Cannot read the modded file.",
+ SanityCheckResult.NotFound => "The modded file does not exist.",
+ SanityCheckResult.Legacy => "This mod is not compatible with Dawntrail. Get an updated version, if possible, or disable it.",
_ => string.Empty,
};
diff --git a/Penumbra/Services/MessageService.cs b/Penumbra/Services/MessageService.cs
index 08118483..a35a67f1 100644
--- a/Penumbra/Services/MessageService.cs
+++ b/Penumbra/Services/MessageService.cs
@@ -2,10 +2,14 @@ using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
+using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using OtterGui.Log;
using OtterGui.Services;
+using Penumbra.Mods.Manager;
+using Penumbra.String.Classes;
+using Notification = OtterGui.Classes.Notification;
namespace Penumbra.Services;
@@ -38,4 +42,16 @@ public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INoti
Message = payload,
});
}
+
+ public void PrintFileWarning(ModManager modManager, string fullPath, Utf8GamePath originalGamePath, string messageComplement)
+ {
+ // Don't warn for files managed by other plugins, or files we aren't sure about.
+ if (!modManager.TryIdentifyPath(fullPath, out var mod, out _))
+ return;
+
+ AddTaggedMessage($"{fullPath}.{messageComplement}",
+ new Notification(
+ $"Cowardly refusing to load replacement for {originalGamePath.Filename().ToString().ToLowerInvariant()} by {mod.Name}{(messageComplement.Length > 0 ? ":\n" : ".")}{messageComplement}",
+ NotificationType.Warning, 10000));
+ }
}
diff --git a/Penumbra/UI/ChatWarningService.cs b/Penumbra/UI/ChatWarningService.cs
deleted file mode 100644
index 84ede2fb..00000000
--- a/Penumbra/UI/ChatWarningService.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using Dalamud.Plugin.Services;
-using OtterGui.Services;
-using Penumbra.Mods.Manager;
-using Penumbra.String.Classes;
-
-namespace Penumbra.UI;
-
-public sealed class ChatWarningService(IChatGui chatGui, IClientState clientState, ModManager modManager) : IUiService
-{
- private readonly Dictionary _lastFileWarnings = [];
- private int _lastFileWarningsCleanCounter;
-
- private const int LastFileWarningsCleanCycle = 100;
- private static readonly TimeSpan LastFileWarningsMaxAge = new(1, 0, 0);
-
- public void CleanLastFileWarnings(bool force)
- {
- if (!force)
- {
- _lastFileWarningsCleanCounter = (_lastFileWarningsCleanCounter + 1) % LastFileWarningsCleanCycle;
- if (_lastFileWarningsCleanCounter != 0)
- return;
- }
-
- var expiredDate = DateTime.Now - LastFileWarningsMaxAge;
- var toRemove = new HashSet();
- foreach (var (key, value) in _lastFileWarnings)
- {
- if (value.Item1 <= expiredDate)
- toRemove.Add(key);
- }
- foreach (var key in toRemove)
- _lastFileWarnings.Remove(key);
- }
-
- public void PrintFileWarning(string fullPath, Utf8GamePath originalGamePath, string messageComplement)
- {
- CleanLastFileWarnings(true);
-
- // Don't warn twice for the same file within a certain time interval unless the reason changed.
- if (_lastFileWarnings.TryGetValue(fullPath, out var lastWarning) && lastWarning.Item2 == messageComplement)
- return;
-
- // Don't warn for files managed by other plugins, or files we aren't sure about.
- if (!modManager.TryIdentifyPath(fullPath, out var mod, out _))
- return;
-
- // Don't warn if there's no local player (as an approximation of no chat), so as not to trigger the cooldown.
- if (clientState.LocalPlayer == null)
- return;
-
- // The wording is an allusion to tar's "Cowardly refusing to create an empty archive"
- chatGui.PrintError($"Cowardly refusing to load replacement for {originalGamePath.Filename().ToString().ToLowerInvariant()} by {mod.Name}{(messageComplement.Length > 0 ? ": " : ".")}{messageComplement}", "Penumbra");
- _lastFileWarnings[fullPath] = (DateTime.Now, messageComplement);
- }
-}