Change most things to new byte strings, introduce new ResourceLoader and Logger fully.

This commit is contained in:
Ottermandias 2022-03-06 16:45:16 +01:00
parent 5d77cd5514
commit f5fccb0235
55 changed files with 2681 additions and 2730 deletions

View file

@ -1,17 +1,21 @@
using System; using System;
using System.IO; using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
namespace Penumbra.GameData.ByteString; namespace Penumbra.GameData.ByteString;
[JsonConverter( typeof( FullPathConverter ) )]
public readonly struct FullPath : IComparable, IEquatable< FullPath > public readonly struct FullPath : IComparable, IEquatable< FullPath >
{ {
public readonly string FullName; public readonly string FullName;
public readonly Utf8String InternalName; public readonly Utf8String InternalName;
public readonly ulong Crc64; public readonly ulong Crc64;
public static readonly FullPath Empty = new(string.Empty);
public FullPath( DirectoryInfo baseDir, NewRelPath relPath ) public FullPath( DirectoryInfo baseDir, Utf8RelPath relPath )
: this( Path.Combine( baseDir.FullName, relPath.ToString() ) ) : this( Path.Combine( baseDir.FullName, relPath.ToString() ) )
{ } { }
@ -19,10 +23,11 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
: this( file.FullName ) : this( file.FullName )
{ } { }
public FullPath( string s ) public FullPath( string s )
{ {
FullName = s; FullName = s;
InternalName = Utf8String.FromString( FullName, out var name, true ) ? name : Utf8String.Empty; InternalName = Utf8String.FromString( FullName, out var name, true ) ? name.Replace( ( byte )'\\', ( byte )'/' ) : Utf8String.Empty;
Crc64 = Functions.ComputeCrc64( InternalName.Span ); Crc64 = Functions.ComputeCrc64( InternalName.Span );
} }
@ -35,9 +40,9 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
public string Name public string Name
=> Path.GetFileName( FullName ); => Path.GetFileName( FullName );
public bool ToGamePath( DirectoryInfo dir, out NewGamePath path ) public bool ToGamePath( DirectoryInfo dir, out Utf8GamePath path )
{ {
path = NewGamePath.Empty; path = Utf8GamePath.Empty;
if( !InternalName.IsAscii || !FullName.StartsWith( dir.FullName ) ) if( !InternalName.IsAscii || !FullName.StartsWith( dir.FullName ) )
{ {
return false; return false;
@ -45,13 +50,13 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
var substring = InternalName.Substring( dir.FullName.Length + 1 ); var substring = InternalName.Substring( dir.FullName.Length + 1 );
path = new NewGamePath( substring.Replace( ( byte )'\\', ( byte )'/' ) ); path = new Utf8GamePath( substring );
return true; return true;
} }
public bool ToRelPath( DirectoryInfo dir, out NewRelPath path ) public bool ToRelPath( DirectoryInfo dir, out Utf8RelPath path )
{ {
path = NewRelPath.Empty; path = Utf8RelPath.Empty;
if( !FullName.StartsWith( dir.FullName ) ) if( !FullName.StartsWith( dir.FullName ) )
{ {
return false; return false;
@ -59,7 +64,7 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
var substring = InternalName.Substring( dir.FullName.Length + 1 ); var substring = InternalName.Substring( dir.FullName.Length + 1 );
path = new NewRelPath( substring ); path = new Utf8RelPath( substring.Replace( ( byte )'/', ( byte )'\\' ) );
return true; return true;
} }
@ -88,9 +93,35 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
return InternalName.Equals( other.InternalName ); return InternalName.Equals( other.InternalName );
} }
public bool IsRooted
=> new Utf8GamePath( InternalName ).IsRooted();
public override int GetHashCode() public override int GetHashCode()
=> InternalName.Crc32; => InternalName.Crc32;
public override string ToString() public override string ToString()
=> FullName; => FullName;
public class FullPathConverter : JsonConverter
{
public override bool CanConvert( Type objectType )
=> objectType == typeof( FullPath );
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
{
var token = JToken.Load( reader ).ToString();
return new FullPath( token );
}
public override bool CanWrite
=> true;
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
{
if( value is FullPath p )
{
serializer.Serialize( writer, p.ToString() );
}
}
}
} }

View file

@ -3,20 +3,21 @@ using System.IO;
using Dalamud.Utility; using Dalamud.Utility;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Util;
namespace Penumbra.GameData.ByteString; namespace Penumbra.GameData.ByteString;
// NewGamePath wrap some additional validity checking around Utf8String, // NewGamePath wrap some additional validity checking around Utf8String,
// provide some filesystem helpers, and conversion to Json. // provide some filesystem helpers, and conversion to Json.
[JsonConverter( typeof( NewGamePathConverter ) )] [JsonConverter( typeof( Utf8GamePathConverter ) )]
public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< NewGamePath >, IDisposable public readonly struct Utf8GamePath : IEquatable< Utf8GamePath >, IComparable< Utf8GamePath >, IDisposable
{ {
public const int MaxGamePathLength = 256; public const int MaxGamePathLength = 256;
public readonly Utf8String Path; public readonly Utf8String Path;
public static readonly NewGamePath Empty = new(Utf8String.Empty); public static readonly Utf8GamePath Empty = new(Utf8String.Empty);
internal NewGamePath( Utf8String s ) internal Utf8GamePath( Utf8String s )
=> Path = s; => Path = s;
public int Length public int Length
@ -25,16 +26,16 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
public bool IsEmpty public bool IsEmpty
=> Path.IsEmpty; => Path.IsEmpty;
public NewGamePath ToLower() public Utf8GamePath ToLower()
=> new(Path.AsciiToLower()); => new(Path.AsciiToLower());
public static unsafe bool FromPointer( byte* ptr, out NewGamePath path, bool lower = false ) public static unsafe bool FromPointer( byte* ptr, out Utf8GamePath path, bool lower = false )
{ {
var utf = new Utf8String( ptr ); var utf = new Utf8String( ptr );
return ReturnChecked( utf, out path, lower ); return ReturnChecked( utf, out path, lower );
} }
public static bool FromSpan( ReadOnlySpan< byte > data, out NewGamePath path, bool lower = false ) public static bool FromSpan( ReadOnlySpan< byte > data, out Utf8GamePath path, bool lower = false )
{ {
var utf = Utf8String.FromSpanUnsafe( data, false, null, null ); var utf = Utf8String.FromSpanUnsafe( data, false, null, null );
return ReturnChecked( utf, out path, lower ); return ReturnChecked( utf, out path, lower );
@ -43,7 +44,7 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
// Does not check for Forward/Backslashes due to assuming that SE-strings use the correct one. // Does not check for Forward/Backslashes due to assuming that SE-strings use the correct one.
// Does not check for initial slashes either, since they are assumed to be by choice. // Does not check for initial slashes either, since they are assumed to be by choice.
// Checks for maxlength, ASCII and lowercase. // Checks for maxlength, ASCII and lowercase.
private static bool ReturnChecked( Utf8String utf, out NewGamePath path, bool lower = false ) private static bool ReturnChecked( Utf8String utf, out Utf8GamePath path, bool lower = false )
{ {
path = Empty; path = Empty;
if( !utf.IsAscii || utf.Length > MaxGamePathLength ) if( !utf.IsAscii || utf.Length > MaxGamePathLength )
@ -51,14 +52,17 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
return false; return false;
} }
path = new NewGamePath( lower ? utf.AsciiToLower() : utf ); path = new Utf8GamePath( lower ? utf.AsciiToLower() : utf );
return true; return true;
} }
public NewGamePath Clone() public Utf8GamePath Clone()
=> new(Path.Clone()); => new(Path.Clone());
public static bool FromString( string? s, out NewGamePath path, bool toLower = false ) public static explicit operator Utf8GamePath( string s )
=> FromString( s, out var p, true ) ? p : Empty;
public static bool FromString( string? s, out Utf8GamePath path, bool toLower = false )
{ {
path = Empty; path = Empty;
if( s.IsNullOrEmpty() ) if( s.IsNullOrEmpty() )
@ -83,11 +87,11 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
return false; return false;
} }
path = new NewGamePath( ascii ); path = new Utf8GamePath( ascii );
return true; return true;
} }
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out NewGamePath path, bool toLower = false ) public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out Utf8GamePath path, bool toLower = false )
{ {
path = Empty; path = Empty;
if( !file.FullName.StartsWith( baseDir.FullName ) ) if( !file.FullName.StartsWith( baseDir.FullName ) )
@ -111,13 +115,13 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
return idx == -1 ? Utf8String.Empty : Path.Substring( idx ); return idx == -1 ? Utf8String.Empty : Path.Substring( idx );
} }
public bool Equals( NewGamePath other ) public bool Equals( Utf8GamePath other )
=> Path.Equals( other.Path ); => Path.Equals( other.Path );
public override int GetHashCode() public override int GetHashCode()
=> Path.GetHashCode(); => Path.GetHashCode();
public int CompareTo( NewGamePath other ) public int CompareTo( Utf8GamePath other )
=> Path.CompareTo( other.Path ); => Path.CompareTo( other.Path );
public override string ToString() public override string ToString()
@ -132,17 +136,17 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
&& ( Path[ 0 ] >= 'A' && Path[ 0 ] <= 'Z' || Path[ 0 ] >= 'a' && Path[ 0 ] <= 'z' ) && ( Path[ 0 ] >= 'A' && Path[ 0 ] <= 'Z' || Path[ 0 ] >= 'a' && Path[ 0 ] <= 'z' )
&& Path[ 1 ] == ':'; && Path[ 1 ] == ':';
private class NewGamePathConverter : JsonConverter public class Utf8GamePathConverter : JsonConverter
{ {
public override bool CanConvert( Type objectType ) public override bool CanConvert( Type objectType )
=> objectType == typeof( NewGamePath ); => objectType == typeof( Utf8GamePath );
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer ) public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
{ {
var token = JToken.Load( reader ).ToString(); var token = JToken.Load( reader ).ToString();
return FromString( token, out var p, true ) return FromString( token, out var p, true )
? p ? p
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( NewGamePath )}." ); : throw new JsonException( $"Could not convert \"{token}\" to {nameof( Utf8GamePath )}." );
} }
public override bool CanWrite public override bool CanWrite
@ -150,10 +154,13 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer ) public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
{ {
if( value is NewGamePath p ) if( value is Utf8GamePath p )
{ {
serializer.Serialize( writer, p.ToString() ); serializer.Serialize( writer, p.ToString() );
} }
} }
} }
public GamePath ToGamePath()
=> GamePath.GenerateUnchecked( ToString() );
} }

View file

