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); - } -}