mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Merge branch 'xivdev:master' into master
This commit is contained in:
commit
b4231d2a2a
22 changed files with 77 additions and 136 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6ce8ca816678e7a363f9f4a6f43f009f8d79c070
|
Subproject commit fa83386909ad0034f5ed7ea90d8bcedf6e8ba748
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 ];
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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 )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 ) )
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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 );
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 )
|
||||||
|
|
|
||||||
|
|
@ -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() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue