Merge branch 'xivdev:master' into master

This commit is contained in:
rootdarkarchon 2022-06-20 18:42:10 +02:00 committed by GitHub
commit b4231d2a2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 77 additions and 136 deletions

@ -1 +1 @@
Subproject commit 6ce8ca816678e7a363f9f4a6f43f009f8d79c070 Subproject commit fa83386909ad0034f5ed7ea90d8bcedf6e8ba748

View file

@ -72,9 +72,9 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
=> obj switch => obj switch
{ {
FullPath p => InternalName?.CompareTo( p.InternalName ) ?? -1, 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, Utf8String u => InternalName?.CompareTo( u ) ?? -1,
string s => string.Compare( FullName, s, StringComparison.InvariantCultureIgnoreCase ), string s => string.Compare( FullName, s, StringComparison.OrdinalIgnoreCase ),
_ => -1, _ => -1,
}; };

View file

@ -38,14 +38,14 @@ public sealed unsafe partial class Utf8String : IDisposable
// Can throw ArgumentOutOfRange if length is higher than max length. // Can throw ArgumentOutOfRange if length is higher than max length.
// The Crc32 will be computed. // The Crc32 will be computed.
public static Utf8String FromByteStringUnsafe( byte* path, int length, bool isNullTerminated, bool? isLower = null, bool? isAscii = false ) 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. // Same as above, just with a span.
public static Utf8String FromSpanUnsafe( ReadOnlySpan< byte > path, bool isNullTerminated, bool? isLower = null, bool? isAscii = false ) public static Utf8String FromSpanUnsafe( ReadOnlySpan< byte > path, bool isNullTerminated, bool? isLower = null, bool? isAscii = false )
{ {
fixed( byte* ptr = path ) fixed( byte* ptr = path )
{ {
return new Utf8String().Setup( ptr, path.Length, null, isNullTerminated, false, isLower, isAscii ); return FromByteStringUnsafe( ptr, path.Length, isNullTerminated, isLower, isAscii );
} }
} }

View file

@ -70,8 +70,8 @@ public readonly struct GamePath : IComparable
{ {
return rhs switch return rhs switch
{ {
string path => string.Compare( _path, path, StringComparison.InvariantCulture ), string path => string.Compare( _path, path, StringComparison.Ordinal ),
GamePath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ), GamePath path => string.Compare( _path, path._path, StringComparison.Ordinal ),
_ => -1, _ => -1,
}; };
} }

View file

@ -48,7 +48,7 @@ public partial class ModCollection
// Obtain a collection case-independently by name. // Obtain a collection case-independently by name.
public bool ByName( string name, [NotNullWhen( true )] out ModCollection? collection ) 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. // Default enumeration skips the empty collection.
public IEnumerator< ModCollection > GetEnumerator() public IEnumerator< ModCollection > GetEnumerator()

View file