@ -1,23 +1,28 @@
using System; using System;
using System.IO; using System.IO;
using Dalamud.Utility; using Dalamud.Utility;
using Microsoft.VisualBasic.CompilerServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Penumbra.GameData.ByteString; namespace Penumbra.GameData.ByteString;
[JsonConverter( typeof( NewRelPathConverter ) )] [JsonConverter( typeof( Utf8RelPathConverter ) )]
public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRelPath >, IDisposable public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf8RelPath >, IDisposable
{ {
public const int MaxRelPathLength = 250; public const int MaxRelPathLength = 250;
public readonly Utf8String Path; public readonly Utf8String Path;
public static readonly NewRelPath Empty = new(Utf8String.Empty); public static readonly Utf8RelPath Empty = new(Utf8String.Empty);
internal NewRelPath( Utf8String path ) internal Utf8RelPath( Utf8String path )
=> Path = path; => Path = path;
public static bool FromString( string? s, out NewRelPath path )
public static explicit operator Utf8RelPath( string s )
=> FromString( s, out var p ) ? p : Empty;
public static bool FromString( string? s, out Utf8RelPath path )
{ {
path = Empty; path = Empty;
if( s.IsNullOrEmpty() ) if( s.IsNullOrEmpty() )
@ -42,11 +47,11 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
return false; return false;
} }
path = new NewRelPath( ascii ); path = new Utf8RelPath( ascii );
return true; return true;
} }
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out NewRelPath path ) public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out Utf8RelPath path )
{ {
path = Empty; path = Empty;
if( !file.FullName.StartsWith( baseDir.FullName ) ) if( !file.FullName.StartsWith( baseDir.FullName ) )
@ -58,7 +63,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
return FromString( substring, out path ); return FromString( substring, out path );
} }
public static bool FromFile( FullPath file, DirectoryInfo baseDir, out NewRelPath path ) public static bool FromFile( FullPath file, DirectoryInfo baseDir, out Utf8RelPath path )
{ {
path = Empty; path = Empty;
if( !file.FullName.StartsWith( baseDir.FullName ) ) if( !file.FullName.StartsWith( baseDir.FullName ) )
@ -70,10 +75,10 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
return FromString( substring, out path ); return FromString( substring, out path );
} }
public NewRelPath( NewGamePath gamePath ) public Utf8RelPath( Utf8GamePath gamePath )
=> Path = gamePath.Path.Replace( ( byte )'/', ( byte )'\\' ); => Path = gamePath.Path.Replace( ( byte )'/', ( byte )'\\' );
public unsafe NewGamePath ToGamePath( int skipFolders = 0 ) public unsafe Utf8GamePath ToGamePath( int skipFolders = 0 )
{ {
var idx = 0; var idx = 0;
while( skipFolders > 0 ) while( skipFolders > 0 )
@ -82,7 +87,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
--skipFolders; --skipFolders;
if( idx <= 0 ) if( idx <= 0 )
{ {
return NewGamePath.Empty; return Utf8GamePath.Empty;
} }
} }
@ -91,13 +96,13 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
ByteStringFunctions.Replace( ptr, length, ( byte )'\\', ( byte )'/' ); ByteStringFunctions.Replace( ptr, length, ( byte )'\\', ( byte )'/' );
ByteStringFunctions.AsciiToLowerInPlace( ptr, length ); ByteStringFunctions.AsciiToLowerInPlace( ptr, length );
var utf = new Utf8String().Setup( ptr, length, null, true, true, true, true ); var utf = new Utf8String().Setup( ptr, length, null, true, true, true, true );
return new NewGamePath( utf ); return new Utf8GamePath( utf );
} }
public int CompareTo( NewRelPath rhs ) public int CompareTo( Utf8RelPath rhs )
=> Path.CompareTo( rhs.Path ); => Path.CompareTo( rhs.Path );
public bool Equals( NewRelPath other ) public bool Equals( Utf8RelPath other )
=> Path.Equals( other.Path ); => Path.Equals( other.Path );
public override string ToString() public override string ToString()
@ -106,17 +111,17 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
public void Dispose() public void Dispose()
=> Path.Dispose(); => Path.Dispose();
private class NewRelPathConverter : JsonConverter public class Utf8RelPathConverter : JsonConverter
{ {
public override bool CanConvert( Type objectType ) public override bool CanConvert( Type objectType )
=> objectType == typeof( NewRelPath ); => objectType == typeof( Utf8RelPath );
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer ) public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
{ {
var token = JToken.Load( reader ).ToString(); var token = JToken.Load( reader ).ToString();
return FromString( token, out var p ) return FromString( token, out var p )
? p ? p
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( NewRelPath )}." ); : throw new JsonException( $"Could not convert \"{token}\" to {nameof( Utf8RelPath )}." );
} }
public override bool CanWrite public override bool CanWrite
@ -124,7 +129,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer ) public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
{ {
if( value is NewRelPath p ) if( value is Utf8RelPath p )
{ {
serializer.Serialize( writer, p.ToString() ); serializer.Serialize( writer, p.ToString() );
} }

View file

@ -75,6 +75,19 @@ public sealed unsafe partial class Utf8String : IEquatable< Utf8String >, ICompa
return ByteStringFunctions.AsciiCaselessCompare( _path, Length, other._path, other.Length ); return ByteStringFunctions.AsciiCaselessCompare( _path, Length, other._path, other.Length );
} }
public bool StartsWith( Utf8String other )
{
var otherLength = other.Length;
return otherLength <= Length && ByteStringFunctions.Equals( other.Path, otherLength, Path, otherLength );
}
public bool EndsWith( Utf8String other )
{
var otherLength = other.Length;
var offset = Length - otherLength;
return offset >= 0 && ByteStringFunctions.Equals( other.Path, otherLength, Path + offset, otherLength );
}
public bool StartsWith( params char[] chars ) public bool StartsWith( params char[] chars )
{ {
if( chars.Length > Length ) if( chars.Length > Length )

View file

@ -36,7 +36,7 @@ public class ModsController : WebApiController
public object GetFiles() public object GetFiles()
{ {
return Penumbra.ModManager.Collections.CurrentCollection.Cache?.ResolvedFiles.ToDictionary( return Penumbra.ModManager.Collections.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
o => ( string )o.Key, o => o.Key.ToString(),
o => o.Value.FullName o => o.Value.FullName
) )
?? new Dictionary< string, string >(); ?? new Dictionary< string, string >();

View file

@ -5,6 +5,7 @@ using System.Reflection;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging; using Dalamud.Logging;
using Lumina.Data; using Lumina.Data;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Mods; using Penumbra.Mods;
@ -78,16 +79,15 @@ public class PenumbraApi : IDisposable, IPenumbraApi
private static string ResolvePath( string path, ModManager manager, ModCollection collection ) private static string ResolvePath( string path, ModManager manager, ModCollection collection )
{ {
if( !Penumbra.Config.IsEnabled ) if( !Penumbra.Config.EnableMods )
{ {
return path; return path;
} }
var gamePath = new GamePath( path ); var gamePath = Utf8GamePath.FromString( path, out var p, true ) ? p : Utf8GamePath.Empty;
var ret = collection.Cache?.ResolveSwappedOrReplacementPath( gamePath ); var ret = collection.Cache?.ResolveSwappedOrReplacementPath( gamePath );
ret ??= manager.Collections.ForcedCollection.Cache?.ResolveSwappedOrReplacementPath( gamePath ); ret ??= manager.Collections.ForcedCollection.Cache?.ResolveSwappedOrReplacementPath( gamePath );
ret ??= path; return ret?.ToString() ?? path;
return ret;
} }
public string ResolvePath( string path ) public string ResolvePath( string path )

View file

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Logging; using Dalamud.Logging;
@ -14,13 +12,19 @@ public class Configuration : IPluginConfiguration
public int Version { get; set; } = CurrentVersion; public int Version { get; set; } = CurrentVersion;
public bool IsEnabled { get; set; } = true; public bool EnableMods { get; set; } = true;
#if DEBUG #if DEBUG
public bool DebugMode { get; set; } = true; public bool DebugMode { get; set; } = true;
#else #else
public bool DebugMode { get; set; } = false; public bool DebugMode { get; set; } = false;
#endif #endif
public bool EnableFullResourceLogging { get; set; } = false;
public bool EnableResourceLogging { get; set; } = false;
public string ResourceLoggingFilter { get; set; } = string.Empty;
public bool ScaleModSelector { get; set; } = false; public bool ScaleModSelector { get; set; } = false;
public bool ShowAdvanced { get; set; } public bool ShowAdvanced { get; set; }
public bool DisableFileSystemNotifications { get; set; } public bool DisableFileSystemNotifications { get; set; }

View file

@ -8,10 +8,11 @@ using Dalamud.Game.Gui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local // ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
namespace Penumbra namespace Penumbra;
{
public class Dalamud public class Dalamud
{ {
public static void Initialize( DalamudPluginInterface pluginInterface ) public static void Initialize( DalamudPluginInterface pluginInterface )
@ -31,4 +32,3 @@ namespace Penumbra
[PluginService][RequiredVersion("1.0")] public static TitleScreenMenu TitleScreenMenu { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static TitleScreenMenu TitleScreenMenu { get; private set; } = null!;
// @formatter:on // @formatter:on
} }
}

View file

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Penumbra.Structs; using Penumbra.Mod;
namespace Penumbra.Importer.Models namespace Penumbra.Importer.Models
{ {

View file

@ -6,10 +6,10 @@ using System.Text;
using Dalamud.Logging; using Dalamud.Logging;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json; using Newtonsoft.Json;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Importer.Models; using Penumbra.Importer.Models;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Structs;
using Penumbra.Util; using Penumbra.Util;
using FileMode = System.IO.FileMode; using FileMode = System.IO.FileMode;
@ -336,14 +336,18 @@ internal class TexToolsImport
{ {
OptionName = opt.Name!, OptionName = opt.Name!,
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description!, OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description!,
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(), OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
}; };
var optDir = NewOptionDirectory( groupFolder, opt.Name! ); var optDir = NewOptionDirectory( groupFolder, opt.Name! );
if( optDir.Exists ) if( optDir.Exists )
{ {
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{ {
option.AddFile( new RelPath( file, baseFolder ), new GamePath( file, optDir ) ); if( Utf8RelPath.FromFile( file, baseFolder, out var rel )
&& Utf8GamePath.FromFile( file, optDir, out var game, true ) )
{
option.AddFile( rel, game );
}
} }
} }

View file

@ -21,7 +21,7 @@ public unsafe partial class ResourceLoader
{ {
public ResourceHandle* OriginalResource; public ResourceHandle* OriginalResource;
public ResourceHandle* ManipulatedResource; public ResourceHandle* ManipulatedResource;
public NewGamePath OriginalPath; public Utf8GamePath OriginalPath;
public FullPath ManipulatedPath; public FullPath ManipulatedPath;
public ResourceCategory Category; public ResourceCategory Category;
public object? ResolverInfo; public object? ResolverInfo;
@ -44,7 +44,7 @@ public unsafe partial class ResourceLoader
ResourceLoaded -= AddModifiedDebugInfo; ResourceLoaded -= AddModifiedDebugInfo;
} }
private void AddModifiedDebugInfo( ResourceHandle* handle, NewGamePath originalPath, FullPath? manipulatedPath, object? resolverInfo ) private void AddModifiedDebugInfo( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, object? resolverInfo )
{ {
if( manipulatedPath == null ) if( manipulatedPath == null )
{ {
@ -188,11 +188,11 @@ public unsafe partial class ResourceLoader
} }
} }
// Logging functions for EnableLogging. // Logging functions for EnableFullLogging.
private static void LogPath( NewGamePath path, bool synchronous ) private static void LogPath( Utf8GamePath path, bool synchronous )
=> PluginLog.Information( $"Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" ); => PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
private static void LogResource( ResourceHandle* handle, NewGamePath path, FullPath? manipulatedPath, object? _ ) private static void LogResource( ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ )
{ {
var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString(); var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString();
PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" ); PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );
@ -200,6 +200,6 @@ public unsafe partial class ResourceLoader
private static void LogLoadedFile( Utf8String path, bool success, bool custom ) private static void LogLoadedFile( Utf8String path, bool success, bool custom )
=> PluginLog.Information( success => PluginLog.Information( success
? $"Loaded {path} from {( custom ? "local files" : "SqPack" )}" ? $"[ResourceLoader] Loaded {path} from {( custom ? "local files" : "SqPack" )}"
: $"Failed to load {path} from {( custom ? "local files" : "SqPack" )}." ); : $"[ResourceLoader] Failed to load {path} from {( custom ? "local files" : "SqPack" )}." );
} }

View file

@ -46,7 +46,7 @@ public unsafe partial class ResourceLoader
[Conditional( "DEBUG" )] [Conditional( "DEBUG" )]
private static void CompareHash( int local, int game, NewGamePath path ) private static void CompareHash( int local, int game, Utf8GamePath path )
{ {
if( local != game ) if( local != game )
{ {
@ -54,12 +54,12 @@ public unsafe partial class ResourceLoader
} }
} }
private event Action< NewGamePath, FullPath?, object? >? PathResolved; private event Action< Utf8GamePath, FullPath?, object? >? PathResolved;
private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, uint* resourceType, private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, uint* resourceType,
int* resourceHash, byte* path, void* unk, bool isUnk ) int* resourceHash, byte* path, void* unk, bool isUnk )
{ {
if( !NewGamePath.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, unk, isUnk );
@ -114,7 +114,7 @@ public unsafe partial class ResourceLoader
return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
} }
var valid = NewGamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false ); var valid = Utf8GamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false );
byte ret; byte ret;
// The internal buffer size does not allow for more than 260 characters. // The internal buffer size does not allow for more than 260 characters.
// We use the IsRooted check to signify paths replaced by us pointing to the local filesystem instead of an SqPack. // We use the IsRooted check to signify paths replaced by us pointing to the local filesystem instead of an SqPack.
@ -151,11 +151,10 @@ public unsafe partial class ResourceLoader
} }
// Use the default method of path replacement. // Use the default method of path replacement.
public static (FullPath?, object?) DefaultReplacer( NewGamePath path ) public static (FullPath?, object?) DefaultReplacer( Utf8GamePath path )
{ {
var gamePath = new GamePath( path.ToString() ); var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path );
var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( gamePath ); return( resolved, null );
return resolved != null ? ( new FullPath( resolved ), null ) : ( null, null );
} }
private void DisposeHooks() private void DisposeHooks()

View file

