From 9dd12f4a71aba6f10d8364024cab3db8e1da5d5c Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Mon, 20 Jun 2022 10:46:39 +0900 Subject: [PATCH 1/6] Take segmented read into consideration when changing CRC32 values after path replacement --- .../Loader/ResourceLoader.Replacement.cs | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index 09572313..5720c089 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -1,12 +1,14 @@ using System; using System.Diagnostics; using System.Linq; +using System.Runtime.InteropServices; using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; +using Penumbra.GameData.Util; using Penumbra.Interop.Structs; using FileMode = Penumbra.Interop.Structs.FileMode; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; @@ -17,31 +19,44 @@ public unsafe partial class ResourceLoader { // Resources can be obtained synchronously and asynchronously. We need to change behaviour in both cases. // Both work basically the same, so we can reduce the main work to one function used by both hooks. + + [StructLayout( LayoutKind.Explicit )] + public struct GetResourceParameters + { + [FieldOffset( 16 )] + public uint SegmentOffset; + + [FieldOffset( 20 )] + public uint SegmentLength; + + public bool IsPartialRead => SegmentLength != 0; + } + public delegate ResourceHandle* GetResourceSyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId, - ResourceType* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown ); + ResourceType* pResourceType, int* pResourceHash, byte* pPath, GetResourceParameters* pGetResParams ); [Signature( "E8 ?? ?? 00 00 48 8D 8F ?? ?? 00 00 48 89 87 ?? ?? 00 00", DetourName = "GetResourceSyncDetour" )] public Hook< GetResourceSyncPrototype > GetResourceSyncHook = null!; public delegate ResourceHandle* GetResourceAsyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId, - ResourceType* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown, bool isUnknown ); + ResourceType* pResourceType, int* pResourceHash, byte* pPath, GetResourceParameters* pGetResParams, bool isUnknown ); [Signature( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00", DetourName = "GetResourceAsyncDetour" )] public Hook< GetResourceAsyncPrototype > GetResourceAsyncHook = null!; private ResourceHandle* GetResourceSyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType, - int* resourceHash, byte* path, void* unk ) - => GetResourceHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, unk, false ); + int* resourceHash, byte* path, GetResourceParameters* pGetResParams ) + => GetResourceHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, false ); private ResourceHandle* GetResourceAsyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType, - int* resourceHash, byte* path, void* unk, bool isUnk ) - => GetResourceHandler( false, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); + int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk ) + => GetResourceHandler( false, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); private ResourceHandle* CallOriginalHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, - ResourceType* resourceType, int* resourceHash, byte* path, void* unk, bool isUnk ) + ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk ) => isSync - ? GetResourceSyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk ) - : GetResourceAsyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); + ? GetResourceSyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams ) + : GetResourceAsyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); [Conditional( "DEBUG" )] @@ -56,15 +71,15 @@ public unsafe partial class ResourceLoader private event Action< Utf8GamePath, FullPath?, object? >? PathResolved; private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, - ResourceType* resourceType, int* resourceHash, byte* path, void* unk, bool isUnk ) + ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk ) { if( !Utf8GamePath.FromPointer( path, out var gamePath ) ) { PluginLog.Error( "Could not create GamePath from resource path." ); - return CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); + return CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); } - CompareHash( gamePath.Path.Crc32, *resourceHash, gamePath ); + CompareHash( ComputeHash( gamePath.Path, pGetResParams ), *resourceHash, gamePath ); ResourceRequested?.Invoke( gamePath, isSync ); @@ -73,15 +88,15 @@ public unsafe partial class ResourceLoader PathResolved?.Invoke( gamePath, resolvedPath, data ); if( resolvedPath == null ) { - var retUnmodified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); + var retUnmodified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data ); return retUnmodified; } // Replace the hash and path with the correct one for the replacement. - *resourceHash = resolvedPath.Value.InternalName.Crc32; + *resourceHash = ComputeHash( resolvedPath.Value.InternalName, pGetResParams ); path = resolvedPath.Value.InternalName.Path; - var retModified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); + var retModified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retModified, gamePath, resolvedPath.Value, data ); return retModified; } @@ -228,4 +243,22 @@ public unsafe partial class ResourceLoader GetResourceSyncHook.Dispose(); GetResourceAsyncHook.Dispose(); } + + private int ComputeHash( Utf8String path, GetResourceParameters* pGetResParams ) + { + if( pGetResParams == null || !pGetResParams->IsPartialRead ) + return path.Crc32; + + // When the game requests file only partially, crc32 includes that information, in format of: + // path/to/file.ext.hex_offset.hex_size + // ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000 + var pathWithSegmentInfo = Utf8String.Join( + 0x2e, + path, + Utf8String.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ), + Utf8String.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true ) + ); + Functions.ComputeCrc32AsciiLowerAndSize( pathWithSegmentInfo.Path, out var crc32, out _, out _ ); + return crc32; + } } \ No newline at end of file From c49fce4487e319b634c7ea873f4cb2115063ae30 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Mon, 20 Jun 2022 10:56:28 +0900 Subject: [PATCH 2/6] Remove MusicManager/DisableSoundStreaming --- Penumbra/Collections/ModCollection.Cache.cs | 6 --- Penumbra/Configuration.cs | 1 - Penumbra/Interop/MusicManager.cs | 41 ------------------- Penumbra/Mods/Mod.Files.cs | 7 ---- Penumbra/Penumbra.cs | 8 ---- Penumbra/UI/ConfigWindow.DebugTab.cs | 3 +- .../UI/ConfigWindow.SettingsTab.Advanced.cs | 31 -------------- 7 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 Penumbra/Interop/MusicManager.cs diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 62b92f7f..55a92c91 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -308,12 +308,6 @@ public partial class ModCollection { foreach( var (path, file) in subMod.Files.Concat( subMod.FileSwaps ) ) { - // Skip all filtered files - if( Mod.FilterFile( path ) ) - { - continue; - } - AddFile( path, file, parentMod ); } diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 3d38a069..14879c2e 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -52,7 +52,6 @@ public partial class Configuration : IPluginConfiguration public bool FixMainWindow { get; set; } = false; public bool ShowAdvanced { get; set; } public bool AutoDeduplicateOnImport { get; set; } = false; - public bool DisableSoundStreaming { get; set; } = true; public bool EnableHttpApi { get; set; } public string DefaultModImportPath { get; set; } = string.Empty; diff --git a/Penumbra/Interop/MusicManager.cs b/Penumbra/Interop/MusicManager.cs deleted file mode 100644 index 7e7568d8..00000000 --- a/Penumbra/Interop/MusicManager.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Dalamud.Logging; -using Dalamud.Utility.Signatures; - -namespace Penumbra.Interop; - -// Use this to disable streaming of specific soundfiles, -// which will allow replacement of .scd files. -public unsafe class MusicManager -{ - // The wildcard is the offset in framework to the MusicManager in Framework. - [Signature( "48 8B 8E ?? ?? ?? ?? 39 78 20 0F 94 C2 45 33 C0", ScanType = ScanType.Text )] - private readonly IntPtr _musicInitCallLocation = IntPtr.Zero; - - private readonly IntPtr _musicManager; - - public MusicManager() - { - SignatureHelper.Initialise( this ); - var framework = Dalamud.Framework.Address.BaseAddress; - var musicManagerOffset = *( int* )( _musicInitCallLocation + 3 ); - _musicManager = *( IntPtr* )( framework + musicManagerOffset ); - PluginLog.Debug( "MusicManager found at 0x{Location:X16}", _musicManager.ToInt64() ); - } - - public bool StreamingEnabled - { - get => *( bool* )( _musicManager + 50 ); - private set - { - PluginLog.Debug( value ? "Music streaming enabled." : "Music streaming disabled." ); - *( bool* )( _musicManager + 50 ) = value; - } - } - - public void EnableStreaming() - => StreamingEnabled = true; - - public void DisableStreaming() - => StreamingEnabled = false; -} \ No newline at end of file diff --git a/Penumbra/Mods/Mod.Files.cs b/Penumbra/Mods/Mod.Files.cs index 439634c5..e1004978 100644 --- a/Penumbra/Mods/Mod.Files.cs +++ b/Penumbra/Mods/Mod.Files.cs @@ -70,13 +70,6 @@ public partial class Mod .ToList(); } - // Filter invalid files. - // If audio streaming is not disabled, replacing .scd files crashes the game, - // so only add those files if it is disabled. - public static bool FilterFile( Utf8GamePath gamePath ) - => !Penumbra.Config.DisableSoundStreaming - && gamePath.Path.EndsWith( '.', 's', 'c', 'd' ); - private static IModGroup? LoadModGroup( FileInfo file, DirectoryInfo basePath ) { if( !File.Exists( file.FullName ) ) diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index b892a602..4d0c253f 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -112,7 +112,6 @@ public class Penumbra : IDisposable public readonly ResourceLogger ResourceLogger; public readonly PathResolver PathResolver; - public readonly MusicManager MusicManager; public readonly ObjectReloader ObjectReloader; public readonly ModFileSystem ModFileSystem; public readonly PenumbraApi Api; @@ -131,12 +130,6 @@ public class Penumbra : IDisposable Backup.CreateBackup( PenumbraBackupFiles() ); Config = Configuration.Load(); - MusicManager = new MusicManager(); - if( Config.DisableSoundStreaming ) - { - MusicManager.DisableStreaming(); - } - ResidentResources = new ResidentResourceManager(); TempMods = new TempModManager(); MetaFileManager = new MetaFileManager(); @@ -462,7 +455,6 @@ public class Penumbra : IDisposable sb.AppendFormat( "> **`Plugin Version: `** {0}\n", Version ); sb.AppendFormat( "> **`Commit Hash: `** {0}\n", CommitHash ); sb.AppendFormat( "> **`Enable Mods: `** {0}\n", Config.EnableMods ); - sb.AppendFormat( "> **`Enable Sound Modification: `** {0}\n", Config.DisableSoundStreaming ); sb.AppendFormat( "> **`Enable HTTP API: `** {0}\n", Config.EnableHttpApi ); sb.AppendFormat( "> **`Root Directory: `** `{0}`, {1}\n", Config.ModDirectory, exists ? "Exists" : "Not Existing" ); sb.AppendFormat( "> **`Free Drive Space: `** {0}\n", diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 95be2069..ebd1b67f 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -99,12 +99,11 @@ public partial class ConfigWindow PrintValue( "Mod Manager BasePath Exists", Directory.Exists( manager.BasePath.FullName ).ToString() ); PrintValue( "Mod Manager Valid", manager.Valid.ToString() ); PrintValue( "Path Resolver Enabled", _window._penumbra.PathResolver.Enabled.ToString() ); - PrintValue( "Music Manager Streaming Disabled", ( !_window._penumbra.MusicManager.StreamingEnabled ).ToString() ); PrintValue( "Web Server Enabled", ( _window._penumbra.WebServer != null ).ToString() ); } // Draw all resources currently replaced by Penumbra and (if existing) the resources they replace. - // Resources are collected by iterating through the + // Resources are collected by iterating through the private static unsafe void DrawDebugTabReplacedResources() { if( !ImGui.CollapsingHeader( "Replaced Resources" ) ) diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs index 13fcce3e..8b52adfb 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs @@ -24,7 +24,6 @@ public partial class ConfigWindow "Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.", Penumbra.Config.AutoDeduplicateOnImport, v => Penumbra.Config.AutoDeduplicateOnImport = v ); DrawRequestedResourceLogging(); - DrawDisableSoundStreamingBox(); DrawEnableHttpApiBox(); DrawEnableDebugModeBox(); DrawEnableFullResourceLoggingBox(); @@ -62,36 +61,6 @@ public partial class ConfigWindow } } - // Toggling audio streaming will need to apply to the music manager - // and rediscover mods due to determining whether .scds will be loaded or not. - private void DrawDisableSoundStreamingBox() - { - var tmp = Penumbra.Config.DisableSoundStreaming; - if( ImGui.Checkbox( "##streaming", ref tmp ) && tmp != Penumbra.Config.DisableSoundStreaming ) - { - Penumbra.Config.DisableSoundStreaming = tmp; - Penumbra.Config.Save(); - if( tmp ) - { - _window._penumbra.MusicManager.DisableStreaming(); - } - else - { - _window._penumbra.MusicManager.EnableStreaming(); - } - - Penumbra.ModManager.DiscoverMods(); - } - - ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker( "Enable Sound Modification", - "Disable streaming in the games audio engine. The game enables this by default, and Penumbra should disable it.\n" - + "If this is unchecked, you can not replace sound files in the game (*.scd files), they will be ignored by Penumbra.\n\n" - + "Only touch this if you experience sound problems like audio stuttering.\n" - + "If you toggle this, make sure no modified or to-be-modified sound file is currently playing or was recently playing, else you might crash.\n" - + "You might need to restart your game for this to fully take effect." ); - } - // Creates and destroys the web server when toggled. private void DrawEnableHttpApiBox() { From 30335fb5d7d9b0b5fbce2fcf4c09195800005920 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Tue, 21 Jun 2022 00:29:52 +0900 Subject: [PATCH 3/6] Remove redundant crc32 calc --- Penumbra/Interop/Loader/ResourceLoader.Replacement.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index 5720c089..6a9fb9f3 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -252,13 +252,11 @@ public unsafe partial class ResourceLoader // When the game requests file only partially, crc32 includes that information, in format of: // path/to/file.ext.hex_offset.hex_size // ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000 - var pathWithSegmentInfo = Utf8String.Join( - 0x2e, + return Utf8String.Join( + (byte)'.', path, Utf8String.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ), Utf8String.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true ) - ); - Functions.ComputeCrc32AsciiLowerAndSize( pathWithSegmentInfo.Path, out var crc32, out _, out _ ); - return crc32; + ).Crc32; } } \ No newline at end of file From 6ebf550284030a9c4e333613ed11bfdd2fd36a11 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jun 2022 18:26:40 +0200 Subject: [PATCH 4/6] Use Ordinal comparisons --- OtterGui | 2 +- Penumbra.GameData/ByteString/FullPath.cs | 4 ++-- Penumbra.GameData/ByteString/Utf8String.Construction.cs | 4 ++-- Penumbra.GameData/Util/GamePath.cs | 4 ++-- Penumbra/Collections/CollectionManager.cs | 2 +- Penumbra/Collections/ModCollection.Cache.cs | 2 +- Penumbra/Interop/Loader/ResourceLogger.cs | 2 +- Penumbra/Mods/Manager/Mod.Manager.Options.cs | 2 +- Penumbra/Mods/Manager/Mod.Manager.Root.cs | 2 +- Penumbra/Penumbra.cs | 6 +++--- Penumbra/UI/Classes/ModEditWindow.Files.cs | 2 +- Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs | 3 +-- Penumbra/UI/ConfigWindow.ChangedItemsTab.cs | 2 +- Penumbra/UI/ConfigWindow.ResourceTab.cs | 2 +- 14 files changed, 19 insertions(+), 20 deletions(-) diff --git a/OtterGui b/OtterGui index 6ce8ca81..fa833869 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 6ce8ca816678e7a363f9f4a6f43f009f8d79c070 +Subproject commit fa83386909ad0034f5ed7ea90d8bcedf6e8ba748 diff --git a/Penumbra.GameData/ByteString/FullPath.cs b/Penumbra.GameData/ByteString/FullPath.cs index 1d13e969..2284bf98 100644 --- a/Penumbra.GameData/ByteString/FullPath.cs +++ b/Penumbra.GameData/ByteString/FullPath.cs @@ -72,9 +72,9 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath > => obj switch { FullPath p => InternalName?.CompareTo( p.InternalName ) ?? -1, - FileInfo f => string.Compare( FullName, f.FullName, StringComparison.InvariantCultureIgnoreCase ), + FileInfo f => string.Compare( FullName, f.FullName, StringComparison.OrdinalIgnoreCase ), Utf8String u => InternalName?.CompareTo( u ) ?? -1, - string s => string.Compare( FullName, s, StringComparison.InvariantCultureIgnoreCase ), + string s => string.Compare( FullName, s, StringComparison.OrdinalIgnoreCase ), _ => -1, }; diff --git a/Penumbra.GameData/ByteString/Utf8String.Construction.cs b/Penumbra.GameData/ByteString/Utf8String.Construction.cs index f6c47a8b..1e6ab325 100644 --- a/Penumbra.GameData/ByteString/Utf8String.Construction.cs +++ b/Penumbra.GameData/ByteString/Utf8String.Construction.cs @@ -38,14 +38,14 @@ public sealed unsafe partial class Utf8String : IDisposable // Can throw ArgumentOutOfRange if length is higher than max length. // The Crc32 will be computed. public static Utf8String FromByteStringUnsafe( byte* path, int length, bool isNullTerminated, bool? isLower = null, bool? isAscii = false ) - => FromSpanUnsafe( new ReadOnlySpan< byte >( path, length ), isNullTerminated, isLower, isAscii ); + => new Utf8String().Setup( path, length, null, isNullTerminated, false, isLower, isAscii ); // Same as above, just with a span. public static Utf8String FromSpanUnsafe( ReadOnlySpan< byte > path, bool isNullTerminated, bool? isLower = null, bool? isAscii = false ) { fixed( byte* ptr = path ) { - return new Utf8String().Setup( ptr, path.Length, null, isNullTerminated, false, isLower, isAscii ); + return FromByteStringUnsafe( ptr, path.Length, isNullTerminated, isLower, isAscii ); } } diff --git a/Penumbra.GameData/Util/GamePath.cs b/Penumbra.GameData/Util/GamePath.cs index ded35038..591b79fc 100644 --- a/Penumbra.GameData/Util/GamePath.cs +++ b/Penumbra.GameData/Util/GamePath.cs @@ -70,8 +70,8 @@ public readonly struct GamePath : IComparable { return rhs switch { - string path => string.Compare( _path, path, StringComparison.InvariantCulture ), - GamePath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ), + string path => string.Compare( _path, path, StringComparison.Ordinal ), + GamePath path => string.Compare( _path, path._path, StringComparison.Ordinal ), _ => -1, }; } diff --git a/Penumbra/Collections/CollectionManager.cs b/Penumbra/Collections/CollectionManager.cs index 411e7de5..d1c5b2a2 100644 --- a/Penumbra/Collections/CollectionManager.cs +++ b/Penumbra/Collections/CollectionManager.cs @@ -48,7 +48,7 @@ public partial class ModCollection // Obtain a collection case-independently by name. public bool ByName( string name, [NotNullWhen( true )] out ModCollection? collection ) - => _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.InvariantCultureIgnoreCase ), out collection ); + => _collections.FindFirst( c => string.Equals( c.Name, name, StringComparison.OrdinalIgnoreCase ), out collection ); // Default enumeration skips the empty collection. public IEnumerator< ModCollection > GetEnumerator() diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 55a92c91..e8f72e32 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -89,7 +89,7 @@ public partial class ModCollection } var iterator = ResolvedFiles - .Where( f => string.Equals( f.Value.Path.FullName, needle, StringComparison.InvariantCultureIgnoreCase ) ) + .Where( f => string.Equals( f.Value.Path.FullName, needle, StringComparison.OrdinalIgnoreCase ) ) .Select( kvp => kvp.Key ); // For files that are not rooted, try to add themselves. diff --git a/Penumbra/Interop/Loader/ResourceLogger.cs b/Penumbra/Interop/Loader/ResourceLogger.cs index 47be8a68..278aea7d 100644 --- a/Penumbra/Interop/Loader/ResourceLogger.cs +++ b/Penumbra/Interop/Loader/ResourceLogger.cs @@ -88,7 +88,7 @@ public class ResourceLogger : IDisposable private string? Match( Utf8String data ) { var s = data.ToString(); - return Filter.Length == 0 || ( _filterRegex?.IsMatch( s ) ?? s.Contains( Filter, StringComparison.InvariantCultureIgnoreCase ) ) + return Filter.Length == 0 || ( _filterRegex?.IsMatch( s ) ?? s.Contains( Filter, StringComparison.OrdinalIgnoreCase ) ) ? s : null; } diff --git a/Penumbra/Mods/Manager/Mod.Manager.Options.cs b/Penumbra/Mods/Manager/Mod.Manager.Options.cs index 38ddc97f..86d97fd0 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.Options.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.Options.cs @@ -284,7 +284,7 @@ public sealed partial class Mod var path = newName.RemoveInvalidPathSymbols(); if( path.Length == 0 || mod.Groups.Any( o => !ReferenceEquals( o, group ) - && string.Equals( o.Name.RemoveInvalidPathSymbols(), path, StringComparison.InvariantCultureIgnoreCase ) ) ) + && string.Equals( o.Name.RemoveInvalidPathSymbols(), path, StringComparison.OrdinalIgnoreCase ) ) ) { if( message ) { diff --git a/Penumbra/Mods/Manager/Mod.Manager.Root.cs b/Penumbra/Mods/Manager/Mod.Manager.Root.cs index 92587bfd..b331daca 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.Root.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.Root.cs @@ -26,7 +26,7 @@ public sealed partial class Mod // Also checks if the directory is available and tries to create it if it is not. private void SetBaseDirectory( string newPath, bool firstTime ) { - if( !firstTime && string.Equals( newPath, Penumbra.Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) ) + if( !firstTime && string.Equals( newPath, Penumbra.Config.ModDirectory, StringComparison.OrdinalIgnoreCase ) ) { return; } diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 4d0c253f..c715eba5 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -78,7 +78,7 @@ public class MainClass : IDalamudPlugin { #if !DEBUG var checkedDirectory = Dalamud.PluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Name; - var ret = checkedDirectory?.Equals( "installedPlugins", StringComparison.InvariantCultureIgnoreCase ) ?? false; + var ret = checkedDirectory?.Equals( "installedPlugins", StringComparison.OrdinalIgnoreCase ) ?? false; if (!ret) PluginLog.Error($"Penumbra is not correctly installed. Application loaded from \"{Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName}\"." ); return !ret; @@ -310,12 +310,12 @@ public class Penumbra : IDisposable ShutdownWebServer(); } - public bool SetCollection( string type, string collectionName ) + public static bool SetCollection( string type, string collectionName ) { type = type.ToLowerInvariant(); collectionName = collectionName.ToLowerInvariant(); - var collection = string.Equals( collectionName, ModCollection.Empty.Name, StringComparison.InvariantCultureIgnoreCase ) + var collection = string.Equals( collectionName, ModCollection.Empty.Name, StringComparison.OrdinalIgnoreCase ) ? ModCollection.Empty : CollectionManager[ collectionName ]; if( collection == null ) diff --git a/Penumbra/UI/Classes/ModEditWindow.Files.cs b/Penumbra/UI/Classes/ModEditWindow.Files.cs index 78f27e3c..72f69ae6 100644 --- a/Penumbra/UI/Classes/ModEditWindow.Files.cs +++ b/Penumbra/UI/Classes/ModEditWindow.Files.cs @@ -25,7 +25,7 @@ public partial class ModEditWindow private int _folderSkip = 0; private bool CheckFilter( Mod.Editor.FileRegistry registry ) - => _fileFilter.IsEmpty || registry.File.FullName.Contains( _fileFilter.Lower, StringComparison.InvariantCultureIgnoreCase ); + => _fileFilter.IsEmpty || registry.File.FullName.Contains( _fileFilter.Lower, StringComparison.OrdinalIgnoreCase ); private bool CheckFilter( (Mod.Editor.FileRegistry, int) p ) => CheckFilter( p.Item1 ); diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs b/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs index 039262fb..91d64c6e 100644 --- a/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs +++ b/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; @@ -22,7 +21,7 @@ public partial class ModFileSystemSelector public ColorId Color; } - private const StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase; + private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase; private LowerString _modFilter = LowerString.Empty; private int _filterType = -1; private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods; diff --git a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs index b9105bda..c763c505 100644 --- a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs +++ b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs @@ -26,7 +26,7 @@ public partial class ConfigWindow bool FilterChangedItem( KeyValuePair< string, (SingleArray< IMod >, object?) > item ) => ( _changedItemFilter.IsEmpty || ChangedItemName( item.Key, item.Value.Item2 ) - .Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ) ) + .Contains( _changedItemFilter.Lower, StringComparison.OrdinalIgnoreCase ) ) && ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) ); void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< IMod >, object?) > item ) diff --git a/Penumbra/UI/ConfigWindow.ResourceTab.cs b/Penumbra/UI/ConfigWindow.ResourceTab.cs index b4deea18..662a98dd 100644 --- a/Penumbra/UI/ConfigWindow.ResourceTab.cs +++ b/Penumbra/UI/ConfigWindow.ResourceTab.cs @@ -87,7 +87,7 @@ public partial class ConfigWindow { // Filter unwanted names. if( _resourceManagerFilter.Length != 0 - && !r->FileName.ToString().Contains( _resourceManagerFilter, StringComparison.InvariantCultureIgnoreCase ) ) + && !r->FileName.ToString().Contains( _resourceManagerFilter, StringComparison.OrdinalIgnoreCase ) ) { return; } From c4f82435bf472c6577ddf298bf251b8eecf1b2e1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jun 2022 18:27:13 +0200 Subject: [PATCH 5/6] Fix for temporary collections. --- Penumbra/Collections/ModCollection.Inheritance.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Penumbra/Collections/ModCollection.Inheritance.cs b/Penumbra/Collections/ModCollection.Inheritance.cs index ba621337..395da60a 100644 --- a/Penumbra/Collections/ModCollection.Inheritance.cs +++ b/Penumbra/Collections/ModCollection.Inheritance.cs @@ -134,6 +134,11 @@ public partial class ModCollection { get { + if( Index <= 0 ) + { + return ( ModSettings.Empty, this ); + } + foreach( var collection in GetFlattenedInheritance() ) { var settings = collection._settings[ idx ]; From 00c11b49f0fcf053b2dbde86bfcb5d676f34f94b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jun 2022 18:27:41 +0200 Subject: [PATCH 6/6] Use file type enum for Crc64 handling. --- Penumbra/Interop/Loader/ResourceLoader.Replacement.cs | 4 ++-- Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index 6a9fb9f3..8390d653 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -68,7 +68,7 @@ public unsafe partial class ResourceLoader } } - private event Action< Utf8GamePath, FullPath?, object? >? PathResolved; + private event Action< Utf8GamePath, ResourceType, FullPath?, object? >? PathResolved; private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk ) @@ -85,7 +85,7 @@ public unsafe partial class ResourceLoader // If no replacements are being made, we still want to be able to trigger the event. var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash ); - PathResolved?.Invoke( gamePath, resolvedPath, data ); + PathResolved?.Invoke( gamePath, *resourceType, resolvedPath, data ); if( resolvedPath == null ) { var retUnmodified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); diff --git a/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs index 42332f16..2393a87d 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs @@ -4,6 +4,7 @@ using Dalamud.Hooking; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using Penumbra.GameData.ByteString; +using Penumbra.GameData.Enums; namespace Penumbra.Interop.Loader; @@ -67,11 +68,11 @@ public unsafe partial class ResourceLoader : LoadMdlFileExternHook.Original( resourceHandle, unk1, unk2, ptr ); - private void AddCrc( Utf8GamePath _, FullPath? path, object? _2 ) + private void AddCrc( Utf8GamePath _, ResourceType type, FullPath? path, object? _2 ) { - if( path is { Extension: ".mdl" or ".tex" } p ) + if( path.HasValue && type is ResourceType.Mdl or ResourceType.Tex ) { - _customFileCrc.Add( p.Crc64 ); + _customFileCrc.Add( path.Value.Crc64 ); } }