@ -89,7 +89,7 @@ public partial class ModCollection
} }
var iterator = ResolvedFiles 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 ); .Select( kvp => kvp.Key );
// For files that are not rooted, try to add themselves. // For files that are not rooted, try to add themselves.
@ -308,12 +308,6 @@ public partial class ModCollection
{ {
foreach( var (path, file) in subMod.Files.Concat( subMod.FileSwaps ) ) foreach( var (path, file) in subMod.Files.Concat( subMod.FileSwaps ) )
{ {
// Skip all filtered files
if( Mod.FilterFile( path ) )
{
continue;
}
AddFile( path, file, parentMod ); AddFile( path, file, parentMod );
} }

View file

@ -134,6 +134,11 @@ public partial class ModCollection
{ {
get get
{ {
if( Index <= 0 )
{
return ( ModSettings.Empty, this );
}
foreach( var collection in GetFlattenedInheritance() ) foreach( var collection in GetFlattenedInheritance() )
{ {
var settings = collection._settings[ idx ]; var settings = collection._settings[ idx ];

View file

@ -52,7 +52,6 @@ public partial class Configuration : IPluginConfiguration
public bool FixMainWindow { get; set; } = false; public bool FixMainWindow { get; set; } = false;
public bool ShowAdvanced { get; set; } public bool ShowAdvanced { get; set; }
public bool AutoDeduplicateOnImport { get; set; } = false; public bool AutoDeduplicateOnImport { get; set; } = false;
public bool DisableSoundStreaming { get; set; } = true;
public bool EnableHttpApi { get; set; } public bool EnableHttpApi { get; set; }
public string DefaultModImportPath { get; set; } = string.Empty; public string DefaultModImportPath { get; set; } = string.Empty;

View file

@ -1,12 +1,14 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using FileMode = Penumbra.Interop.Structs.FileMode; using FileMode = Penumbra.Interop.Structs.FileMode;
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; 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. // 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. // 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, 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" )] [Signature( "E8 ?? ?? 00 00 48 8D 8F ?? ?? 00 00 48 89 87 ?? ?? 00 00", DetourName = "GetResourceSyncDetour" )]
public Hook< GetResourceSyncPrototype > GetResourceSyncHook = null!; public Hook< GetResourceSyncPrototype > GetResourceSyncHook = null!;
public delegate ResourceHandle* GetResourceAsyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId, 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" )] [Signature( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00", DetourName = "GetResourceAsyncDetour" )]
public Hook< GetResourceAsyncPrototype > GetResourceAsyncHook = null!; public Hook< GetResourceAsyncPrototype > GetResourceAsyncHook = null!;
private ResourceHandle* GetResourceSyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType, private ResourceHandle* GetResourceSyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
int* resourceHash, byte* path, void* unk ) int* resourceHash, byte* path, GetResourceParameters* pGetResParams )
=> GetResourceHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, unk, false ); => GetResourceHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, false );
private ResourceHandle* GetResourceAsyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType, private ResourceHandle* GetResourceAsyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
int* resourceHash, byte* path, void* unk, bool isUnk ) int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk )
=> GetResourceHandler( false, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); => GetResourceHandler( false, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
private ResourceHandle* CallOriginalHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, 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 => isSync
? GetResourceSyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk ) ? GetResourceSyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams )
: GetResourceAsyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); : GetResourceAsyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
[Conditional( "DEBUG" )] [Conditional( "DEBUG" )]
@ -53,35 +68,35 @@ 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, 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 ) ) if( !Utf8GamePath.FromPointer( path, out var gamePath ) )
{ {
PluginLog.Error( "Could not create GamePath from resource path." ); 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 ); ResourceRequested?.Invoke( gamePath, isSync );
// If no replacements are being made, we still want to be able to trigger the event. // If no replacements are being made, we still want to be able to trigger the event.
var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash ); var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash );
PathResolved?.Invoke( gamePath, resolvedPath, data ); PathResolved?.Invoke( gamePath, *resourceType, resolvedPath, data );
if( resolvedPath == null ) 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 ); ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data );
return retUnmodified; return retUnmodified;
} }
// Replace the hash and path with the correct one for the replacement. // 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; 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 ); ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retModified, gamePath, resolvedPath.Value, data );
return retModified; return retModified;
} }
@ -228,4 +243,20 @@ public unsafe partial class ResourceLoader
GetResourceSyncHook.Dispose(); GetResourceSyncHook.Dispose();
GetResourceAsyncHook.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
return Utf8String.Join(
(byte)'.',
path,
Utf8String.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ),
Utf8String.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true )
).Crc32;
}
} }

View file

@ -4,6 +4,7 @@ using Dalamud.Hooking;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
namespace Penumbra.Interop.Loader; namespace Penumbra.Interop.Loader;
@ -67,11 +68,11 @@ public unsafe partial class ResourceLoader
: LoadMdlFileExternHook.Original( resourceHandle, unk1, unk2, ptr ); : 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 );
} }
} }

View file

@ -88,7 +88,7 @@ public class ResourceLogger : IDisposable
private string? Match( Utf8String data ) private string? Match( Utf8String data )
{ {
var s = data.ToString(); 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 ? s
: null; : null;
} }

View file

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

View file