@ -4,8 +4,6 @@ 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.Util;
using Penumbra.Util;
namespace Penumbra.Interop; namespace Penumbra.Interop;
@ -69,7 +67,7 @@ public unsafe partial class ResourceLoader
: LoadMdlFileExternHook.Original( resourceHandle, unk1, unk2, ptr ); : LoadMdlFileExternHook.Original( resourceHandle, unk1, unk2, ptr );
private void AddCrc( NewGamePath _, FullPath? path, object? _2 ) private void AddCrc( Utf8GamePath _, FullPath? path, object? _2 )
{ {
if( path is { Extension: ".mdl" or ".tex" } p ) if( path is { Extension: ".mdl" or ".tex" } p )
{ {

View file

@ -17,7 +17,7 @@ public unsafe partial class ResourceLoader : IDisposable
// Events can be used to make smarter logging. // Events can be used to make smarter logging.
public bool IsLoggingEnabled { get; private set; } public bool IsLoggingEnabled { get; private set; }
public void EnableLogging() public void EnableFullLogging()
{ {
if( IsLoggingEnabled ) if( IsLoggingEnabled )
{ {
@ -31,7 +31,7 @@ public unsafe partial class ResourceLoader : IDisposable
EnableHooks(); EnableHooks();
} }
public void DisableLogging() public void DisableFullLogging()
{ {
if( !IsLoggingEnabled ) if( !IsLoggingEnabled )
{ {
@ -99,13 +99,13 @@ public unsafe partial class ResourceLoader : IDisposable
} }
// Event fired whenever a resource is requested. // Event fired whenever a resource is requested.
public delegate void ResourceRequestedDelegate( NewGamePath path, bool synchronous ); public delegate void ResourceRequestedDelegate( Utf8GamePath path, bool synchronous );
public event ResourceRequestedDelegate? ResourceRequested; public event ResourceRequestedDelegate? ResourceRequested;
// Event fired whenever a resource is returned. // Event fired whenever a resource is returned.
// If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource. // If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource.
// resolveData is additional data returned by the current ResolvePath function and is user-defined. // resolveData is additional data returned by the current ResolvePath function and is user-defined.
public delegate void ResourceLoadedDelegate( ResourceHandle* handle, NewGamePath originalPath, FullPath? manipulatedPath, public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
object? resolveData ); object? resolveData );
public event ResourceLoadedDelegate? ResourceLoaded; public event ResourceLoadedDelegate? ResourceLoaded;
@ -118,10 +118,11 @@ public unsafe partial class ResourceLoader : IDisposable
public event FileLoadedDelegate? FileLoaded; public event FileLoadedDelegate? FileLoaded;
// Customization point to control how path resolving is handled. // Customization point to control how path resolving is handled.
public Func< NewGamePath, (FullPath?, object?) > ResolvePath { get; set; } = DefaultReplacer; public Func< Utf8GamePath, (FullPath?, object?) > ResolvePath { get; set; } = DefaultReplacer;
public void Dispose() public void Dispose()
{ {
DisableFullLogging();
DisposeHooks(); DisposeHooks();
DisposeTexMdlTreatment(); DisposeTexMdlTreatment();
} }

View file

@ -0,0 +1,98 @@
using System;
using System.Text.RegularExpressions;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
namespace Penumbra.Interop;
// A logger class that contains the relevant data to log requested files via regex.
// Filters are case-insensitive.
public class ResourceLogger : IDisposable
{
// Enable or disable the logging of resources subject to the current filter.
public void SetState( bool value )
{
if( value == Penumbra.Config.EnableResourceLogging )
{
return;
}
Penumbra.Config.EnableResourceLogging = value;
Penumbra.Config.Save();
if( value )
{
_resourceLoader.ResourceRequested += OnResourceRequested;
}
else
{
_resourceLoader.ResourceRequested -= OnResourceRequested;
}
}
// Set the current filter to a new string, doing all other necessary work.
public void SetFilter( string newFilter )
{
if( newFilter == Filter )
{
return;
}
Penumbra.Config.ResourceLoggingFilter = newFilter;
Penumbra.Config.Save();
SetupRegex();
}
// Returns whether the current filter is a valid regular expression.
public bool ValidRegex
=> _filterRegex != null;
private readonly ResourceLoader _resourceLoader;
private Regex? _filterRegex;
private static string Filter
=> Penumbra.Config.ResourceLoggingFilter;
private void SetupRegex()
{
try
{
_filterRegex = new Regex( Filter, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant );
}
catch
{
_filterRegex = null;
}
}
public ResourceLogger( ResourceLoader loader )
{
_resourceLoader = loader;
SetupRegex();
if( Penumbra.Config.EnableResourceLogging )
{
_resourceLoader.ResourceRequested += OnResourceRequested;
}
}
private void OnResourceRequested( Utf8GamePath data, bool synchronous )
{
var path = Match( data.Path );
if( path != null )
{
PluginLog.Information( $"{path} was requested {( synchronous ? "synchronously." : "asynchronously." )}" );
}
}
// Returns the converted string if the filter matches, and null otherwise.
// The filter matches if it is empty, if it is a valid and matching regex or if the given string contains it.
private string? Match( Utf8String data )
{
var s = data.ToString();
return Filter.Length == 0 || ( _filterRegex?.IsMatch( s ) ?? s.Contains( Filter, StringComparison.InvariantCultureIgnoreCase ) )
? s
: null;
}
public void Dispose()
=> _resourceLoader.ResourceRequested -= OnResourceRequested;
}

View file

@ -1,5 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Penumbra.Structs;
namespace Penumbra.Interop.Structs; namespace Penumbra.Interop.Structs;

View file

@ -8,7 +8,6 @@ using Penumbra.GameData.ByteString;
using Penumbra.Importer; using Penumbra.Importer;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Structs;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Meta; namespace Penumbra.Meta;
@ -167,14 +166,14 @@ public class MetaCollection
continue; continue;
} }
var path = new RelPath( file, basePath ); Utf8RelPath.FromFile( file, basePath, out var path );
var foundAny = false; var foundAny = false;
foreach( var group in modMeta.Groups ) foreach( var (name, group) in modMeta.Groups )
{ {
foreach( var option in group.Value.Options.Where( o => o.OptionFiles.ContainsKey( path ) ) ) foreach( var option in group.Options.Where( o => o.OptionFiles.ContainsKey( path ) ) )
{ {
foundAny = true; foundAny = true;
AddMeta( group.Key, option.OptionName, metaData ); AddMeta( name, option.OptionName, metaData );
} }
} }

View file

@ -6,7 +6,6 @@ using Dalamud.Logging;
using Lumina.Data.Files; using Lumina.Data.Files;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Interop;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Util; using Penumbra.Util;
@ -24,7 +23,7 @@ public class MetaManager : IDisposable
public FileInformation( object data ) public FileInformation( object data )
=> Data = data; => Data = data;
public void Write( DirectoryInfo dir, GamePath originalPath ) public void Write( DirectoryInfo dir, Utf8GamePath originalPath )
{ {
ByteData = Data switch ByteData = Data switch
{ {
@ -45,15 +44,15 @@ public class MetaManager : IDisposable
public const string TmpDirectory = "penumbrametatmp"; public const string TmpDirectory = "penumbrametatmp";
private readonly DirectoryInfo _dir; private readonly DirectoryInfo _dir;
private readonly Dictionary< GamePath, FullPath > _resolvedFiles; private readonly Dictionary< Utf8GamePath, FullPath > _resolvedFiles;
private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new(); private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new();
private readonly Dictionary< GamePath, FileInformation > _currentFiles = new(); private readonly Dictionary< Utf8GamePath, FileInformation > _currentFiles = new();
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
=> _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) ); => _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) );
public IEnumerable< (GamePath, FullPath) > Files public IEnumerable< (Utf8GamePath, FullPath) > Files
=> _currentFiles.Where( kvp => kvp.Value.CurrentFile != null ) => _currentFiles.Where( kvp => kvp.Value.CurrentFile != null )
.Select( kvp => ( kvp.Key, kvp.Value.CurrentFile!.Value ) ); .Select( kvp => ( kvp.Key, kvp.Value.CurrentFile!.Value ) );
@ -121,7 +120,7 @@ public class MetaManager : IDisposable
private void ClearDirectory() private void ClearDirectory()
=> ClearDirectory( _dir ); => ClearDirectory( _dir );
public MetaManager( string name, Dictionary< GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir ) public MetaManager( string name, Dictionary< Utf8GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir )
{ {
_resolvedFiles = resolvedFiles; _resolvedFiles = resolvedFiles;
_dir = new DirectoryInfo( Path.Combine( tempDir.FullName, name.ReplaceBadXivSymbols() ) ); _dir = new DirectoryInfo( Path.Combine( tempDir.FullName, name.ReplaceBadXivSymbols() ) );
@ -135,13 +134,13 @@ public class MetaManager : IDisposable
Directory.CreateDirectory( _dir.FullName ); Directory.CreateDirectory( _dir.FullName );
} }
foreach( var kvp in _currentFiles.Where( kvp => kvp.Value.Changed ) ) foreach( var (key, value) in _currentFiles.Where( kvp => kvp.Value.Changed ) )
{ {
kvp.Value.Write( _dir, kvp.Key ); value.Write( _dir, key );
_resolvedFiles[ kvp.Key ] = kvp.Value.CurrentFile!.Value; _resolvedFiles[ key ] = value.CurrentFile!.Value;
if( kvp.Value.Data is EqpFile ) if( value.Data is EqpFile )
{ {
EqpData = kvp.Value.ByteData; EqpData = value.ByteData;
} }
} }
} }
@ -154,7 +153,7 @@ public class MetaManager : IDisposable
} }
_currentManipulations.Add( m, mod ); _currentManipulations.Add( m, mod );
var gamePath = m.CorrespondingFilename(); var gamePath = Utf8GamePath.FromString(m.CorrespondingFilename(), out var p, false) ? p : Utf8GamePath.Empty; // TODO
try try
{ {
if( !_currentFiles.TryGetValue( gamePath, out var file ) ) if( !_currentFiles.TryGetValue( gamePath, out var file ) )

View file

@ -3,14 +3,12 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Plugin;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra namespace Penumbra;
{
public static class MigrateConfiguration public static class MigrateConfiguration
{ {
public static void Version0To1( Configuration config ) public static void Version0To1( Configuration config )
@ -83,4 +81,3 @@ namespace Penumbra
} }
} }
} }
}

View file

@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.ComponentModel;
using Newtonsoft.Json;
using Penumbra.GameData.ByteString;
using Penumbra.Util;
namespace Penumbra.Mod;
public enum SelectType
{
Single,
Multi,
}
public struct Option
{
public string OptionName;
public string OptionDesc;
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< Utf8GamePath > ) )]
public Dictionary< Utf8RelPath, HashSet< Utf8GamePath > > OptionFiles;
public bool AddFile( Utf8RelPath filePath, Utf8GamePath gamePath )
{
if( OptionFiles.TryGetValue( filePath, out var set ) )
{
return set.Add( gamePath );
}
OptionFiles[ filePath ] = new HashSet< Utf8GamePath > { gamePath };
return true;
}
}
public struct OptionGroup
{
public string GroupName;
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
public SelectType SelectionType;
public List< Option > Options;
private bool ApplySingleGroupFiles( Utf8RelPath relPath, int selection, HashSet< Utf8GamePath > paths )
{
// Selection contains the path, merge all GamePaths for this config.
if( Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
{
paths.UnionWith( groupPaths );
return true;
}
// If the group contains the file in another selection, return true to skip it for default files.
for( var i = 0; i < Options.Count; ++i )
{
if( i == selection )
{
continue;
}
if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
{
return true;
}
}
return false;
}
private bool ApplyMultiGroupFiles( Utf8RelPath relPath, int selection, HashSet< Utf8GamePath > paths )
{
var doNotAdd = false;
for( var i = 0; i < Options.Count; ++i )
{
if( ( selection & ( 1 << i ) ) != 0 )
{
if( Options[ i ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
{
paths.UnionWith( groupPaths );
doNotAdd = true;
}
}
else if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
{
doNotAdd = true;
}
}
return doNotAdd;
}
// Adds all game paths from the given option that correspond to the given RelPath to paths, if any exist.
internal bool ApplyGroupFiles( Utf8RelPath relPath, int selection, HashSet< Utf8GamePath > paths )
{
return SelectionType switch
{
SelectType.Single => ApplySingleGroupFiles( relPath, selection, paths ),
SelectType.Multi => ApplyMultiGroupFiles( relPath, selection, paths ),
_ => throw new InvalidEnumArgumentException( "Invalid option group type." ),
};
}
}

View file

@ -1,10 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Penumbra.GameData.Util; using Penumbra.GameData.ByteString;
using Penumbra.Util;
namespace Penumbra.Mod;
namespace Penumbra.Mod
{
// A complete Mod containing settings (i.e. dependent on a collection) // A complete Mod containing settings (i.e. dependent on a collection)
// and the resulting cache. // and the resulting cache.
public class Mod public class Mod
@ -23,13 +22,12 @@ namespace Penumbra.Mod
public bool FixSettings() public bool FixSettings()
=> Settings.FixInvalidSettings( Data.Meta ); => Settings.FixInvalidSettings( Data.Meta );
public HashSet< GamePath > GetFiles( FileInfo file ) public HashSet< Utf8GamePath > GetFiles( FileInfo file )
{ {
var relPath = new RelPath( file, Data.BasePath ); var relPath = Utf8RelPath.FromFile( file, Data.BasePath, out var p ) ? p : Utf8RelPath.Empty;
return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta ); return ModFunctions.GetFilesForConfig( relPath, Settings, Data.Meta );
} }
public override string ToString() public override string ToString()
=> Data.Meta.Name; => Data.Meta.Name;
} }
}

View file

@ -1,16 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Penumbra.GameData.Util; using Penumbra.GameData.ByteString;
using Penumbra.Meta; using Penumbra.Meta;
namespace Penumbra.Mod namespace Penumbra.Mod;
{
// The ModCache contains volatile information dependent on all current settings in a collection. // The ModCache contains volatile information dependent on all current settings in a collection.
public class ModCache public class ModCache
{ {
public Dictionary< Mod, (List< GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new(); public Dictionary< Mod, (List< Utf8GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new();
public void AddConflict( Mod precedingMod, GamePath gamePath ) public void AddConflict( Mod precedingMod, Utf8GamePath gamePath )
{ {
if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Files.Contains( gamePath ) ) if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Files.Contains( gamePath ) )
{ {
@ -18,7 +18,7 @@ namespace Penumbra.Mod
} }
else else
{ {
Conflicts[ precedingMod ] = ( new List< GamePath > { gamePath }, new List< MetaManipulation >() ); Conflicts[ precedingMod ] = ( new List< Utf8GamePath > { gamePath }, new List< MetaManipulation >() );
} }
} }
@ -30,7 +30,7 @@ namespace Penumbra.Mod
} }
else else
{ {
Conflicts[ precedingMod ] = ( new List< GamePath >(), new List< MetaManipulation > { manipulation } ); Conflicts[ precedingMod ] = ( new List< Utf8GamePath >(), new List< MetaManipulation > { manipulation } );
} }
} }
@ -55,4 +55,3 @@ namespace Penumbra.Mod
} ); } );
} }
} }
}

View file

