diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index 8ca9aa36..3f0ed27b 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -1,3 +1,4 @@ +using Dalamud.Interface.ImGuiNotification; using OtterGui; using OtterGui.Classes; using Penumbra.Meta.Manipulations; @@ -274,6 +275,24 @@ public sealed class CollectionCache : IDisposable _manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod); } + private static bool IsRedirectionSupported(Utf8GamePath path, IMod mod) + { + var ext = path.Extension().AsciiToLower().ToString(); + switch (ext) + { + case ".atch" or ".eqp" or ".eqdp" or ".est" or ".gmp" or ".cmp" or ".imc": + Penumbra.Messager.NotificationMessage( + $"Redirection of {ext} files for {mod.Name} is unsupported. Please use the corresponding meta manipulations instead.", + NotificationType.Warning); + return false; + case ".lvb" or ".lgb" or ".sgb": + Penumbra.Messager.NotificationMessage($"Redirection of {ext} files for {mod.Name} is unsupported as this breaks the game.", + NotificationType.Warning); + return false; + default: return true; + } + } + // Add a specific file redirection, handling potential conflicts. // For different mods, higher mod priority takes precedence before option group priority, // which takes precedence before option priority, which takes precedence before ordering. @@ -283,6 +302,9 @@ public sealed class CollectionCache : IDisposable if (!CheckFullPath(path, file)) return; + if (!IsRedirectionSupported(path, mod)) + return; + try { if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) @@ -342,8 +364,9 @@ public sealed class CollectionCache : IDisposable // Returns if the added mod takes priority before the existing mod. private bool AddConflict(object data, IMod addedMod, IMod existingMod) { - var addedPriority = addedMod.Index >= 0 ? _collection.GetActualSettings(addedMod.Index).Settings!.Priority : addedMod.Priority; - var existingPriority = existingMod.Index >= 0 ? _collection.GetActualSettings(existingMod.Index).Settings!.Priority : existingMod.Priority; + var addedPriority = addedMod.Index >= 0 ? _collection.GetActualSettings(addedMod.Index).Settings!.Priority : addedMod.Priority; + var existingPriority = + existingMod.Index >= 0 ? _collection.GetActualSettings(existingMod.Index).Settings!.Priority : existingMod.Priority; if (existingPriority < addedPriority) { @@ -427,7 +450,8 @@ public sealed class CollectionCache : IDisposable if (!_changedItems.TryGetValue(name, out var data)) _changedItems.Add(name, (new SingleArray(mod), obj)); else if (!data.Item1.Contains(mod)) - _changedItems[name] = (data.Item1.Append(mod), obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y ? x + y : obj); + _changedItems[name] = (data.Item1.Append(mod), + obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y ? x + y : obj); else if (obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y) _changedItems[name] = (data.Item1, x + y); } diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index c46759c7..ec48e608 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -171,8 +171,7 @@ public class CollectionCacheManager : IDisposable, IService try { ResolvedFileChanged.Invoke(collection, ResolvedFileChanged.Type.FullRecomputeStart, Utf8GamePath.Empty, FullPath.Empty, - FullPath.Empty, - null); + FullPath.Empty, null); cache.ResolvedFiles.Clear(); cache.Meta.Reset(); cache.ConflictDict.Clear(); diff --git a/Penumbra/Interop/PathResolving/PathResolver.cs b/Penumbra/Interop/PathResolving/PathResolver.cs index 0b6c8340..8e5504d5 100644 --- a/Penumbra/Interop/PathResolving/PathResolver.cs +++ b/Penumbra/Interop/PathResolving/PathResolver.cs @@ -49,42 +49,38 @@ public class PathResolver : IDisposable, IService if (!_config.EnableMods) return (null, ResolveData.Invalid); - // Do not allow manipulating layers to prevent very obvious cheating and softlocks. - if (resourceType is ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb) - return (null, ResolveData.Invalid); - - // Prevent .atch loading to prevent crashes on outdated .atch files. TODO: handle atch modding differently. - if (resourceType is ResourceType.Atch) - return ResolveAtch(path); - - return category switch + return resourceType switch { - // Only Interface collection. - ResourceCategory.Ui => ResolveUi(path), - // Never allow changing scripts. - ResourceCategory.UiScript => (null, ResolveData.Invalid), - ResourceCategory.GameScript => (null, ResolveData.Invalid), - // Use actual resolving. - ResourceCategory.Chara => Resolve(path, resourceType), - ResourceCategory.Shader => ResolveShader(path, resourceType), - ResourceCategory.Vfx => Resolve(path, resourceType), - ResourceCategory.Sound => Resolve(path, resourceType), - // EXD Modding in general should probably be prohibited but is currently used for fan translations. - // We prevent WebURL specifically because it technically allows launching arbitrary programs / to execute arbitrary code. - ResourceCategory.Exd => path.Path.StartsWith("exd/weburl"u8) - ? (null, ResolveData.Invalid) - : DefaultResolver(path), - // None of these files are ever associated with specific characters, - // always use the default resolver for now, - // except that common/font is conceptually more UI. - ResourceCategory.Common => path.Path.StartsWith("common/font"u8) - ? ResolveUi(path) - : DefaultResolver(path), - ResourceCategory.BgCommon => DefaultResolver(path), - ResourceCategory.Bg => DefaultResolver(path), - ResourceCategory.Cut => DefaultResolver(path), - ResourceCategory.Music => DefaultResolver(path), - _ => DefaultResolver(path), + // Do not allow manipulating layers to prevent very obvious cheating and softlocks. + ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb => (null, ResolveData.Invalid), + // Prevent .atch loading to prevent crashes on outdated .atch files. + ResourceType.Atch => ResolveAtch(path), + + _ => category switch + { + // Only Interface collection. + ResourceCategory.Ui => ResolveUi(path), + // Never allow changing scripts. + ResourceCategory.UiScript => (null, ResolveData.Invalid), + ResourceCategory.GameScript => (null, ResolveData.Invalid), + // Use actual resolving. + ResourceCategory.Chara => Resolve(path, resourceType), + ResourceCategory.Shader => ResolveShader(path, resourceType), + ResourceCategory.Vfx => Resolve(path, resourceType), + ResourceCategory.Sound => Resolve(path, resourceType), + // EXD Modding in general should probably be prohibited but is currently used for fan translations. + // We prevent WebURL specifically because it technically allows launching arbitrary programs / to execute arbitrary code. + ResourceCategory.Exd => path.Path.StartsWith("exd/weburl"u8) ? (null, ResolveData.Invalid) : DefaultResolver(path), + // None of these files are ever associated with specific characters, + // always use the default resolver for now, + // except that common/font is conceptually more UI. + ResourceCategory.Common => path.Path.StartsWith("common/font"u8) ? ResolveUi(path) : DefaultResolver(path), + ResourceCategory.BgCommon => DefaultResolver(path), + ResourceCategory.Bg => DefaultResolver(path), + ResourceCategory.Cut => DefaultResolver(path), + ResourceCategory.Music => DefaultResolver(path), + _ => DefaultResolver(path), + } }; }