@ -284,7 +284,7 @@ public sealed partial class Mod
var path = newName.RemoveInvalidPathSymbols(); var path = newName.RemoveInvalidPathSymbols();
if( path.Length == 0 if( path.Length == 0
|| mod.Groups.Any( o => !ReferenceEquals( o, group ) || 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 ) if( message )
{ {

View file

@ -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. // Also checks if the directory is available and tries to create it if it is not.
private void SetBaseDirectory( string newPath, bool firstTime ) 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; return;
} }

View file

@ -70,13 +70,6 @@ public partial class Mod
.ToList(); .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 ) private static IModGroup? LoadModGroup( FileInfo file, DirectoryInfo basePath )
{ {
if( !File.Exists( file.FullName ) ) if( !File.Exists( file.FullName ) )

View file

@ -78,7 +78,7 @@ public class MainClass : IDalamudPlugin
{ {
#if !DEBUG #if !DEBUG
var checkedDirectory = Dalamud.PluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Name; 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) if (!ret)
PluginLog.Error($"Penumbra is not correctly installed. Application loaded from \"{Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName}\"." ); PluginLog.Error($"Penumbra is not correctly installed. Application loaded from \"{Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName}\"." );
return !ret; return !ret;
@ -112,7 +112,6 @@ public class Penumbra : IDisposable
public readonly ResourceLogger ResourceLogger; public readonly ResourceLogger ResourceLogger;
public readonly PathResolver PathResolver; public readonly PathResolver PathResolver;
public readonly MusicManager MusicManager;
public readonly ObjectReloader ObjectReloader; public readonly ObjectReloader ObjectReloader;
public readonly ModFileSystem ModFileSystem; public readonly ModFileSystem ModFileSystem;
public readonly PenumbraApi Api; public readonly PenumbraApi Api;
@ -131,12 +130,6 @@ public class Penumbra : IDisposable
Backup.CreateBackup( PenumbraBackupFiles() ); Backup.CreateBackup( PenumbraBackupFiles() );
Config = Configuration.Load(); Config = Configuration.Load();
MusicManager = new MusicManager();
if( Config.DisableSoundStreaming )
{
MusicManager.DisableStreaming();
}
ResidentResources = new ResidentResourceManager(); ResidentResources = new ResidentResourceManager();
TempMods = new TempModManager(); TempMods = new TempModManager();
MetaFileManager = new MetaFileManager(); MetaFileManager = new MetaFileManager();
@ -317,12 +310,12 @@ public class Penumbra : IDisposable
ShutdownWebServer(); ShutdownWebServer();
} }
public bool SetCollection( string type, string collectionName ) public static bool SetCollection( string type, string collectionName )
{ {
type = type.ToLowerInvariant(); type = type.ToLowerInvariant();
collectionName = collectionName.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 ? ModCollection.Empty
: CollectionManager[ collectionName ]; : CollectionManager[ collectionName ];
if( collection == null ) if( collection == null )
@ -462,7 +455,6 @@ public class Penumbra : IDisposable
sb.AppendFormat( "> **`Plugin Version: `** {0}\n", Version ); sb.AppendFormat( "> **`Plugin Version: `** {0}\n", Version );
sb.AppendFormat( "> **`Commit Hash: `** {0}\n", CommitHash ); sb.AppendFormat( "> **`Commit Hash: `** {0}\n", CommitHash );
sb.AppendFormat( "> **`Enable Mods: `** {0}\n", Config.EnableMods ); 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( "> **`Enable HTTP API: `** {0}\n", Config.EnableHttpApi );
sb.AppendFormat( "> **`Root Directory: `** `{0}`, {1}\n", Config.ModDirectory, exists ? "Exists" : "Not Existing" ); sb.AppendFormat( "> **`Root Directory: `** `{0}`, {1}\n", Config.ModDirectory, exists ? "Exists" : "Not Existing" );
sb.AppendFormat( "> **`Free Drive Space: `** {0}\n", sb.AppendFormat( "> **`Free Drive Space: `** {0}\n",

View file

@ -25,7 +25,7 @@ public partial class ModEditWindow
private int _folderSkip = 0; private int _folderSkip = 0;
private bool CheckFilter( Mod.Editor.FileRegistry registry ) 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 ) private bool CheckFilter( (Mod.Editor.FileRegistry, int) p )
=> CheckFilter( p.Item1 ); => CheckFilter( p.Item1 );

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -22,7 +21,7 @@ public partial class ModFileSystemSelector
public ColorId Color; public ColorId Color;
} }
private const StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase; private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
private LowerString _modFilter = LowerString.Empty; private LowerString _modFilter = LowerString.Empty;
private int _filterType = -1; private int _filterType = -1;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods; private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;

View file

@ -26,7 +26,7 @@ public partial class ConfigWindow
bool FilterChangedItem( KeyValuePair< string, (SingleArray< IMod >, object?) > item ) bool FilterChangedItem( KeyValuePair< string, (SingleArray< IMod >, object?) > item )
=> ( _changedItemFilter.IsEmpty => ( _changedItemFilter.IsEmpty
|| ChangedItemName( item.Key, item.Value.Item2 ) || 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 ) ) ); && ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) );
void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< IMod >, object?) > item ) void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< IMod >, object?) > item )

View file

@ -99,7 +99,6 @@ public partial class ConfigWindow
PrintValue( "Mod Manager BasePath Exists", Directory.Exists( manager.BasePath.FullName ).ToString() ); PrintValue( "Mod Manager BasePath Exists", Directory.Exists( manager.BasePath.FullName ).ToString() );
PrintValue( "Mod Manager Valid", manager.Valid.ToString() ); PrintValue( "Mod Manager Valid", manager.Valid.ToString() );
PrintValue( "Path Resolver Enabled", _window._penumbra.PathResolver.Enabled.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() ); PrintValue( "Web Server Enabled", ( _window._penumbra.WebServer != null ).ToString() );
} }

View file

@ -87,7 +87,7 @@ public partial class ConfigWindow
{ {
// Filter unwanted names. // Filter unwanted names.
if( _resourceManagerFilter.Length != 0 if( _resourceManagerFilter.Length != 0
&& !r->FileName.ToString().Contains( _resourceManagerFilter, StringComparison.InvariantCultureIgnoreCase ) ) && !r->FileName.ToString().Contains( _resourceManagerFilter, StringComparison.OrdinalIgnoreCase ) )
{ {
return; return;
} }

View file

@ -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.", "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 ); Penumbra.Config.AutoDeduplicateOnImport, v => Penumbra.Config.AutoDeduplicateOnImport = v );
DrawRequestedResourceLogging(); DrawRequestedResourceLogging();
DrawDisableSoundStreamingBox();
DrawEnableHttpApiBox(); DrawEnableHttpApiBox();
DrawEnableDebugModeBox(); DrawEnableDebugModeBox();
DrawEnableFullResourceLoggingBox(); 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. // Creates and destroys the web server when toggled.
private void DrawEnableHttpApiBox() private void DrawEnableHttpApiBox()
{ {