@ -2,25 +2,23 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Importer; using Penumbra.Importer;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Structs;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Mod namespace Penumbra.Mod;
{
public class ModCleanup public class ModCleanup
{ {
private const string Duplicates = "Duplicates"; private const string Duplicates = "Duplicates";
private const string Required = "Required"; private const string Required = "Required";
private readonly DirectoryInfo _baseDir; private readonly DirectoryInfo _baseDir;
private readonly ModMeta _mod; private readonly ModMeta _mod;
private SHA256? _hasher; private SHA256? _hasher;
@ -51,7 +49,7 @@ namespace Penumbra.Mod
} }
else else
{ {
_filesBySize[ fileLength ] = new List< FileInfo >() { file }; _filesBySize[ fileLength ] = new List< FileInfo > { file };
} }
} }
} }
@ -59,8 +57,7 @@ namespace Penumbra.Mod
private static DirectoryInfo CreateNewModDir( ModData mod, string optionGroup, string option ) private static DirectoryInfo CreateNewModDir( ModData mod, string optionGroup, string option )
{ {
var newName = $"{mod.BasePath.Name}_{optionGroup}_{option}"; var newName = $"{mod.BasePath.Name}_{optionGroup}_{option}";
var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config!.ModDirectory ), newName ); return TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config.ModDirectory ), newName );
return newDir;
} }
private static ModData CreateNewMod( DirectoryInfo newDir, string newSortOrder ) private static ModData CreateNewMod( DirectoryInfo newDir, string newSortOrder )
@ -90,18 +87,18 @@ namespace Penumbra.Mod
{ {
try try
{ {
var newDir = CreateNewModDir( mod, group.GroupName!, option.OptionName ); var newDir = CreateNewModDir( mod, group.GroupName, option.OptionName );
var newName = group.SelectionType == SelectType.Multi ? $"{group.GroupName} - {option.OptionName}" : option.OptionName; var newName = group.SelectionType == SelectType.Multi ? $"{group.GroupName} - {option.OptionName}" : option.OptionName;
var newMeta = CreateNewMeta( newDir, mod, newName, group.GroupName!, option.OptionName ); var newMeta = CreateNewMeta( newDir, mod, newName, group.GroupName, option.OptionName );
foreach( var (fileName, paths) in option.OptionFiles ) foreach( var (fileName, paths) in option.OptionFiles )
{ {
var oldPath = Path.Combine( mod.BasePath.FullName, fileName ); var oldPath = Path.Combine( mod.BasePath.FullName, fileName.ToString() );
unseenPaths.Remove( oldPath ); unseenPaths.Remove( oldPath );
if( File.Exists( oldPath ) ) if( File.Exists( oldPath ) )
{ {
foreach( var path in paths ) foreach( var path in paths )
{ {
var newPath = Path.Combine( newDir.FullName, path ); var newPath = Path.Combine( newDir.FullName, path.ToString() );
Directory.CreateDirectory( Path.GetDirectoryName( newPath )! ); Directory.CreateDirectory( Path.GetDirectoryName( newPath )! );
File.Copy( oldPath, newPath, true ); File.Copy( oldPath, newPath, true );
} }
@ -121,7 +118,7 @@ namespace Penumbra.Mod
public static void SplitMod( ModData mod ) public static void SplitMod( ModData mod )
{ {
if( !mod.Meta.Groups.Any() ) if( mod.Meta.Groups.Count == 0 )
{ {
return; return;
} }
@ -135,7 +132,7 @@ namespace Penumbra.Mod
} }
} }
if( !unseenPaths.Any() ) if( unseenPaths.Count == 0 )
{ {
return; return;
} }
@ -148,8 +145,10 @@ namespace Penumbra.Mod
var defaultOption = new Option() var defaultOption = new Option()
{ {
OptionName = "Files", OptionName = "Files",
OptionFiles = unseenPaths.ToDictionary( p => new RelPath( new FileInfo( p ), mod.BasePath ), OptionFiles = unseenPaths.ToDictionary(
p => new HashSet< GamePath >() { new( new FileInfo( p ), mod.BasePath ) } ), p => Utf8RelPath.FromFile( new FileInfo( p ), mod.BasePath, out var rel ) ? rel : Utf8RelPath.Empty,
p => new HashSet< Utf8GamePath >()
{ Utf8GamePath.FromFile( new FileInfo( p ), mod.BasePath, out var game, true ) ? game : Utf8GamePath.Empty } ),
}; };
CreateModSplit( unseenPaths, mod, defaultGroup, defaultOption ); CreateModSplit( unseenPaths, mod, defaultGroup, defaultOption );
} }
@ -161,7 +160,7 @@ namespace Penumbra.Mod
{ {
OptionName = Required, OptionName = Required,
OptionDesc = "", OptionDesc = "",
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(), OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
}; };
if( meta.Groups.TryGetValue( Duplicates, out var duplicates ) ) if( meta.Groups.TryGetValue( Duplicates, out var duplicates ) )
@ -189,35 +188,35 @@ namespace Penumbra.Mod
public static void Deduplicate( DirectoryInfo baseDir, ModMeta mod ) public static void Deduplicate( DirectoryInfo baseDir, ModMeta mod )
{ {
var dedup = new ModCleanup( baseDir, mod ); var dedup = new ModCleanup( baseDir, mod );
foreach( var pair in dedup._filesBySize.Where( pair => pair.Value.Count >= 2 ) ) foreach( var (key, value) in dedup._filesBySize.Where( pair => pair.Value.Count >= 2 ) )
{ {
if( pair.Value.Count == 2 ) if( value.Count == 2 )
{ {
if( CompareFilesDirectly( pair.Value[ 0 ], pair.Value[ 1 ] ) ) if( CompareFilesDirectly( value[ 0 ], value[ 1 ] ) )
{ {
dedup.ReplaceFile( pair.Value[ 0 ], pair.Value[ 1 ] ); dedup.ReplaceFile( value[ 0 ], value[ 1 ] );
} }
} }
else else
{ {
var deleted = Enumerable.Repeat( false, pair.Value.Count ).ToArray(); var deleted = Enumerable.Repeat( false, value.Count ).ToArray();
var hashes = pair.Value.Select( dedup.ComputeHash ).ToArray(); var hashes = value.Select( dedup.ComputeHash ).ToArray();
for( var i = 0; i < pair.Value.Count; ++i ) for( var i = 0; i < value.Count; ++i )
{ {
if( deleted[ i ] ) if( deleted[ i ] )
{ {
continue; continue;
} }
for( var j = i + 1; j < pair.Value.Count; ++j ) for( var j = i + 1; j < value.Count; ++j )
{ {
if( deleted[ j ] || !CompareHashes( hashes[ i ], hashes[ j ] ) ) if( deleted[ j ] || !CompareHashes( hashes[ i ], hashes[ j ] ) )
{ {
continue; continue;
} }
dedup.ReplaceFile( pair.Value[ i ], pair.Value[ j ] ); dedup.ReplaceFile( value[ i ], value[ j ] );
deleted[ j ] = true; deleted[ j ] = true;
} }
} }
@ -230,8 +229,11 @@ namespace Penumbra.Mod
private void ReplaceFile( FileInfo f1, FileInfo f2 ) private void ReplaceFile( FileInfo f1, FileInfo f2 )
{ {
RelPath relName1 = new( f1, _baseDir ); if( !Utf8RelPath.FromFile( f1, _baseDir, out var relName1 )
RelPath relName2 = new( f2, _baseDir ); || !Utf8RelPath.FromFile( f2, _baseDir, out var relName2 ) )
{
return;
}
var inOption1 = false; var inOption1 = false;
var inOption2 = false; var inOption2 = false;
@ -302,7 +304,7 @@ namespace Penumbra.Mod
} }
} }
private static bool FileIsInAnyGroup( ModMeta meta, RelPath relPath, bool exceptDuplicates = false ) private static bool FileIsInAnyGroup( ModMeta meta, Utf8RelPath relPath, bool exceptDuplicates = false )
{ {
var groupEnumerator = exceptDuplicates var groupEnumerator = exceptDuplicates
? meta.Groups.Values.Where( g => g.GroupName != Duplicates ) ? meta.Groups.Values.Where( g => g.GroupName != Duplicates )
@ -322,16 +324,16 @@ namespace Penumbra.Mod
if( requiredIdx >= 0 ) if( requiredIdx >= 0 )
{ {
var required = info.Options[ requiredIdx ]; var required = info.Options[ requiredIdx ];
foreach( var kvp in required.OptionFiles.ToArray() ) foreach( var (key, value) in required.OptionFiles.ToArray() )
{ {
if( kvp.Value.Count > 1 || FileIsInAnyGroup( meta, kvp.Key, true ) ) if( value.Count > 1 || FileIsInAnyGroup( meta, key, true ) )
{ {
continue; continue;
} }
if( kvp.Value.Count == 0 || kvp.Value.First().CompareTo( kvp.Key.ToGamePath() ) == 0 ) if( value.Count == 0 || value.First().CompareTo( key.ToGamePath() ) == 0 )
{ {
required.OptionFiles.Remove( kvp.Key ); required.OptionFiles.Remove( key );
} }
} }
@ -354,7 +356,7 @@ namespace Penumbra.Mod
Multi = 2, Multi = 2,
}; };
private static void RemoveFromGroups( ModMeta meta, RelPath relPath, GamePath gamePath, GroupType type = GroupType.Both, private static void RemoveFromGroups( ModMeta meta, Utf8RelPath relPath, Utf8GamePath gamePath, GroupType type = GroupType.Both,
bool skipDuplicates = true ) bool skipDuplicates = true )
{ {
if( meta.Groups.Count == 0 ) if( meta.Groups.Count == 0 )
@ -384,18 +386,18 @@ namespace Penumbra.Mod
} }
} }
public static bool MoveFile( ModMeta meta, string basePath, RelPath oldRelPath, RelPath newRelPath ) public static bool MoveFile( ModMeta meta, string basePath, Utf8RelPath oldRelPath, Utf8RelPath newRelPath )
{ {
if( oldRelPath == newRelPath ) if( oldRelPath.Equals( newRelPath ) )
{ {
return true; return true;
} }
try try
{ {
var newFullPath = Path.Combine( basePath, newRelPath ); var newFullPath = Path.Combine( basePath, newRelPath.ToString() );
new FileInfo( newFullPath ).Directory!.Create(); new FileInfo( newFullPath ).Directory!.Create();
File.Move( Path.Combine( basePath, oldRelPath ), newFullPath ); File.Move( Path.Combine( basePath, oldRelPath.ToString() ), newFullPath );
} }
catch( Exception e ) catch( Exception e )
{ {
@ -429,10 +431,10 @@ namespace Penumbra.Mod
foreach( var group in meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single && g.GroupName != Duplicates ) ) foreach( var group in meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single && g.GroupName != Duplicates ) )
{ {
var firstOption = true; var firstOption = true;
HashSet< (RelPath, GamePath) > groupList = new(); HashSet< (Utf8RelPath, Utf8GamePath) > groupList = new();
foreach( var option in group.Options ) foreach( var option in group.Options )
{ {
HashSet< (RelPath, GamePath) > optionList = new(); HashSet< (Utf8RelPath, Utf8GamePath) > optionList = new();
foreach( var (file, gamePaths) in option.OptionFiles.Select( p => ( p.Key, p.Value ) ) ) foreach( var (file, gamePaths) in option.OptionFiles.Select( p => ( p.Key, p.Value ) ) )
{ {
optionList.UnionWith( gamePaths.Select( p => ( file, p ) ) ); optionList.UnionWith( gamePaths.Select( p => ( file, p ) ) );
@ -450,14 +452,14 @@ namespace Penumbra.Mod
firstOption = false; firstOption = false;
} }
var newPath = new Dictionary< RelPath, GamePath >(); var newPath = new Dictionary< Utf8RelPath, Utf8GamePath >();
foreach( var (path, gamePath) in groupList ) foreach( var (path, gamePath) in groupList )
{ {
var relPath = new RelPath( gamePath ); var relPath = new Utf8RelPath( gamePath );
if( newPath.TryGetValue( path, out var usedGamePath ) ) if( newPath.TryGetValue( path, out var usedGamePath ) )
{ {
var required = FindOrCreateDuplicates( meta ); var required = FindOrCreateDuplicates( meta );
var usedRelPath = new RelPath( usedGamePath ); var usedRelPath = new Utf8RelPath( usedGamePath );
required.AddFile( usedRelPath, gamePath ); required.AddFile( usedRelPath, gamePath );
required.AddFile( usedRelPath, usedGamePath ); required.AddFile( usedRelPath, usedGamePath );
RemoveFromGroups( meta, relPath, gamePath, GroupType.Single ); RemoveFromGroups( meta, relPath, gamePath, GroupType.Single );
@ -498,13 +500,15 @@ namespace Penumbra.Mod
{ {
OptionDesc = string.Empty, OptionDesc = string.Empty,
OptionName = optionDir.Name, OptionName = optionDir.Name,
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(), OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
}; };
foreach( var file in optionDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) foreach( var file in optionDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{ {
var relPath = new RelPath( file, baseDir ); if( Utf8RelPath.FromFile( file, baseDir, out var rel )
var gamePath = new GamePath( file, optionDir ); && Utf8GamePath.FromFile( file, optionDir, out var game ) )
option.OptionFiles[ relPath ] = new HashSet< GamePath > { gamePath }; {
option.OptionFiles[ rel ] = new HashSet< Utf8GamePath > { game };
}
} }
if( option.OptionFiles.Any() ) if( option.OptionFiles.Any() )
@ -520,8 +524,8 @@ namespace Penumbra.Mod
} }
foreach( var collection in Penumbra.ModManager.Collections.Collections.Values ) foreach( var collection in Penumbra.ModManager.Collections.Collections.Values )
{
collection.UpdateSetting( baseDir, meta, true ); collection.UpdateSetting( baseDir, meta, true );
} }
} }
} }

View file

@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.Mod namespace Penumbra.Mod;
{
public struct SortOrder : IComparable< SortOrder > public struct SortOrder : IComparable< SortOrder >
{ {
public ModFolder ParentFolder { get; set; } public ModFolder ParentFolder { get; set; }
@ -28,7 +28,7 @@ namespace Penumbra.Mod
get get
{ {
var path = SortOrderPath; var path = SortOrderPath;
return path.Any() ? $"{path}/{SortOrderName}" : SortOrderName; return path.Length > 0 ? $"{path}/{SortOrderName}" : SortOrderName;
} }
} }
@ -40,7 +40,7 @@ namespace Penumbra.Mod
} }
public string FullPath public string FullPath
=> SortOrderPath.Any() ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName; => SortOrderPath.Length > 0 ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
public int CompareTo( SortOrder other ) public int CompareTo( SortOrder other )
=> string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase ); => string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
@ -77,17 +77,17 @@ namespace Penumbra.Mod
{ {
var identifier = GameData.GameData.GetIdentifier(); var identifier = GameData.GameData.GetIdentifier();
ChangedItems.Clear(); ChangedItems.Clear();
foreach( var file in Resources.ModFiles.Select( f => new RelPath( f, BasePath ) ) ) foreach( var file in Resources.ModFiles.Select( f => f.ToRelPath( BasePath, out var p ) ? p : Utf8RelPath.Empty ) )
{ {
foreach( var path in ModFunctions.GetAllFiles( file, Meta ) ) foreach( var path in ModFunctions.GetAllFiles( file, Meta ) )
{ {
identifier.Identify( ChangedItems, path ); identifier.Identify( ChangedItems, path.ToGamePath() );
} }
} }
foreach( var path in Meta.FileSwaps.Keys ) foreach( var path in Meta.FileSwaps.Keys )
{ {
identifier.Identify( ChangedItems, path ); identifier.Identify( ChangedItems, path.ToGamePath() );
} }
LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) ); LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) );
@ -133,4 +133,3 @@ namespace Penumbra.Mod
public override string ToString() public override string ToString()
=> SortOrder.FullPath; => SortOrder.FullPath;
} }
}

View file

@ -1,12 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Penumbra.GameData.Util; using Penumbra.GameData.ByteString;
using Penumbra.Structs;
using Penumbra.Util; namespace Penumbra.Mod;
namespace Penumbra.Mod
{
// Functions that do not really depend on only one component of a mod. // Functions that do not really depend on only one component of a mod.
public static class ModFunctions public static class ModFunctions
{ {
@ -23,10 +21,10 @@ namespace Penumbra.Mod
return anyChanges; return anyChanges;
} }
public static HashSet< GamePath > GetFilesForConfig( RelPath relPath, ModSettings settings, ModMeta meta ) public static HashSet< Utf8GamePath > GetFilesForConfig( Utf8RelPath relPath, ModSettings settings, ModMeta meta )
{ {
var doNotAdd = false; var doNotAdd = false;
var files = new HashSet< GamePath >(); var files = new HashSet< Utf8GamePath >();
foreach( var group in meta.Groups.Values.Where( g => g.Options.Count > 0 ) ) foreach( var group in meta.Groups.Values.Where( g => g.Options.Count > 0 ) )
{ {
doNotAdd |= group.ApplyGroupFiles( relPath, settings.Settings[ group.GroupName ], files ); doNotAdd |= group.ApplyGroupFiles( relPath, settings.Settings[ group.GroupName ], files );
@ -34,15 +32,15 @@ namespace Penumbra.Mod
if( !doNotAdd ) if( !doNotAdd )
{ {
files.Add( new GamePath( relPath ) ); files.Add( relPath.ToGamePath() );
} }
return files; return files;
} }
public static HashSet< GamePath > GetAllFiles( RelPath relPath, ModMeta meta ) public static HashSet< Utf8GamePath > GetAllFiles( Utf8RelPath relPath, ModMeta meta )
{ {
var ret = new HashSet< GamePath >(); var ret = new HashSet< Utf8GamePath >();
foreach( var option in meta.Groups.Values.SelectMany( g => g.Options ) ) foreach( var option in meta.Groups.Values.SelectMany( g => g.Options ) )
{ {
if( option.OptionFiles.TryGetValue( relPath, out var files ) ) if( option.OptionFiles.TryGetValue( relPath, out var files ) )
@ -67,32 +65,32 @@ namespace Penumbra.Mod
Settings = namedSettings.Settings.Keys.ToDictionary( k => k, _ => 0 ), Settings = namedSettings.Settings.Keys.ToDictionary( k => k, _ => 0 ),
}; };
foreach( var kvp in namedSettings.Settings ) foreach( var setting in namedSettings.Settings.Keys )
{ {
if( !meta.Groups.TryGetValue( kvp.Key, out var info ) ) if( !meta.Groups.TryGetValue( setting, out var info ) )
{ {
continue; continue;
} }
if( info.SelectionType == SelectType.Single ) if( info.SelectionType == SelectType.Single )
{ {
if( namedSettings.Settings[ kvp.Key ].Count == 0 ) if( namedSettings.Settings[ setting ].Count == 0 )
{ {
ret.Settings[ kvp.Key ] = 0; ret.Settings[ setting ] = 0;
} }
else else
{ {
var idx = info.Options.FindIndex( o => o.OptionName == namedSettings.Settings[ kvp.Key ].Last() ); var idx = info.Options.FindIndex( o => o.OptionName == namedSettings.Settings[ setting ].Last() );
ret.Settings[ kvp.Key ] = idx < 0 ? 0 : idx; ret.Settings[ setting ] = idx < 0 ? 0 : idx;
} }
} }
else else
{ {
foreach( var idx in namedSettings.Settings[ kvp.Key ] foreach( var idx in namedSettings.Settings[ setting ]
.Select( option => info.Options.FindIndex( o => o.OptionName == option ) ) .Select( option => info.Options.FindIndex( o => o.OptionName == option ) )
.Where( idx => idx >= 0 ) ) .Where( idx => idx >= 0 ) )
{ {
ret.Settings[ kvp.Key ] |= 1 << idx; ret.Settings[ setting ] |= 1 << idx;
} }
} }
} }
@ -100,4 +98,3 @@ namespace Penumbra.Mod
return ret; return ret;
} }
} }
}

View file

@ -4,11 +4,11 @@ using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Structs;
namespace Penumbra.Mod namespace Penumbra.Mod;
{
// Contains descriptive data about the mod as well as possible settings and fileswaps. // Contains descriptive data about the mod as well as possible settings and fileswaps.
public class ModMeta public class ModMeta
{ {
@ -48,8 +48,8 @@ namespace Penumbra.Mod
public string Version { get; set; } = ""; public string Version { get; set; } = "";
public string Website { get; set; } = ""; public string Website { get; set; } = "";
[JsonProperty( ItemConverterType = typeof( GamePathConverter ) )] [JsonProperty( ItemConverterType = typeof( FullPath.FullPathConverter ) )]
public Dictionary< GamePath, GamePath > FileSwaps { get; set; } = new(); public Dictionary< Utf8GamePath, FullPath > FileSwaps { get; set; } = new();
public Dictionary< string, OptionGroup > Groups { get; set; } = new(); public Dictionary< string, OptionGroup > Groups { get; set; } = new();
@ -134,4 +134,3 @@ namespace Penumbra.Mod
} }
} }
} }
}

View file

@ -1,10 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Penumbra.Structs;
namespace Penumbra.Mod namespace Penumbra.Mod;
{
// Contains the settings for a given mod. // Contains the settings for a given mod.
public class ModSettings public class ModSettings
{ {
@ -31,7 +30,7 @@ namespace Penumbra.Mod
public static ModSettings DefaultSettings( ModMeta meta ) public static ModSettings DefaultSettings( ModMeta meta )
{ {
return new() return new ModSettings
{ {
Enabled = false, Enabled = false,
Priority = 0, Priority = 0,
@ -72,4 +71,3 @@ namespace Penumbra.Mod
.Aggregate( false, ( current, name ) => current | FixSpecificSetting( name, meta ) ); .Aggregate( false, ( current, name ) => current | FixSpecificSetting( name, meta ) );
} }
} }
}

View file

@ -1,9 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Penumbra.Structs;
namespace Penumbra.Mod namespace Penumbra.Mod;
{
// Contains settings with the option selections stored by names instead of index. // Contains settings with the option selections stored by names instead of index.
// This is meant to make them possibly more portable when we support importing collections from other users. // This is meant to make them possibly more portable when we support importing collections from other users.
// Enabled does not exist, because disabled mods would not be exported in this way. // Enabled does not exist, because disabled mods would not be exported in this way.
@ -45,4 +44,3 @@ namespace Penumbra.Mod
} }
} }
} }
}

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.Interop;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Util; using Penumbra.Util;
@ -250,8 +249,11 @@ public class CollectionManager
public bool CreateCharacterCollection( string characterName ) public bool CreateCharacterCollection( string characterName )
{ {
if( !CharacterCollection.ContainsKey( characterName ) ) if( CharacterCollection.ContainsKey( characterName ) )
{ {
return false;
}
CharacterCollection[ characterName ] = ModCollection.Empty; CharacterCollection[ characterName ] = ModCollection.Empty;
Penumbra.Config.CharacterCollections[ characterName ] = string.Empty; Penumbra.Config.CharacterCollections[ characterName ] = string.Empty;
Penumbra.Config.Save(); Penumbra.Config.Save();
@ -259,9 +261,6 @@ public class CollectionManager
return true; return true;
} }
return false;
}
public void RemoveCharacterCollection( string characterName ) public void RemoveCharacterCollection( string characterName )
{ {
if( CharacterCollection.TryGetValue( characterName, out var collection ) ) if( CharacterCollection.TryGetValue( characterName, out var collection ) )
@ -299,7 +298,7 @@ public class CollectionManager
private bool LoadForcedCollection( Configuration config ) private bool LoadForcedCollection( Configuration config )
{ {
if( config.ForcedCollection == string.Empty ) if( config.ForcedCollection.Length == 0 )
{ {
ForcedCollection = ModCollection.Empty; ForcedCollection = ModCollection.Empty;
return false; return false;
@ -320,7 +319,7 @@ public class CollectionManager
private bool LoadDefaultCollection( Configuration config ) private bool LoadDefaultCollection( Configuration config )
{ {
if( config.DefaultCollection == string.Empty ) if( config.DefaultCollection.Length == 0 )
{ {
DefaultCollection = ModCollection.Empty; DefaultCollection = ModCollection.Empty;
return false; return false;
@ -345,7 +344,7 @@ public class CollectionManager
foreach( var (player, collectionName) in config.CharacterCollections.ToArray() ) foreach( var (player, collectionName) in config.CharacterCollections.ToArray() )
{ {
Penumbra.PlayerWatcher.AddPlayerToWatch( player ); Penumbra.PlayerWatcher.AddPlayerToWatch( player );
if( collectionName == string.Empty ) if( collectionName.Length == 0 )
{ {
CharacterCollection.Add( player, ModCollection.Empty ); CharacterCollection.Add( player, ModCollection.Empty );
} }

View file

@ -1,17 +1,16 @@
using Dalamud.Plugin;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Interop;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Mods namespace Penumbra.Mods;
{
// A ModCollection is a named set of ModSettings to all of the users' installed mods. // A ModCollection is a named set of ModSettings to all of the users' installed mods.
// It is meant to be local only, and thus should always contain settings for every mod, not just the enabled ones. // It is meant to be local only, and thus should always contain settings for every mod, not just the enabled ones.
// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made. // Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
@ -104,7 +103,10 @@ namespace Penumbra.Mods
} }
if( clear ) if( clear )
{
settings.Settings.Clear(); settings.Settings.Clear();
}
if( settings.FixInvalidSettings( meta ) ) if( settings.FixInvalidSettings( meta ) )
{ {
Save(); Save();
@ -247,9 +249,8 @@ namespace Penumbra.Mods
data ); data );
} }
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath ) public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath ); => Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
public static readonly ModCollection Empty = new() { Name = "" }; public static readonly ModCollection Empty = new() { Name = "" };
} }
}

View file

@ -10,7 +10,6 @@ using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Structs;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Mods; namespace Penumbra.Mods;
@ -21,13 +20,12 @@ public class ModCollectionCache
{ {
// Shared caches to avoid allocations. // Shared caches to avoid allocations.
private static readonly BitArray FileSeen = new(256); private static readonly BitArray FileSeen = new(256);
private static readonly Dictionary< GamePath, Mod.Mod > RegisteredFiles = new(256); private static readonly Dictionary< Utf8GamePath, Mod.Mod > RegisteredFiles = new(256);
public readonly Dictionary< string, Mod.Mod > AvailableMods = new(); public readonly Dictionary< string, Mod.Mod > AvailableMods = new();
private readonly SortedList< string, object? > _changedItems = new(); private readonly SortedList< string, object? > _changedItems = new();
public readonly Dictionary< GamePath, FullPath > ResolvedFiles = new(); public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
public readonly HashSet< FullPath > MissingFiles = new(); public readonly HashSet< FullPath > MissingFiles = new();
public readonly HashSet< ulong > Checksums = new(); public readonly HashSet< ulong > Checksums = new();
public readonly MetaManager MetaManipulations; public readonly MetaManager MetaManipulations;
@ -61,7 +59,6 @@ public class ModCollectionCache
public void CalculateEffectiveFileList() public void CalculateEffectiveFileList()
{ {
ResolvedFiles.Clear(); ResolvedFiles.Clear();
SwappedFiles.Clear();
MissingFiles.Clear(); MissingFiles.Clear();
RegisteredFiles.Clear(); RegisteredFiles.Clear();
_changedItems.Clear(); _changedItems.Clear();
@ -85,7 +82,7 @@ public class ModCollectionCache
private void SetChangedItems() private void SetChangedItems()
{ {
if( _changedItems.Count > 0 || ResolvedFiles.Count + SwappedFiles.Count + MetaManipulations.Count == 0 ) if( _changedItems.Count > 0 || ResolvedFiles.Count + MetaManipulations.Count == 0 )
{ {
return; return;
} }
@ -98,12 +95,7 @@ public class ModCollectionCache
var identifier = GameData.GameData.GetIdentifier(); var identifier = GameData.GameData.GetIdentifier();
foreach( var resolved in ResolvedFiles.Keys.Where( file => !metaFiles.Contains( file ) ) ) foreach( var resolved in ResolvedFiles.Keys.Where( file => !metaFiles.Contains( file ) ) )
{ {
identifier.Identify( _changedItems, resolved ); identifier.Identify( _changedItems, resolved.ToGamePath() );
}
foreach( var swapped in SwappedFiles.Keys )
{
identifier.Identify( _changedItems, swapped );
} }
} }
catch( Exception e ) catch( Exception e )
@ -134,12 +126,12 @@ public class ModCollectionCache
AddRemainingFiles( mod ); AddRemainingFiles( mod );
} }
private bool FilterFile( GamePath gamePath ) private static bool FilterFile( Utf8GamePath gamePath )
{ {
// If audio streaming is not disabled, replacing .scd files crashes the game, // If audio streaming is not disabled, replacing .scd files crashes the game,
// so only add those files if it is disabled. // so only add those files if it is disabled.
if( !Penumbra.Config.DisableSoundStreaming if( !Penumbra.Config.DisableSoundStreaming
&& gamePath.ToString().EndsWith( ".scd", StringComparison.InvariantCultureIgnoreCase ) ) && gamePath.Path.EndsWith( '.', 's', 'c', 'd' ) )
{ {
return true; return true;
} }
@ -148,7 +140,7 @@ public class ModCollectionCache
} }
private void AddFile( Mod.Mod mod, GamePath gamePath, FullPath file ) private void AddFile( Mod.Mod mod, Utf8GamePath gamePath, FullPath file )
{ {
if( FilterFile( gamePath ) ) if( FilterFile( gamePath ) )
{ {
@ -187,8 +179,7 @@ public class ModCollectionCache
{ {
foreach( var (file, paths) in option.OptionFiles ) foreach( var (file, paths) in option.OptionFiles )
{ {
var fullPath = new FullPath( mod.Data.BasePath, var fullPath = new FullPath( mod.Data.BasePath, file );
NewRelPath.FromString( file.ToString(), out var p ) ? p : NewRelPath.Empty ); // TODO
var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals( fullPath ) ); var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals( fullPath ) );
if( idx < 0 ) if( idx < 0 )
{ {
@ -259,7 +250,7 @@ public class ModCollectionCache
{ {
if( file.ToGamePath( mod.Data.BasePath, out var gamePath ) ) if( file.ToGamePath( mod.Data.BasePath, out var gamePath ) )
{ {
AddFile( mod, new GamePath( gamePath.ToString() ), file ); // TODO AddFile( mod, gamePath, file );
} }
else else
{ {
@ -294,7 +285,7 @@ public class ModCollectionCache
if( !RegisteredFiles.TryGetValue( key, out var oldMod ) ) if( !RegisteredFiles.TryGetValue( key, out var oldMod ) )
{ {
RegisteredFiles.Add( key, mod ); RegisteredFiles.Add( key, mod );
SwappedFiles.Add( key, value ); ResolvedFiles.Add( key, value );
} }
else else
{ {
@ -341,54 +332,54 @@ public class ModCollectionCache
public void RemoveMod( DirectoryInfo basePath ) public void RemoveMod( DirectoryInfo basePath )
{ {
if( AvailableMods.TryGetValue( basePath.Name, out var mod ) ) if( !AvailableMods.TryGetValue( basePath.Name, out var mod ) )
{ {
return;
}
AvailableMods.Remove( basePath.Name ); AvailableMods.Remove( basePath.Name );
if( mod.Settings.Enabled ) if( !mod.Settings.Enabled )
{ {
return;
}
CalculateEffectiveFileList(); CalculateEffectiveFileList();
if( mod.Data.Resources.MetaManipulations.Count > 0 ) if( mod.Data.Resources.MetaManipulations.Count > 0 )
{ {
UpdateMetaManipulations(); UpdateMetaManipulations();
} }
} }
}
}
private class PriorityComparer : IComparer< Mod.Mod >
{
public int Compare( Mod.Mod? x, Mod.Mod? y )
=> ( x?.Settings.Priority ?? 0 ).CompareTo( y?.Settings.Priority ?? 0 );
}
private static readonly PriorityComparer Comparer = new();
public void AddMod( ModSettings settings, ModData data, bool updateFileList = true ) public void AddMod( ModSettings settings, ModData data, bool updateFileList = true )
{ {
if( !AvailableMods.TryGetValue( data.BasePath.Name, out var existingMod ) ) if( AvailableMods.ContainsKey( data.BasePath.Name ) )
{ {
var newMod = new Mod.Mod( settings, data ); return;
AvailableMods[ data.BasePath.Name ] = newMod; }
if( updateFileList && settings.Enabled ) AvailableMods[ data.BasePath.Name ] = new Mod.Mod( settings, data );
if( !updateFileList || !settings.Enabled )
{ {
return;
}
CalculateEffectiveFileList(); CalculateEffectiveFileList();
if( data.Resources.MetaManipulations.Count > 0 ) if( data.Resources.MetaManipulations.Count > 0 )
{ {
UpdateMetaManipulations(); UpdateMetaManipulations();
} }
} }
}
}
public FullPath? GetCandidateForGameFile( GamePath gameResourcePath ) public FullPath? GetCandidateForGameFile( Utf8GamePath gameResourcePath )
{ {
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) ) if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
{ {
return null; return null;
} }
if( candidate.FullName.Length >= 260 || !candidate.Exists ) if( candidate.InternalName.Length > Utf8GamePath.MaxGamePathLength
|| candidate.IsRooted && !candidate.Exists )
{ {
return null; return null;
} }
@ -396,9 +387,6 @@ public class ModCollectionCache
return candidate; return candidate;
} }
public GamePath? GetSwappedFilePath( GamePath gameResourcePath ) public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null; => GetCandidateForGameFile( gameResourcePath );
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
=> GetCandidateForGameFile( gameResourcePath )?.FullName.Replace( '\\', '/' ) ?? GetSwappedFilePath( gameResourcePath ) ?? null;
} }

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mod; using Penumbra.Mod;
@ -347,15 +348,7 @@ namespace Penumbra.Mods
return true; return true;
} }
public bool CheckCrc64( ulong crc ) public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
{
if( Collections.ActiveCollection.Cache?.Checksums.Contains( crc ) ?? false )
return true;
return Collections.ForcedCollection.Cache?.Checksums.Contains( crc ) ?? false;
}
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
{ {
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath ); var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath ); ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );

View file

@ -4,7 +4,6 @@ using System.ComponentModel;
using System.IO; using System.IO;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Structs;
namespace Penumbra.Mods; namespace Penumbra.Mods;

View file

@ -18,22 +18,6 @@ using System.Linq;
namespace Penumbra; namespace Penumbra;
public class Penumbra2 // : IDalamudPlugin
{
public string Name
=> "Penumbra";
private const string CommandName = "/penumbra";
public static Configuration Config { get; private set; } = null!;
public static ResourceLoader ResourceLoader { get; private set; } = null!;
public void Dispose()
{
ResourceLoader.Dispose();
}
}
public class Penumbra : IDalamudPlugin public class Penumbra : IDalamudPlugin
{ {
public string Name public string Name
@ -54,6 +38,7 @@ public class Penumbra : IDalamudPlugin
public ResourceLoader ResourceLoader { get; } public ResourceLoader ResourceLoader { get; }
public ResourceLogger ResourceLogger { get; }
//public PathResolver PathResolver { get; } //public PathResolver PathResolver { get; }
public SettingsInterface SettingsInterface { get; } public SettingsInterface SettingsInterface { get; }
@ -81,19 +66,18 @@ public class Penumbra : IDalamudPlugin
CharacterUtility = new CharacterUtility(); CharacterUtility = new CharacterUtility();
MetaDefaults = new MetaDefaults(); MetaDefaults = new MetaDefaults();
ResourceLoader = new ResourceLoader( this ); ResourceLoader = new ResourceLoader( this );
ResourceLogger = new ResourceLogger( ResourceLoader );
PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects );
ModManager = new ModManager(); ModManager = new ModManager();
ModManager.DiscoverMods(); ModManager.DiscoverMods();
//PathResolver = new PathResolver( ResourceLoader, gameUtils );
PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects );
ObjectReloader = new ObjectReloader( ModManager, Config.WaitFrames ); ObjectReloader = new ObjectReloader( ModManager, Config.WaitFrames );
//PathResolver = new PathResolver( ResourceLoader, gameUtils );
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand ) Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
{ {
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods", HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods",
} ); } );
ResourceLoader.EnableReplacements();
ResourceLoader.EnableLogging();
if( Config.DebugMode ) if( Config.DebugMode )
{ {
ResourceLoader.EnableDebug(); ResourceLoader.EnableDebug();
@ -112,7 +96,7 @@ public class Penumbra : IDalamudPlugin
CreateWebServer(); CreateWebServer();
} }
if( !Config.EnablePlayerWatch || !Config.IsEnabled ) if( !Config.EnablePlayerWatch || !Config.EnableMods )
{ {
PlayerWatcher.Disable(); PlayerWatcher.Disable();
} }
@ -122,16 +106,25 @@ public class Penumbra : IDalamudPlugin
PluginLog.Debug( "Triggered Redraw of {Player}.", p.Name ); PluginLog.Debug( "Triggered Redraw of {Player}.", p.Name );
ObjectReloader.RedrawObject( p, RedrawType.OnlyWithSettings ); ObjectReloader.RedrawObject( p, RedrawType.OnlyWithSettings );
}; };
ResourceLoader.EnableHooks();
if (Config.EnableMods)
ResourceLoader.EnableReplacements();
if (Config.DebugMode)
ResourceLoader.EnableDebug();
if (Config.EnableFullResourceLogging)
ResourceLoader.EnableFullLogging();
} }
public bool Enable() public bool Enable()
{ {
if( Config.IsEnabled ) if( Config.EnableMods )
{ {
return false; return false;
} }
Config.IsEnabled = true; Config.EnableMods = true;
ResourceLoader.EnableReplacements();
ResidentResources.Reload(); ResidentResources.Reload();
if( Config.EnablePlayerWatch ) if( Config.EnablePlayerWatch )
{ {
@ -145,12 +138,13 @@ public class Penumbra : IDalamudPlugin
public bool Disable() public bool Disable()
{ {
if( !Config.IsEnabled ) if( !Config.EnableMods )
{ {
return false; return false;
} }
Config.IsEnabled = false; Config.EnableMods = false;
ResourceLoader.DisableReplacements();
ResidentResources.Reload(); ResidentResources.Reload();
if( Config.EnablePlayerWatch ) if( Config.EnablePlayerWatch )
{ {
@ -219,8 +213,10 @@ public class Penumbra : IDalamudPlugin
Dalamud.Commands.RemoveHandler( CommandName ); Dalamud.Commands.RemoveHandler( CommandName );
//PathResolver.Dispose(); //PathResolver.Dispose();
ResourceLogger.Dispose();
ResourceLoader.Dispose(); ResourceLoader.Dispose();
ShutdownWebServer(); ShutdownWebServer();
} }
@ -322,8 +318,8 @@ public class Penumbra : IDalamudPlugin
} }
case "toggle": case "toggle":
{ {
SetEnabled( !Config.IsEnabled ); SetEnabled( !Config.EnableMods );
Dalamud.Chat.Print( Config.IsEnabled Dalamud.Chat.Print( Config.EnableMods
? modsEnabled ? modsEnabled
: modsDisabled ); : modsDisabled );
break; break;

View file

@ -1,103 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
using Newtonsoft.Json;
using Penumbra.GameData.Util;
using Penumbra.Util;
namespace Penumbra.Structs
{
public enum SelectType
{
Single,
Multi,
}
public struct Option
{
public string OptionName;
public string OptionDesc;
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< GamePath > ) )]
public Dictionary< RelPath, HashSet< GamePath > > OptionFiles;
public bool AddFile( RelPath filePath, GamePath gamePath )
{
if( OptionFiles.TryGetValue( filePath, out var set ) )
{
return set.Add( gamePath );
}
OptionFiles[ filePath ] = new HashSet< GamePath >() { gamePath };
return true;
}
}
public struct OptionGroup
{
public string GroupName;
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
public SelectType SelectionType;
public List< Option > Options;
private bool ApplySingleGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths )
{
// Selection contains the path, merge all GamePaths for this config.
if( Options[ selection ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
{
paths.UnionWith( groupPaths );
return true;
}
// If the group contains the file in another selection, return true to skip it for default files.
for( var i = 0; i < Options.Count; ++i )
{
if( i == selection )
{
continue;
}
if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
{
return true;
}
}
return false;
}
private bool ApplyMultiGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths )
{
var doNotAdd = false;
for( var i = 0; i < Options.Count; ++i )
{
if( ( selection & ( 1 << i ) ) != 0 )
{
if( Options[ i ].OptionFiles.TryGetValue( relPath, out var groupPaths ) )
{
paths.UnionWith( groupPaths );
doNotAdd = true;
}
}
else if( Options[ i ].OptionFiles.ContainsKey( relPath ) )
{
doNotAdd = true;
}
}
return doNotAdd;
}
// Adds all game paths from the given option that correspond to the given RelPath to paths, if any exist.
internal bool ApplyGroupFiles( RelPath relPath, int selection, HashSet< GamePath > paths )
{
return SelectionType switch
{
SelectType.Single => ApplySingleGroupFiles( relPath, selection, paths ),
SelectType.Multi => ApplyMultiGroupFiles( relPath, selection, paths ),
_ => throw new InvalidEnumArgumentException( "Invalid option group type." ),
};
}
}
}

View file

@ -1,8 +1,8 @@
using System.Numerics; using System.Numerics;
using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms; using System.Windows.Forms;
using Dalamud.Interface; using Dalamud.Interface;
using ImGuiNET; using ImGuiNET;
using Penumbra.GameData.ByteString;
namespace Penumbra.UI.Custom namespace Penumbra.UI.Custom
{ {
@ -20,6 +20,19 @@ namespace Penumbra.UI.Custom
ImGui.SetTooltip( "Click to copy to clipboard." ); ImGui.SetTooltip( "Click to copy to clipboard." );
} }
} }
public static unsafe void CopyOnClickSelectable( Utf8String text )
{
if( ImGuiNative.igSelectable_Bool( text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ) != 0 )
{
ImGuiNative.igSetClipboardText( text.Path );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( "Click to copy to clipboard." );
}
}
} }
public static partial class ImGuiCustom public static partial class ImGuiCustom

View file

@ -21,11 +21,21 @@ public partial class SettingsInterface
private string _filePathFilter = string.Empty; private string _filePathFilter = string.Empty;
private string _filePathFilterLower = string.Empty; private string _filePathFilterLower = string.Empty;
private readonly float _leftTextLength = private const float LeftTextLength = 600;
ImGui.CalcTextSize( "chara/human/c0000/obj/body/b0000/material/v0000/mt_c0000b0000_b.mtrl" ).X / ImGuiHelpers.GlobalScale + 40;
private float _arrowLength = 0; private float _arrowLength = 0;
private static void DrawLine( Utf8GamePath path, FullPath name )
{
ImGui.TableNextColumn();
ImGuiCustom.CopyOnClickSelectable( path.Path );
ImGui.TableNextColumn();
ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltLeft );
ImGui.SameLine();
ImGuiCustom.CopyOnClickSelectable( name.InternalName );
}
private static void DrawLine( string path, string name ) private static void DrawLine( string path, string name )
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -45,13 +55,13 @@ public partial class SettingsInterface
_arrowLength = ImGui.CalcTextSize( FontAwesomeIcon.LongArrowAltLeft.ToIconString() ).X / ImGuiHelpers.GlobalScale; _arrowLength = ImGui.CalcTextSize( FontAwesomeIcon.LongArrowAltLeft.ToIconString() ).X / ImGuiHelpers.GlobalScale;
} }
ImGui.SetNextItemWidth( _leftTextLength * ImGuiHelpers.GlobalScale ); ImGui.SetNextItemWidth( LeftTextLength * ImGuiHelpers.GlobalScale );
if( ImGui.InputTextWithHint( "##effective_changes_gfilter", "Filter game path...", ref _gamePathFilter, 256 ) ) if( ImGui.InputTextWithHint( "##effective_changes_gfilter", "Filter game path...", ref _gamePathFilter, 256 ) )
{ {
_gamePathFilterLower = _gamePathFilter.ToLowerInvariant(); _gamePathFilterLower = _gamePathFilter.ToLowerInvariant();
} }
ImGui.SameLine( ( _leftTextLength + _arrowLength ) * ImGuiHelpers.GlobalScale + 3 * ImGui.GetStyle().ItemSpacing.X ); ImGui.SameLine( ( LeftTextLength + _arrowLength ) * ImGuiHelpers.GlobalScale + 3 * ImGui.GetStyle().ItemSpacing.X );
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
if( ImGui.InputTextWithHint( "##effective_changes_ffilter", "Filter file path...", ref _filePathFilter, 256 ) ) if( ImGui.InputTextWithHint( "##effective_changes_ffilter", "Filter file path...", ref _filePathFilter, 256 ) )
{ {
@ -59,7 +69,7 @@ public partial class SettingsInterface
} }
} }
private bool CheckFilters( KeyValuePair< GamePath, FullPath > kvp ) private bool CheckFilters( KeyValuePair< Utf8GamePath, FullPath > kvp )
{ {
if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) ) if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) )
{ {
@ -69,7 +79,7 @@ public partial class SettingsInterface
return !_filePathFilter.Any() || kvp.Value.FullName.ToLowerInvariant().Contains( _filePathFilterLower ); return !_filePathFilter.Any() || kvp.Value.FullName.ToLowerInvariant().Contains( _filePathFilterLower );
} }
private bool CheckFilters( KeyValuePair< GamePath, GamePath > kvp ) private bool CheckFilters( KeyValuePair< Utf8GamePath, Utf8GamePath > kvp )
{ {
if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) ) if( _gamePathFilter.Any() && !kvp.Key.ToString().Contains( _gamePathFilterLower ) )
{ {
@ -94,11 +104,6 @@ public partial class SettingsInterface
void DrawFileLines( ModCollectionCache cache ) void DrawFileLines( ModCollectionCache cache )
{ {
foreach( var (gp, fp) in cache.ResolvedFiles.Where( CheckFilters ) ) foreach( var (gp, fp) in cache.ResolvedFiles.Where( CheckFilters ) )
{
DrawLine( gp, fp.FullName );
}
foreach( var (gp, fp) in cache.SwappedFiles.Where( CheckFilters ) )
{ {
DrawLine( gp, fp ); DrawLine( gp, fp );
} }
@ -139,24 +144,27 @@ public partial class SettingsInterface
var activeCollection = modManager.Collections.ActiveCollection.Cache; var activeCollection = modManager.Collections.ActiveCollection.Cache;
var forcedCollection = modManager.Collections.ForcedCollection.Cache; var forcedCollection = modManager.Collections.ForcedCollection.Cache;
var (activeResolved, activeSwap, activeMeta) = activeCollection != null var (activeResolved, activeMeta) = activeCollection != null
? ( activeCollection.ResolvedFiles.Count, activeCollection.SwappedFiles.Count, activeCollection.MetaManipulations.Count ) ? ( activeCollection.ResolvedFiles.Count, activeCollection.MetaManipulations.Count )
: ( 0, 0, 0 ); : ( 0, 0 );
var (forcedResolved, forcedSwap, forcedMeta) = forcedCollection != null var (forcedResolved, forcedMeta) = forcedCollection != null
? ( forcedCollection.ResolvedFiles.Count, forcedCollection.SwappedFiles.Count, forcedCollection.MetaManipulations.Count ) ? ( forcedCollection.ResolvedFiles.Count, forcedCollection.MetaManipulations.Count )
: ( 0, 0, 0 ); : ( 0, 0 );
var totalLines = activeResolved + forcedResolved + activeSwap + forcedSwap + activeMeta + forcedMeta; var totalLines = activeResolved + forcedResolved + activeMeta + forcedMeta;
if( totalLines == 0 ) if( totalLines == 0 )
{ {
return; return;
} }
if( ImGui.BeginTable( "##effective_changes", 2, flags, AutoFillSize ) ) if( !ImGui.BeginTable( "##effective_changes", 2, flags, AutoFillSize ) )
{ {
raii.Push( ImGui.EndTable ); return;
ImGui.TableSetupColumn( "##tableGamePathCol", ImGuiTableColumnFlags.None, _leftTextLength * ImGuiHelpers.GlobalScale ); }
if( _filePathFilter.Any() || _gamePathFilter.Any() ) raii.Push( ImGui.EndTable );
ImGui.TableSetupColumn( "##tableGamePathCol", ImGuiTableColumnFlags.None, LeftTextLength * ImGuiHelpers.GlobalScale );
if( _filePathFilter.Length > 0 || _gamePathFilter.Length > 0 )
{ {
DrawFilteredRows( activeCollection, forcedCollection ); DrawFilteredRows( activeCollection, forcedCollection );
} }
@ -180,14 +188,9 @@ public partial class SettingsInterface
if( row < activeResolved ) if( row < activeResolved )
{ {
var (gamePath, file) = activeCollection!.ResolvedFiles.ElementAt( row ); var (gamePath, file) = activeCollection!.ResolvedFiles.ElementAt( row );
DrawLine( gamePath, file.FullName ); DrawLine( gamePath, file );
} }
else if( ( row -= activeResolved ) < activeSwap ) else if( ( row -= activeResolved ) < activeMeta )
{
var (gamePath, swap) = activeCollection!.SwappedFiles.ElementAt( row );
DrawLine( gamePath, swap );
}
else if( ( row -= activeSwap ) < activeMeta )
{ {
var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row ); var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( manip.IdentifierString(), mod.Data.Meta.Name ); DrawLine( manip.IdentifierString(), mod.Data.Meta.Name );
@ -195,16 +198,11 @@ public partial class SettingsInterface
else if( ( row -= activeMeta ) < forcedResolved ) else if( ( row -= activeMeta ) < forcedResolved )
{ {
var (gamePath, file) = forcedCollection!.ResolvedFiles.ElementAt( row ); var (gamePath, file) = forcedCollection!.ResolvedFiles.ElementAt( row );
DrawLine( gamePath, file.FullName ); DrawLine( gamePath, file );
}
else if( ( row -= forcedResolved ) < forcedSwap )
{
var (gamePath, swap) = forcedCollection!.SwappedFiles.ElementAt( row );
DrawLine( gamePath, swap );
} }
else else
{ {
row -= forcedSwap; row -= forcedResolved;
var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row ); var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
DrawLine( manip.IdentifierString(), mod.Data.Meta.Name ); DrawLine( manip.IdentifierString(), mod.Data.Meta.Name );
} }
@ -214,4 +212,3 @@ public partial class SettingsInterface
} }
} }
} }
}

View file

@ -11,7 +11,6 @@ using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mod; using Penumbra.Mod;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Structs;
using Penumbra.UI.Custom; using Penumbra.UI.Custom;
using Penumbra.Util; using Penumbra.Util;
using ImGui = ImGuiNET.ImGui; using ImGui = ImGuiNET.ImGui;
@ -56,7 +55,7 @@ public partial class SettingsInterface
private Option? _selectedOption; private Option? _selectedOption;
private string _currentGamePaths = ""; private string _currentGamePaths = "";
private (FullPath name, bool selected, uint color, RelPath relName)[]? _fullFilenameList; private (FullPath name, bool selected, uint color, Utf8RelPath relName)[]? _fullFilenameList;
private readonly Selector _selector; private readonly Selector _selector;
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
@ -218,7 +217,10 @@ public partial class SettingsInterface
indent.Push( 15f ); indent.Push( 15f );
foreach( var file in files ) foreach( var file in files )
{ {
ImGui.Selectable( file ); unsafe
{
ImGuiNative.igSelectable_Bool( file.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero );
}
} }
foreach( var manip in manipulations ) foreach( var manip in manipulations )
@ -258,13 +260,13 @@ public partial class SettingsInterface
foreach( var (source, target) in Meta.FileSwaps ) foreach( var (source, target) in Meta.FileSwaps )
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiCustom.CopyOnClickSelectable( source ); ImGuiCustom.CopyOnClickSelectable( source.Path );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltRight ); ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltRight );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiCustom.CopyOnClickSelectable( target ); ImGuiCustom.CopyOnClickSelectable( target.InternalName );
ImGui.TableNextRow(); ImGui.TableNextRow();
} }
@ -278,7 +280,8 @@ public partial class SettingsInterface
} }
_fullFilenameList = Mod.Data.Resources.ModFiles _fullFilenameList = Mod.Data.Resources.ModFiles
.Select( f => ( f, false, ColorGreen, new RelPath( f, Mod.Data.BasePath ) ) ).ToArray(); .Select( f => ( f, false, ColorGreen, Utf8RelPath.FromFile( f, Mod.Data.BasePath, out var p ) ? p : Utf8RelPath.Empty ) )
.ToArray();
if( Meta.Groups.Count == 0 ) if( Meta.Groups.Count == 0 )
{ {
@ -339,24 +342,23 @@ public partial class SettingsInterface
} }
} }
private static int HandleDefaultString( GamePath[] gamePaths, out int removeFolders ) private static int HandleDefaultString( Utf8GamePath[] gamePaths, out int removeFolders )
{ {
removeFolders = 0; removeFolders = 0;
var defaultIndex = var defaultIndex = gamePaths.IndexOf( p => p.Path.StartsWith( DefaultUtf8GamePath ) );
gamePaths.IndexOf( p => ( ( string )p ).StartsWith( TextDefaultGamePath ) );
if( defaultIndex < 0 ) if( defaultIndex < 0 )
{ {
return defaultIndex; return defaultIndex;
} }
string path = gamePaths[ defaultIndex ]; var path = gamePaths[ defaultIndex ].Path;
if( path.Length == TextDefaultGamePath.Length ) if( path.Length == TextDefaultGamePath.Length )
{ {
return defaultIndex; return defaultIndex;
} }
if( path[ TextDefaultGamePath.Length ] != '-' if( path[ TextDefaultGamePath.Length ] != ( byte )'-'
|| !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ), out removeFolders ) ) || !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ).ToString(), out removeFolders ) )
{ {
return -1; return -1;
} }
@ -373,8 +375,9 @@ public partial class SettingsInterface
var option = ( Option )_selectedOption; var option = ( Option )_selectedOption;
var gamePaths = _currentGamePaths.Split( ';' ).Select( p => new GamePath( p ) ).ToArray(); var gamePaths = _currentGamePaths.Split( ';' )
if( gamePaths.Length == 0 || ( ( string )gamePaths[ 0 ] ).Length == 0 ) .Select( p => Utf8GamePath.FromString( p, out var path, false ) ? path : Utf8GamePath.Empty ).Where( p => !p.IsEmpty ).ToArray();
if( gamePaths.Length == 0 )
{ {
return; return;
} }
@ -518,17 +521,17 @@ public partial class SettingsInterface
Selectable( 0, ColorGreen ); Selectable( 0, ColorGreen );
using var indent = ImGuiRaii.PushIndent( indentWidth ); using var indent = ImGuiRaii.PushIndent( indentWidth );
var tmpPaths = gamePaths.ToArray(); foreach( var gamePath in gamePaths.ToArray() )
foreach( var gamePath in tmpPaths )
{ {
string tmp = gamePath; var tmp = gamePath.ToString();
var old = tmp;
if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue ) if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue )
&& tmp != gamePath ) && tmp != old )
{ {
gamePaths.Remove( gamePath ); gamePaths.Remove( gamePath );
if( tmp.Length > 0 ) if( tmp.Length > 0 && Utf8GamePath.FromString( tmp, out var p, true ) )
{ {
gamePaths.Add( new GamePath( tmp ) ); gamePaths.Add( p );
} }
else if( gamePaths.Count == 0 ) else if( gamePaths.Count == 0 )
{ {

View file

@ -3,14 +3,15 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using ImGuiNET; using ImGuiNET;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Mod;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Structs;
using Penumbra.UI.Custom; using Penumbra.UI.Custom;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.UI namespace Penumbra.UI;
{
public partial class SettingsInterface public partial class SettingsInterface
{ {
private partial class PluginDetails private partial class PluginDetails
@ -24,6 +25,7 @@ namespace Penumbra.UI
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines."; private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
private const string TextNoOptionAvailable = "[Not Available]"; private const string TextNoOptionAvailable = "[Not Available]";
private const string TextDefaultGamePath = "default"; private const string TextDefaultGamePath = "default";
private static readonly Utf8String DefaultUtf8GamePath = Utf8String.FromStringUnsafe( TextDefaultGamePath, true );
private const char GamePathsSeparator = ';'; private const char GamePathsSeparator = ';';
private static readonly string TooltipFilesTabEdit = private static readonly string TooltipFilesTabEdit =
@ -131,7 +133,7 @@ namespace Penumbra.UI
&& newOption.Length != 0 ) && newOption.Length != 0 )
{ {
group.Options.Add( new Option() group.Options.Add( new Option()
{ OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >() } ); { OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >() } );
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() ) if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() )
{ {
@ -212,7 +214,7 @@ namespace Penumbra.UI
{ {
OptionName = newName, OptionName = newName,
OptionDesc = "", OptionDesc = "",
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(), OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
} ); } );
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
@ -330,24 +332,23 @@ namespace Penumbra.UI
var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2; var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2;
for( var idx = 0; idx < swaps.Length + 1; ++idx ) for( var idx = 0; idx < swaps.Length + 1; ++idx )
{ {
var key = idx == swaps.Length ? GamePath.GenerateUnchecked( "" ) : swaps[ idx ]; var key = idx == swaps.Length ? Utf8GamePath.Empty : swaps[ idx ];
var value = idx == swaps.Length ? GamePath.GenerateUnchecked( "" ) : Meta.FileSwaps[ key ]; var value = idx == swaps.Length ? FullPath.Empty : Meta.FileSwaps[ key ];
string keyString = key; var keyString = key.ToString();
string valueString = value; var valueString = value.ToString();
ImGui.SetNextItemWidth( width ); ImGui.SetNextItemWidth( width );
if( ImGui.InputTextWithHint( $"##swapLhs_{idx}", "Enter new file to be replaced...", ref keyString, if( ImGui.InputTextWithHint( $"##swapLhs_{idx}", "Enter new file to be replaced...", ref keyString,
GamePath.MaxGamePathLength, ImGuiInputTextFlags.EnterReturnsTrue ) ) GamePath.MaxGamePathLength, ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
var newKey = new GamePath( keyString ); if( Utf8GamePath.FromString( keyString, out var newKey, true ) && newKey.CompareTo( key ) != 0 )
if( newKey.CompareTo( key ) != 0 )
{ {
if( idx < swaps.Length ) if( idx < swaps.Length )
{ {
Meta.FileSwaps.Remove( key ); Meta.FileSwaps.Remove( key );
} }
if( newKey != string.Empty ) if( !newKey.IsEmpty )
{ {
Meta.FileSwaps[ newKey ] = value; Meta.FileSwaps[ newKey ] = value;
} }
@ -371,7 +372,7 @@ namespace Penumbra.UI
GamePath.MaxGamePathLength, GamePath.MaxGamePathLength,
ImGuiInputTextFlags.EnterReturnsTrue ) ) ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
var newValue = new GamePath( valueString ); var newValue = new FullPath( valueString.ToLowerInvariant() );
if( newValue.CompareTo( value ) != 0 ) if( newValue.CompareTo( value ) != 0 )
{ {
Meta.FileSwaps[ key ] = newValue; Meta.FileSwaps[ key ] = newValue;
@ -383,4 +384,3 @@ namespace Penumbra.UI
} }
} }
} }
}

View file

@ -488,7 +488,7 @@ public partial class SettingsInterface
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
if( ModPanel.DrawSortOrder( mod.Data, _modManager, this ) ) if( ModPanel.DrawSortOrder( mod.Data, Penumbra.ModManager, this ) )
{ {
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
} }
@ -509,7 +509,7 @@ public partial class SettingsInterface
{ {
var change = false; var change = false;
var metaManips = false; var metaManips = false;
foreach( var _ in folder.AllMods( _modManager.Config.SortFoldersFirst ) ) foreach( var _ in folder.AllMods( Penumbra.ModManager.Config.SortFoldersFirst ) )
{ {
var (mod, _, _) = Cache.GetMod( currentIdx++ ); var (mod, _, _) = Cache.GetMod( currentIdx++ );
if( mod != null ) if( mod != null )

View file

@ -3,8 +3,10 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Text.RegularExpressions;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using Dalamud.Logging;
using ImGuiNET; using ImGuiNET;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.Interop; using Penumbra.Interop;
@ -131,7 +133,7 @@ public partial class SettingsInterface
private void DrawEnabledBox() private void DrawEnabledBox()
{ {
var enabled = _config.IsEnabled; var enabled = _config.EnableMods;
if( ImGui.Checkbox( "Enable Mods", ref enabled ) ) if( ImGui.Checkbox( "Enable Mods", ref enabled ) )
{ {
_base._penumbra.SetEnabled( enabled ); _base._penumbra.SetEnabled( enabled );
@ -317,14 +319,84 @@ public partial class SettingsInterface
+ "You usually should not need to do this." ); + "You usually should not need to do this." );
} }
private void DrawEnableFullResourceLoggingBox()
{
var tmp = _config.EnableFullResourceLogging;
if( ImGui.Checkbox( "Enable Full Resource Logging", ref tmp ) && tmp != _config.EnableFullResourceLogging )
{
if( tmp )
{
_base._penumbra.ResourceLoader.EnableFullLogging();
}
else
{
_base._penumbra.ResourceLoader.DisableFullLogging();
}
_config.EnableFullResourceLogging = tmp;
_configChanged = true;
}
ImGui.SameLine();
ImGuiComponents.HelpMarker( "[DEBUG] Enable the logging of all ResourceLoader events indiscriminately." );
}
private void DrawEnableDebugModeBox()
{
var tmp = _config.DebugMode;
if( ImGui.Checkbox( "Enable Debug Mode", ref tmp ) && tmp != _config.DebugMode )
{
if( tmp )
{
_base._penumbra.ResourceLoader.EnableDebug();
}
else
{
_base._penumbra.ResourceLoader.DisableDebug();
}
_config.DebugMode = tmp;
_configChanged = true;
}
ImGui.SameLine();
ImGuiComponents.HelpMarker( "[DEBUG] Enable the Debug Tab and Resource Manager Tab as well as some additional data collection." );
}
private void DrawRequestedResourceLogging()
{
var tmp = _config.EnableResourceLogging;
if( ImGui.Checkbox( "Enable Requested Resource Logging", ref tmp ) )
{
_base._penumbra.ResourceLogger.SetState( tmp );
}
ImGui.SameLine();
ImGuiComponents.HelpMarker( "Log all game paths FFXIV requests to the plugin log.\n"
+ "You can filter the logged paths for those containing the entered string or matching the regex, if the entered string compiles to a valid regex.\n"
+ "Red boundary indicates invalid regex." );
ImGui.SameLine();
var tmpString = Penumbra.Config.ResourceLoggingFilter;
using var color = ImGuiRaii.PushColor( ImGuiCol.Border, 0xFF0000B0, !_base._penumbra.ResourceLogger.ValidRegex );
using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale,
!_base._penumbra.ResourceLogger.ValidRegex );
if( ImGui.InputTextWithHint( "##ResourceLogFilter", "Filter...", ref tmpString, Utf8GamePath.MaxGamePathLength ) )
{
_base._penumbra.ResourceLogger.SetFilter( tmpString );
}
}
private void DrawAdvancedSettings() private void DrawAdvancedSettings()
{ {
DrawTempFolder(); DrawTempFolder();
DrawRequestedResourceLogging();
DrawDisableSoundStreamingBox(); DrawDisableSoundStreamingBox();
DrawLogLoadedFilesBox(); DrawLogLoadedFilesBox();
DrawDisableNotificationsBox(); DrawDisableNotificationsBox();
DrawEnableHttpApiBox(); DrawEnableHttpApiBox();
DrawReloadResourceButton(); DrawReloadResourceButton();
DrawEnableDebugModeBox();
DrawEnableFullResourceLoggingBox();
} }
public static unsafe void Text( Utf8String s ) public static unsafe void Text( Utf8String s )

View file

@ -1,9 +1,7 @@
using System.Numerics; using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using ImGuiNET; using ImGuiNET;
using Penumbra.Mods;
using Penumbra.UI.Custom; using Penumbra.UI.Custom;
using Penumbra.Util;
namespace Penumbra.UI; namespace Penumbra.UI;

View file

@ -5,8 +5,8 @@ using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.UI.Custom; using Penumbra.UI.Custom;
namespace Penumbra.UI namespace Penumbra.UI;
{
public partial class SettingsInterface public partial class SettingsInterface
{ {
internal void DrawChangedItem( string name, object? data, float itemIdOffset = 0 ) internal void DrawChangedItem( string name, object? data, float itemIdOffset = 0 )
@ -37,4 +37,3 @@ namespace Penumbra.UI
} }
} }
} }
}

View file

@ -1,31 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Penumbra.Util namespace Penumbra.Util;
{
public static class ArrayExtensions public static class ArrayExtensions
{ {
public static T[] Slice< T >( this T[] source, int index, int length )
{
var slice = new T[length];
Array.Copy( source, index * length, slice, 0, length );
return slice;
}
public static void Swap< T >( this T[] array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static void Swap< T >( this List< T > array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static int IndexOf< T >( this T[] array, Predicate< T > match ) public static int IndexOf< T >( this T[] array, Predicate< T > match )
{ {
for( var i = 0; i < array.Length; ++i ) for( var i = 0; i < array.Length; ++i )
@ -39,49 +18,16 @@ namespace Penumbra.Util
return -1; return -1;
} }
public static void Swap< T >( this T[] array, T lhs, T rhs )
{
var idx1 = Array.IndexOf( array, lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = Array.IndexOf( array, rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
public static void Swap< T >( this List< T > array, T lhs, T rhs )
{
var idx1 = array.IndexOf( lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = array.IndexOf( rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
public static int IndexOf< T >( this IList< T > array, Func< T, bool > predicate ) public static int IndexOf< T >( this IList< T > array, Func< T, bool > predicate )
{ {
for( var i = 0; i < array.Count; ++i ) for( var i = 0; i < array.Count; ++i )
{ {
if( predicate.Invoke( array[ i ] ) ) if( predicate.Invoke( array[ i ] ) )
{
return i; return i;
} }
}
return -1; return -1;
} }
} }
}

View file

@ -3,10 +3,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace Penumbra.Util namespace Penumbra.Util;
{
public static class BinaryReaderExtensions public static class BinaryReaderExtensions
{ {
/// <summary> /// <summary>
@ -47,60 +46,6 @@ namespace Penumbra.Util
return list; return list;
} }
public static T[] ReadStructuresAsArray< T >( this BinaryReader br, int count ) where T : struct
{
var size = Marshal.SizeOf< T >();
var data = br.ReadBytes( size * count );
// im a pirate arr
var arr = new T[count];
for( var i = 0; i < count; i++ )
{
var offset = size * i;
var span = new ReadOnlySpan< byte >( data, offset, size );
arr[ i ] = MemoryMarshal.Read< T >( span );
}
return arr;
}
/// <summary>
/// Moves the BinaryReader position to offset, reads a string, then
/// sets the reader position back to where it was when it started
/// </summary>
/// <param name="br"></param>
/// <param name="offset">The offset to read a string starting from.</param>
/// <returns></returns>
public static string ReadStringOffsetData( this BinaryReader br, long offset )
=> Encoding.UTF8.GetString( ReadRawOffsetData( br, offset ) );
/// <summary>
/// Moves the BinaryReader position to offset, reads raw bytes until a null byte, then
/// sets the reader position back to where it was when it started
/// </summary>
/// <param name="br"></param>
/// <param name="offset">The offset to read data starting from.</param>
/// <returns></returns>
public static byte[] ReadRawOffsetData( this BinaryReader br, long offset )
{
var originalPosition = br.BaseStream.Position;
br.BaseStream.Position = offset;
var chars = new List< byte >();
byte current;
while( ( current = br.ReadByte() ) != 0 )
{
chars.Add( current );
}
br.BaseStream.Position = originalPosition;
return chars.ToArray();
}
/// <summary> /// <summary>
/// Seeks this BinaryReader's position to the given offset. Syntactic sugar. /// Seeks this BinaryReader's position to the given offset. Syntactic sugar.
/// </summary> /// </summary>
@ -108,30 +53,4 @@ namespace Penumbra.Util
{ {
br.BaseStream.Position = offset; br.BaseStream.Position = offset;
} }
/// <summary>
/// Reads a byte and moves the stream position back to where it started before the operation
/// </summary>
/// <param name="br">The reader to use to read the byte</param>
/// <returns>The byte that was read</returns>
public static byte PeekByte( this BinaryReader br )
{
var data = br.ReadByte();
br.BaseStream.Position--;
return data;
}
/// <summary>
/// Reads bytes and moves the stream position back to where it started before the operation
/// </summary>
/// <param name="br">The reader to use to read the bytes</param>
/// <param name="count">The number of bytes to read</param>
/// <returns>The read bytes</returns>
public static byte[] PeekBytes( this BinaryReader br, int count )
{
var data = br.ReadBytes( count );
br.BaseStream.Position -= count;
return data;
}
}
} }

View file

@ -2,11 +2,10 @@ using System.Collections.Generic;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
namespace Penumbra.Util namespace Penumbra.Util;
{
public static class ChatUtil public static class ChatUtil
{ {
public static void LinkItem( Item item ) public static void LinkItem( Item item )
@ -34,4 +33,3 @@ namespace Penumbra.Util
} ); } );
} }
} }
}

View file

@ -1,57 +0,0 @@
using System.Linq;
using System.Runtime.CompilerServices;
namespace Penumbra.Util
{
/// <summary>
/// Performs the 32-bit reversed variant of the cyclic redundancy check algorithm
/// </summary>
public class Crc32
{
private const uint Poly = 0xedb88320;
private static readonly uint[] CrcArray =
Enumerable.Range( 0, 256 ).Select( i =>
{
var k = ( uint )i;
for( var j = 0; j < 8; j++ )
{
k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1;
}
return k;
} ).ToArray();
public uint Checksum
=> ~_crc32;
private uint _crc32 = 0xFFFFFFFF;
/// <summary>
/// Initializes Crc32's state
/// </summary>
public void Init()
{
_crc32 = 0xFFFFFFFF;
}
/// <summary>
/// Updates Crc32's state with new data
/// </summary>
/// <param name="data">Data to calculate the new CRC from</param>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public void Update( byte[] data )
{
foreach( var b in data )
{
Update( b );
}
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public void Update( byte b )
{
_crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^ ( ( _crc32 >> 8 ) & 0x00FFFFFF );
}
}
}

View file

@ -5,8 +5,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace Penumbra.Util namespace Penumbra.Util;
{
public static class DialogExtensions public static class DialogExtensions
{ {
public static Task< DialogResult > ShowDialogAsync( this CommonDialog form ) public static Task< DialogResult > ShowDialogAsync( this CommonDialog form )
@ -79,4 +79,3 @@ namespace Penumbra.Util
} }
} }
} }
}

View file

@ -1,14 +0,0 @@
using System;
using Dalamud.Logging;
namespace Penumbra.Util
{
public static class GeneralUtil
{
public static void PrintDebugAddress( string name, IntPtr address )
{
var module = Dalamud.SigScanner.Module.BaseAddress.ToInt64();
PluginLog.Debug( "{Name} found at 0x{Address:X16}, +0x{Offset:X}", name, address.ToInt64(), address.ToInt64() - module );
}
}
}

View file

@ -1,12 +0,0 @@
using System.IO;
namespace Penumbra.Util
{
public static class MemoryStreamExtensions
{
public static void Write( this MemoryStream stream, byte[] data )
{
stream.Write( data, 0, data.Length );
}
}
}

View file

@ -16,7 +16,6 @@ public static class ModelChanger
public const string MaterialFormat = "/mt_c0201b0001_{0}.mtrl"; public const string MaterialFormat = "/mt_c0201b0001_{0}.mtrl";
public static readonly Regex MaterialRegex = new(@"/mt_c0201b0001_.*?\.mtrl", RegexOptions.Compiled); public static readonly Regex MaterialRegex = new(@"/mt_c0201b0001_.*?\.mtrl", RegexOptions.Compiled);
public static bool ValidStrings( string from, string to ) public static bool ValidStrings( string from, string to )
=> from.Length != 0 => from.Length != 0
&& to.Length != 0 && to.Length != 0

View file

@ -3,12 +3,10 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using Lumina;
using Lumina.Data.Structs; using Lumina.Data.Structs;
namespace Penumbra.Util namespace Penumbra.Util;
{
public class PenumbraSqPackStream : IDisposable public class PenumbraSqPackStream : IDisposable
{ {
public Stream BaseStream { get; protected set; } public Stream BaseStream { get; protected set; }
@ -351,7 +349,7 @@ namespace Penumbra.Util
public void Dispose() public void Dispose()
{ {
Reader?.Dispose(); Reader.Dispose();
} }
public class PenumbraFileInfo public class PenumbraFileInfo
@ -375,15 +373,10 @@ namespace Penumbra.Util
public byte[] Data { get; internal set; } = new byte[0]; public byte[] Data { get; internal set; } = new byte[0];
public Span< byte > DataSpan
=> Data.AsSpan();
public MemoryStream? FileStream { get; internal set; } public MemoryStream? FileStream { get; internal set; }
public BinaryReader? Reader { get; internal set; } public BinaryReader? Reader { get; internal set; }
public ParsedFilePath? FilePath { get; internal set; }
/// <summary> /// <summary>
/// Called once the files are read out from the dats. Used to further parse the file into usable data structures. /// Called once the files are read out from the dats. Used to further parse the file into usable data structures.
/// </summary> /// </summary>
@ -391,25 +384,6 @@ namespace Penumbra.Util
{ {
// this function is intentionally left blank // this function is intentionally left blank
} }
public virtual void SaveFile( string path )
{
File.WriteAllBytes( path, Data );
}
public string GetFileHash()
{
using var sha256 = System.Security.Cryptography.SHA256.Create();
var hash = sha256.ComputeHash( Data );
var sb = new StringBuilder();
foreach( var b in hash )
{
sb.Append( $"{b:x2}" );
}
return sb.ToString();
}
} }
[StructLayout( LayoutKind.Sequential )] [StructLayout( LayoutKind.Sequential )]
@ -431,4 +405,3 @@ namespace Penumbra.Util
public uint BlockCount; public uint BlockCount;
} }
} }
}

View file

@ -3,8 +3,8 @@ using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Penumbra.Util namespace Penumbra.Util;
{
public class SingleOrArrayConverter< T > : JsonConverter public class SingleOrArrayConverter< T > : JsonConverter
{ {
public override bool CanConvert( Type objectType ) public override bool CanConvert( Type objectType )
@ -43,4 +43,3 @@ namespace Penumbra.Util
writer.WriteEndArray(); writer.WriteEndArray();
} }
} }
}

View file

@ -2,8 +2,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
namespace Penumbra.Util namespace Penumbra.Util;
{
public static class StringPathExtensions public static class StringPathExtensions
{ {
private static readonly HashSet< char > Invalid = new(Path.GetInvalidFileNameChars()); private static readonly HashSet< char > Invalid = new(Path.GetInvalidFileNameChars());
@ -65,4 +65,3 @@ namespace Penumbra.Util
return sb.ToString(); return sb.ToString();
} }
} }
}

View file

@ -1,8 +1,8 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
namespace Penumbra.Util namespace Penumbra.Util;
{
public static class TempFile public static class TempFile
{ {
public static FileInfo TempFileName( DirectoryInfo baseDir, string suffix = "" ) public static FileInfo TempFileName( DirectoryInfo baseDir, string suffix = "" )
@ -31,4 +31,3 @@ namespace Penumbra.Util
return fileName; return fileName;
} }
} }
}