mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Change most things to new byte strings, introduce new ResourceLoader and Logger fully.
This commit is contained in:
parent
5d77cd5514
commit
f5fccb0235
55 changed files with 2681 additions and 2730 deletions
|
|
@ -1,17 +1,21 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.GameData.ByteString;
|
||||
|
||||
[JsonConverter( typeof( FullPathConverter ) )]
|
||||
public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
||||
{
|
||||
public readonly string FullName;
|
||||
public readonly Utf8String InternalName;
|
||||
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() ) )
|
||||
{ }
|
||||
|
||||
|
|
@ -19,10 +23,11 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|||
: this( file.FullName )
|
||||
{ }
|
||||
|
||||
|
||||
public FullPath( string 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 );
|
||||
}
|
||||
|
||||
|
|
@ -35,9 +40,9 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|||
public string Name
|
||||
=> 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 ) )
|
||||
{
|
||||
return false;
|
||||
|
|
@ -45,13 +50,13 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|||
|
||||
var substring = InternalName.Substring( dir.FullName.Length + 1 );
|
||||
|
||||
path = new NewGamePath( substring.Replace( ( byte )'\\', ( byte )'/' ) );
|
||||
path = new Utf8GamePath( substring );
|
||||
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 ) )
|
||||
{
|
||||
return false;
|
||||
|
|
@ -59,7 +64,7 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|||
|
||||
var substring = InternalName.Substring( dir.FullName.Length + 1 );
|
||||
|
||||
path = new NewRelPath( substring );
|
||||
path = new Utf8RelPath( substring.Replace( ( byte )'/', ( byte )'\\' ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -88,9 +93,35 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|||
return InternalName.Equals( other.InternalName );
|
||||
}
|
||||
|
||||
public bool IsRooted
|
||||
=> new Utf8GamePath( InternalName ).IsRooted();
|
||||
|
||||
public override int GetHashCode()
|
||||
=> InternalName.Crc32;
|
||||
|
||||
public override string ToString()
|
||||
=> 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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,20 +3,21 @@ using System.IO;
|
|||
using Dalamud.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.GameData.ByteString;
|
||||
|
||||
// NewGamePath wrap some additional validity checking around Utf8String,
|
||||
// provide some filesystem helpers, and conversion to Json.
|
||||
[JsonConverter( typeof( NewGamePathConverter ) )]
|
||||
public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< NewGamePath >, IDisposable
|
||||
[JsonConverter( typeof( Utf8GamePathConverter ) )]
|
||||
public readonly struct Utf8GamePath : IEquatable< Utf8GamePath >, IComparable< Utf8GamePath >, IDisposable
|
||||
{
|
||||
public const int MaxGamePathLength = 256;
|
||||
|
||||
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;
|
||||
|
||||
public int Length
|
||||
|
|
@ -25,16 +26,16 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
|
|||
public bool IsEmpty
|
||||
=> Path.IsEmpty;
|
||||
|
||||
public NewGamePath ToLower()
|
||||
public Utf8GamePath ToLower()
|
||||
=> 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 );
|
||||
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 );
|
||||
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 initial slashes either, since they are assumed to be by choice.
|
||||
// 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;
|
||||
if( !utf.IsAscii || utf.Length > MaxGamePathLength )
|
||||
|
|
@ -51,14 +52,17 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
|
|||
return false;
|
||||
}
|
||||
|
||||
path = new NewGamePath( lower ? utf.AsciiToLower() : utf );
|
||||
path = new Utf8GamePath( lower ? utf.AsciiToLower() : utf );
|
||||
return true;
|
||||
}
|
||||
|
||||
public NewGamePath Clone()
|
||||
public Utf8GamePath 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;
|
||||
if( s.IsNullOrEmpty() )
|
||||
|
|
@ -83,11 +87,11 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
|
|||
return false;
|
||||
}
|
||||
|
||||
path = new NewGamePath( ascii );
|
||||
path = new Utf8GamePath( ascii );
|
||||
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;
|
||||
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 );
|
||||
}
|
||||
|
||||
public bool Equals( NewGamePath other )
|
||||
public bool Equals( Utf8GamePath other )
|
||||
=> Path.Equals( other.Path );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Path.GetHashCode();
|
||||
|
||||
public int CompareTo( NewGamePath other )
|
||||
public int CompareTo( Utf8GamePath other )
|
||||
=> Path.CompareTo( other.Path );
|
||||
|
||||
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[ 1 ] == ':';
|
||||
|
||||
private class NewGamePathConverter : JsonConverter
|
||||
public class Utf8GamePathConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert( Type objectType )
|
||||
=> objectType == typeof( NewGamePath );
|
||||
=> objectType == typeof( Utf8GamePath );
|
||||
|
||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
||||
{
|
||||
var token = JToken.Load( reader ).ToString();
|
||||
return FromString( token, out var p, true )
|
||||
? 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
|
||||
|
|
@ -150,10 +154,13 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
|
|||
|
||||
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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GamePath ToGamePath()
|
||||
=> GamePath.GenerateUnchecked( ToString() );
|
||||
}
|
||||
|
|
@ -1,23 +1,28 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Dalamud.Utility;
|
||||
using Microsoft.VisualBasic.CompilerServices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Penumbra.GameData.ByteString;
|
||||
|
||||
[JsonConverter( typeof( NewRelPathConverter ) )]
|
||||
public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRelPath >, IDisposable
|
||||
[JsonConverter( typeof( Utf8RelPathConverter ) )]
|
||||
public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf8RelPath >, IDisposable
|
||||
{
|
||||
public const int MaxRelPathLength = 250;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
if( s.IsNullOrEmpty() )
|
||||
|
|
@ -42,11 +47,11 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
|
|||
return false;
|
||||
}
|
||||
|
||||
path = new NewRelPath( ascii );
|
||||
path = new Utf8RelPath( ascii );
|
||||
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;
|
||||
if( !file.FullName.StartsWith( baseDir.FullName ) )
|
||||
|
|
@ -58,7 +63,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
|
|||
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;
|
||||
if( !file.FullName.StartsWith( baseDir.FullName ) )
|
||||
|
|
@ -70,10 +75,10 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
|
|||
return FromString( substring, out path );
|
||||
}
|
||||
|
||||
public NewRelPath( NewGamePath gamePath )
|
||||
public Utf8RelPath( Utf8GamePath gamePath )
|
||||
=> Path = gamePath.Path.Replace( ( byte )'/', ( byte )'\\' );
|
||||
|
||||
public unsafe NewGamePath ToGamePath( int skipFolders = 0 )
|
||||
public unsafe Utf8GamePath ToGamePath( int skipFolders = 0 )
|
||||
{
|
||||
var idx = 0;
|
||||
while( skipFolders > 0 )
|
||||
|
|
@ -82,7 +87,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
|
|||
--skipFolders;
|
||||
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.AsciiToLowerInPlace( ptr, length );
|
||||
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 );
|
||||
|
||||
public bool Equals( NewRelPath other )
|
||||
public bool Equals( Utf8RelPath other )
|
||||
=> Path.Equals( other.Path );
|
||||
|
||||
public override string ToString()
|
||||
|
|
@ -106,17 +111,17 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
|
|||
public void Dispose()
|
||||
=> Path.Dispose();
|
||||
|
||||
private class NewRelPathConverter : JsonConverter
|
||||
public class Utf8RelPathConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert( Type objectType )
|
||||
=> objectType == typeof( NewRelPath );
|
||||
=> objectType == typeof( Utf8RelPath );
|
||||
|
||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
||||
{
|
||||
var token = JToken.Load( reader ).ToString();
|
||||
return FromString( token, out var 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
|
||||
|
|
@ -124,7 +129,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
|
|||
|
||||
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() );
|
||||
}
|
||||
|
|
@ -75,6 +75,19 @@ public sealed unsafe partial class Utf8String : IEquatable< Utf8String >, ICompa
|
|||
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 )
|
||||
{
|
||||
if( chars.Length > Length )
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class ModsController : WebApiController
|
|||
public object GetFiles()
|
||||
{
|
||||
return Penumbra.ModManager.Collections.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
|
||||
o => ( string )o.Key,
|
||||
o => o.Key.ToString(),
|
||||
o => o.Value.FullName
|
||||
)
|
||||
?? new Dictionary< string, string >();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Reflection;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Lumina.Data;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Mods;
|
||||
|
|
@ -78,16 +79,15 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
|
||||
private static string ResolvePath( string path, ModManager manager, ModCollection collection )
|
||||
{
|
||||
if( !Penumbra.Config.IsEnabled )
|
||||
if( !Penumbra.Config.EnableMods )
|
||||
{
|
||||
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 );
|
||||
ret ??= manager.Collections.ForcedCollection.Cache?.ResolveSwappedOrReplacementPath( gamePath );
|
||||
ret ??= path;
|
||||
return ret;
|
||||
return ret?.ToString() ?? path;
|
||||
}
|
||||
|
||||
public string ResolvePath( string path )
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Logging;
|
||||
|
||||
|
|
@ -14,13 +12,19 @@ public class Configuration : IPluginConfiguration
|
|||
|
||||
public int Version { get; set; } = CurrentVersion;
|
||||
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool EnableMods { get; set; } = true;
|
||||
#if DEBUG
|
||||
public bool DebugMode { get; set; } = true;
|
||||
#else
|
||||
public bool DebugMode { get; set; } = false;
|
||||
#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 ShowAdvanced { get; set; }
|
||||
|
||||
public bool DisableFileSystemNotifications { get; set; }
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@ using Dalamud.Game.Gui;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
|
||||
|
||||
namespace Penumbra
|
||||
namespace Penumbra;
|
||||
|
||||
public class Dalamud
|
||||
{
|
||||
public class Dalamud
|
||||
{
|
||||
public static void Initialize(DalamudPluginInterface pluginInterface)
|
||||
=> pluginInterface.Create<Dalamud>();
|
||||
public static void Initialize( DalamudPluginInterface pluginInterface )
|
||||
=> pluginInterface.Create< Dalamud >();
|
||||
|
||||
// @formatter:off
|
||||
[PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
||||
|
|
@ -30,5 +31,4 @@ namespace Penumbra
|
|||
[PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!;
|
||||
[PluginService][RequiredVersion("1.0")] public static TitleScreenMenu TitleScreenMenu { get; private set; } = null!;
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Mod;
|
||||
|
||||
namespace Penumbra.Importer.Models
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ using System.Text;
|
|||
using Dalamud.Logging;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Importer.Models;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
using FileMode = System.IO.FileMode;
|
||||
|
||||
|
|
@ -336,14 +336,18 @@ internal class TexToolsImport
|
|||
{
|
||||
OptionName = opt.Name!,
|
||||
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! );
|
||||
if( optDir.Exists )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public unsafe partial class ResourceLoader
|
|||
{
|
||||
public ResourceHandle* OriginalResource;
|
||||
public ResourceHandle* ManipulatedResource;
|
||||
public NewGamePath OriginalPath;
|
||||
public Utf8GamePath OriginalPath;
|
||||
public FullPath ManipulatedPath;
|
||||
public ResourceCategory Category;
|
||||
public object? ResolverInfo;
|
||||
|
|
@ -44,7 +44,7 @@ public unsafe partial class ResourceLoader
|
|||
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 )
|
||||
{
|
||||
|
|
@ -188,11 +188,11 @@ public unsafe partial class ResourceLoader
|
|||
}
|
||||
}
|
||||
|
||||
// Logging functions for EnableLogging.
|
||||
private static void LogPath( NewGamePath path, bool synchronous )
|
||||
=> PluginLog.Information( $"Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
|
||||
// Logging functions for EnableFullLogging.
|
||||
private static void LogPath( Utf8GamePath path, bool synchronous )
|
||||
=> 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();
|
||||
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 )
|
||||
=> PluginLog.Information( success
|
||||
? $"Loaded {path} from {( custom ? "local files" : "SqPack" )}"
|
||||
: $"Failed to load {path} from {( custom ? "local files" : "SqPack" )}." );
|
||||
? $"[ResourceLoader] Loaded {path} from {( custom ? "local files" : "SqPack" )}"
|
||||
: $"[ResourceLoader] Failed to load {path} from {( custom ? "local files" : "SqPack" )}." );
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
|
||||
[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 )
|
||||
{
|
||||
|
|
@ -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,
|
||||
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." );
|
||||
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 );
|
||||
}
|
||||
|
||||
var valid = NewGamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false );
|
||||
var valid = Utf8GamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false );
|
||||
byte ret;
|
||||
// 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.
|
||||
|
|
@ -151,11 +151,10 @@ public unsafe partial class ResourceLoader
|
|||
}
|
||||
|
||||
// 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( gamePath );
|
||||
return resolved != null ? ( new FullPath( resolved ), null ) : ( null, null );
|
||||
var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path );
|
||||
return( resolved, null );
|
||||
}
|
||||
|
||||
private void DisposeHooks()
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ using Dalamud.Hooking;
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Interop;
|
||||
|
||||
|
|
@ -69,7 +67,7 @@ public unsafe partial class ResourceLoader
|
|||
: 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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public unsafe partial class ResourceLoader : IDisposable
|
|||
// Events can be used to make smarter logging.
|
||||
public bool IsLoggingEnabled { get; private set; }
|
||||
|
||||
public void EnableLogging()
|
||||
public void EnableFullLogging()
|
||||
{
|
||||
if( IsLoggingEnabled )
|
||||
{
|
||||
|
|
@ -31,7 +31,7 @@ public unsafe partial class ResourceLoader : IDisposable
|
|||
EnableHooks();
|
||||
}
|
||||
|
||||
public void DisableLogging()
|
||||
public void DisableFullLogging()
|
||||
{
|
||||
if( !IsLoggingEnabled )
|
||||
{
|
||||
|
|
@ -99,13 +99,13 @@ public unsafe partial class ResourceLoader : IDisposable
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Event fired whenever a resource is returned.
|
||||
// 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.
|
||||
public delegate void ResourceLoadedDelegate( ResourceHandle* handle, NewGamePath originalPath, FullPath? manipulatedPath,
|
||||
public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
|
||||
object? resolveData );
|
||||
|
||||
public event ResourceLoadedDelegate? ResourceLoaded;
|
||||
|
|
@ -118,10 +118,11 @@ public unsafe partial class ResourceLoader : IDisposable
|
|||
public event FileLoadedDelegate? FileLoaded;
|
||||
|
||||
// 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()
|
||||
{
|
||||
DisableFullLogging();
|
||||
DisposeHooks();
|
||||
DisposeTexMdlTreatment();
|
||||
}
|
||||
|
|
|
|||
98
Penumbra/Interop/ResourceLogger.cs
Normal file
98
Penumbra/Interop/ResourceLogger.cs
Normal 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;
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Penumbra.Structs;
|
||||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Penumbra.GameData.ByteString;
|
|||
using Penumbra.Importer;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
|
@ -167,14 +166,14 @@ public class MetaCollection
|
|||
continue;
|
||||
}
|
||||
|
||||
var path = new RelPath( file, basePath );
|
||||
Utf8RelPath.FromFile( file, basePath, out var path );
|
||||
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;
|
||||
AddMeta( group.Key, option.OptionName, metaData );
|
||||
AddMeta( name, option.OptionName, metaData );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using Dalamud.Logging;
|
|||
using Lumina.Data.Files;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -24,7 +23,7 @@ public class MetaManager : IDisposable
|
|||
public FileInformation( object data )
|
||||
=> Data = data;
|
||||
|
||||
public void Write( DirectoryInfo dir, GamePath originalPath )
|
||||
public void Write( DirectoryInfo dir, Utf8GamePath originalPath )
|
||||
{
|
||||
ByteData = Data switch
|
||||
{
|
||||
|
|
@ -45,15 +44,15 @@ public class MetaManager : IDisposable
|
|||
public const string TmpDirectory = "penumbrametatmp";
|
||||
|
||||
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< GamePath, FileInformation > _currentFiles = new();
|
||||
private readonly Dictionary< Utf8GamePath, FileInformation > _currentFiles = new();
|
||||
|
||||
public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations
|
||||
=> _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) );
|
||||
|
||||
public IEnumerable< (GamePath, FullPath) > Files
|
||||
public IEnumerable< (Utf8GamePath, FullPath) > Files
|
||||
=> _currentFiles.Where( kvp => kvp.Value.CurrentFile != null )
|
||||
.Select( kvp => ( kvp.Key, kvp.Value.CurrentFile!.Value ) );
|
||||
|
||||
|
|
@ -121,7 +120,7 @@ public class MetaManager : IDisposable
|
|||
private void ClearDirectory()
|
||||
=> ClearDirectory( _dir );
|
||||
|
||||
public MetaManager( string name, Dictionary< GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir )
|
||||
public MetaManager( string name, Dictionary< Utf8GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir )
|
||||
{
|
||||
_resolvedFiles = resolvedFiles;
|
||||
_dir = new DirectoryInfo( Path.Combine( tempDir.FullName, name.ReplaceBadXivSymbols() ) );
|
||||
|
|
@ -135,13 +134,13 @@ public class MetaManager : IDisposable
|
|||
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 );
|
||||
_resolvedFiles[ kvp.Key ] = kvp.Value.CurrentFile!.Value;
|
||||
if( kvp.Value.Data is EqpFile )
|
||||
value.Write( _dir, key );
|
||||
_resolvedFiles[ key ] = value.CurrentFile!.Value;
|
||||
if( value.Data is EqpFile )
|
||||
{
|
||||
EqpData = kvp.Value.ByteData;
|
||||
EqpData = value.ByteData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +153,7 @@ public class MetaManager : IDisposable
|
|||
}
|
||||
|
||||
_currentManipulations.Add( m, mod );
|
||||
var gamePath = m.CorrespondingFilename();
|
||||
var gamePath = Utf8GamePath.FromString(m.CorrespondingFilename(), out var p, false) ? p : Utf8GamePath.Empty; // TODO
|
||||
try
|
||||
{
|
||||
if( !_currentFiles.TryGetValue( gamePath, out var file ) )
|
||||
|
|
|
|||
|
|
@ -3,16 +3,14 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra
|
||||
namespace Penumbra;
|
||||
|
||||
public static class MigrateConfiguration
|
||||
{
|
||||
public static class MigrateConfiguration
|
||||
{
|
||||
public static void Version0To1( Configuration config )
|
||||
{
|
||||
if( config.Version != 0 )
|
||||
|
|
@ -82,5 +80,4 @@ namespace Penumbra
|
|||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
Penumbra/Mod/GroupInformation.cs
Normal file
102
Penumbra/Mod/GroupInformation.cs
Normal 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." ),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Util;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mod
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
// A complete Mod containing settings (i.e. dependent on a collection)
|
||||
// and the resulting cache.
|
||||
public class Mod
|
||||
{
|
||||
// A complete Mod containing settings (i.e. dependent on a collection)
|
||||
// and the resulting cache.
|
||||
public class Mod
|
||||
{
|
||||
public ModSettings Settings { get; }
|
||||
public ModData Data { get; }
|
||||
public ModCache Cache { get; }
|
||||
|
|
@ -23,13 +22,12 @@ namespace Penumbra.Mod
|
|||
public bool FixSettings()
|
||||
=> 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 );
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Data.Meta.Name;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra.Mod
|
||||
{
|
||||
// The ModCache contains volatile information dependent on all current settings in a collection.
|
||||
public class ModCache
|
||||
{
|
||||
public Dictionary< Mod, (List< GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new();
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
public void AddConflict( Mod precedingMod, GamePath gamePath )
|
||||
// The ModCache contains volatile information dependent on all current settings in a collection.
|
||||
public class ModCache
|
||||
{
|
||||
public Dictionary< Mod, (List< Utf8GamePath > Files, List< MetaManipulation > Manipulations) > Conflicts { get; private set; } = new();
|
||||
|
||||
public void AddConflict( Mod precedingMod, Utf8GamePath gamePath )
|
||||
{
|
||||
if( Conflicts.TryGetValue( precedingMod, out var conflicts ) && !conflicts.Files.Contains( gamePath ) )
|
||||
{
|
||||
|
|
@ -18,7 +18,7 @@ namespace Penumbra.Mod
|
|||
}
|
||||
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
|
||||
{
|
||||
Conflicts[ precedingMod ] = ( new List< GamePath >(), new List< MetaManipulation > { manipulation } );
|
||||
Conflicts[ precedingMod ] = ( new List< Utf8GamePath >(), new List< MetaManipulation > { manipulation } );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,5 +54,4 @@ namespace Penumbra.Mod
|
|||
return kvp.Value;
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,25 +2,23 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mod
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
public class ModCleanup
|
||||
{
|
||||
public class ModCleanup
|
||||
{
|
||||
private const string Duplicates = "Duplicates";
|
||||
private const string Required = "Required";
|
||||
|
||||
|
||||
private readonly DirectoryInfo _baseDir;
|
||||
private readonly ModMeta _mod;
|
||||
private SHA256? _hasher;
|
||||
|
|
@ -51,7 +49,7 @@ namespace Penumbra.Mod
|
|||
}
|
||||
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 )
|
||||
{
|
||||
var newName = $"{mod.BasePath.Name}_{optionGroup}_{option}";
|
||||
var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config!.ModDirectory ), newName );
|
||||
return newDir;
|
||||
return TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config.ModDirectory ), newName );
|
||||
}
|
||||
|
||||
private static ModData CreateNewMod( DirectoryInfo newDir, string newSortOrder )
|
||||
|
|
@ -90,18 +87,18 @@ namespace Penumbra.Mod
|
|||
{
|
||||
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 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 )
|
||||
{
|
||||
var oldPath = Path.Combine( mod.BasePath.FullName, fileName );
|
||||
var oldPath = Path.Combine( mod.BasePath.FullName, fileName.ToString() );
|
||||
unseenPaths.Remove( oldPath );
|
||||
if( File.Exists( oldPath ) )
|
||||
{
|
||||
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 )! );
|
||||
File.Copy( oldPath, newPath, true );
|
||||
}
|
||||
|
|
@ -121,7 +118,7 @@ namespace Penumbra.Mod
|
|||
|
||||
public static void SplitMod( ModData mod )
|
||||
{
|
||||
if( !mod.Meta.Groups.Any() )
|
||||
if( mod.Meta.Groups.Count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -135,7 +132,7 @@ namespace Penumbra.Mod
|
|||
}
|
||||
}
|
||||
|
||||
if( !unseenPaths.Any() )
|
||||
if( unseenPaths.Count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -148,8 +145,10 @@ namespace Penumbra.Mod
|
|||
var defaultOption = new Option()
|
||||
{
|
||||
OptionName = "Files",
|
||||
OptionFiles = unseenPaths.ToDictionary( p => new RelPath( new FileInfo( p ), mod.BasePath ),
|
||||
p => new HashSet< GamePath >() { new( new FileInfo( p ), mod.BasePath ) } ),
|
||||
OptionFiles = unseenPaths.ToDictionary(
|
||||
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 );
|
||||
}
|
||||
|
|
@ -161,7 +160,7 @@ namespace Penumbra.Mod
|
|||
{
|
||||
OptionName = Required,
|
||||
OptionDesc = "",
|
||||
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
||||
OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
|
||||
};
|
||||
|
||||
if( meta.Groups.TryGetValue( Duplicates, out var duplicates ) )
|
||||
|
|
@ -189,35 +188,35 @@ namespace Penumbra.Mod
|
|||
public static void Deduplicate( DirectoryInfo baseDir, ModMeta 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
|
||||
{
|
||||
var deleted = Enumerable.Repeat( false, pair.Value.Count ).ToArray();
|
||||
var hashes = pair.Value.Select( dedup.ComputeHash ).ToArray();
|
||||
var deleted = Enumerable.Repeat( false, value.Count ).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 ] )
|
||||
{
|
||||
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 ] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dedup.ReplaceFile( pair.Value[ i ], pair.Value[ j ] );
|
||||
dedup.ReplaceFile( value[ i ], value[ j ] );
|
||||
deleted[ j ] = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -230,8 +229,11 @@ namespace Penumbra.Mod
|
|||
|
||||
private void ReplaceFile( FileInfo f1, FileInfo f2 )
|
||||
{
|
||||
RelPath relName1 = new( f1, _baseDir );
|
||||
RelPath relName2 = new( f2, _baseDir );
|
||||
if( !Utf8RelPath.FromFile( f1, _baseDir, out var relName1 )
|
||||
|| !Utf8RelPath.FromFile( f2, _baseDir, out var relName2 ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var inOption1 = 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
|
||||
? meta.Groups.Values.Where( g => g.GroupName != Duplicates )
|
||||
|
|
@ -322,16 +324,16 @@ namespace Penumbra.Mod
|
|||
if( requiredIdx >= 0 )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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 )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var newFullPath = Path.Combine( basePath, newRelPath );
|
||||
var newFullPath = Path.Combine( basePath, newRelPath.ToString() );
|
||||
new FileInfo( newFullPath ).Directory!.Create();
|
||||
File.Move( Path.Combine( basePath, oldRelPath ), newFullPath );
|
||||
File.Move( Path.Combine( basePath, oldRelPath.ToString() ), newFullPath );
|
||||
}
|
||||
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 ) )
|
||||
{
|
||||
var firstOption = true;
|
||||
HashSet< (RelPath, GamePath) > groupList = new();
|
||||
HashSet< (Utf8RelPath, Utf8GamePath) > groupList = new();
|
||||
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 ) ) )
|
||||
{
|
||||
optionList.UnionWith( gamePaths.Select( p => ( file, p ) ) );
|
||||
|
|
@ -450,14 +452,14 @@ namespace Penumbra.Mod
|
|||
firstOption = false;
|
||||
}
|
||||
|
||||
var newPath = new Dictionary< RelPath, GamePath >();
|
||||
var newPath = new Dictionary< Utf8RelPath, Utf8GamePath >();
|
||||
foreach( var (path, gamePath) in groupList )
|
||||
{
|
||||
var relPath = new RelPath( gamePath );
|
||||
var relPath = new Utf8RelPath( gamePath );
|
||||
if( newPath.TryGetValue( path, out var usedGamePath ) )
|
||||
{
|
||||
var required = FindOrCreateDuplicates( meta );
|
||||
var usedRelPath = new RelPath( usedGamePath );
|
||||
var usedRelPath = new Utf8RelPath( usedGamePath );
|
||||
required.AddFile( usedRelPath, gamePath );
|
||||
required.AddFile( usedRelPath, usedGamePath );
|
||||
RemoveFromGroups( meta, relPath, gamePath, GroupType.Single );
|
||||
|
|
@ -498,13 +500,15 @@ namespace Penumbra.Mod
|
|||
{
|
||||
OptionDesc = string.Empty,
|
||||
OptionName = optionDir.Name,
|
||||
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
||||
OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
|
||||
};
|
||||
foreach( var file in optionDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||
{
|
||||
var relPath = new RelPath( file, baseDir );
|
||||
var gamePath = new GamePath( file, optionDir );
|
||||
option.OptionFiles[ relPath ] = new HashSet< GamePath > { gamePath };
|
||||
if( Utf8RelPath.FromFile( file, baseDir, out var rel )
|
||||
&& Utf8GamePath.FromFile( file, optionDir, out var game ) )
|
||||
{
|
||||
option.OptionFiles[ rel ] = new HashSet< Utf8GamePath > { game };
|
||||
}
|
||||
}
|
||||
|
||||
if( option.OptionFiles.Any() )
|
||||
|
|
@ -519,9 +523,9 @@ namespace Penumbra.Mod
|
|||
}
|
||||
}
|
||||
|
||||
foreach(var collection in Penumbra.ModManager.Collections.Collections.Values)
|
||||
collection.UpdateSetting(baseDir, meta, true);
|
||||
|
||||
foreach( var collection in Penumbra.ModManager.Collections.Collections.Values )
|
||||
{
|
||||
collection.UpdateSetting( baseDir, meta, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,13 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
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; }
|
||||
|
||||
private string _sortOrderName;
|
||||
|
|
@ -28,7 +28,7 @@ namespace Penumbra.Mod
|
|||
get
|
||||
{
|
||||
var path = SortOrderPath;
|
||||
return path.Any() ? $"{path}/{SortOrderName}" : SortOrderName;
|
||||
return path.Length > 0 ? $"{path}/{SortOrderName}" : SortOrderName;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,17 +40,17 @@ namespace Penumbra.Mod
|
|||
}
|
||||
|
||||
public string FullPath
|
||||
=> SortOrderPath.Any() ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
|
||||
=> SortOrderPath.Length > 0 ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
|
||||
|
||||
public int CompareTo( SortOrder other )
|
||||
=> string.Compare( FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
|
||||
}
|
||||
}
|
||||
|
||||
// ModData contains all permanent information about a mod,
|
||||
// and is independent of collections or settings.
|
||||
// It only changes when the user actively changes the mod or their filesystem.
|
||||
public class ModData
|
||||
{
|
||||
// ModData contains all permanent information about a mod,
|
||||
// and is independent of collections or settings.
|
||||
// It only changes when the user actively changes the mod or their filesystem.
|
||||
public class ModData
|
||||
{
|
||||
public DirectoryInfo BasePath;
|
||||
public ModMeta Meta;
|
||||
public ModResources Resources;
|
||||
|
|
@ -77,24 +77,24 @@ namespace Penumbra.Mod
|
|||
{
|
||||
var identifier = GameData.GameData.GetIdentifier();
|
||||
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 ) )
|
||||
{
|
||||
identifier.Identify( ChangedItems, path );
|
||||
identifier.Identify( ChangedItems, path.ToGamePath() );
|
||||
}
|
||||
}
|
||||
|
||||
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() ) );
|
||||
}
|
||||
|
||||
public static FileInfo MetaFileInfo( DirectoryInfo basePath )
|
||||
=> new( Path.Combine( basePath.FullName, "meta.json" ) );
|
||||
=> new(Path.Combine( basePath.FullName, "meta.json" ));
|
||||
|
||||
public static ModData? LoadMod( ModFolder parentFolder, DirectoryInfo basePath )
|
||||
{
|
||||
|
|
@ -132,5 +132,4 @@ namespace Penumbra.Mod
|
|||
|
||||
public override string ToString()
|
||||
=> SortOrder.FullPath;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.Mod
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
// Functions that do not really depend on only one component of a mod.
|
||||
public static class ModFunctions
|
||||
{
|
||||
// Functions that do not really depend on only one component of a mod.
|
||||
public static class ModFunctions
|
||||
{
|
||||
public static bool CleanUpCollection( Dictionary< string, ModSettings > settings, IEnumerable< DirectoryInfo > modPaths )
|
||||
{
|
||||
var hashes = modPaths.Select( p => p.Name ).ToHashSet();
|
||||
|
|
@ -23,10 +21,10 @@ namespace Penumbra.Mod
|
|||
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 files = new HashSet< GamePath >();
|
||||
var files = new HashSet< Utf8GamePath >();
|
||||
foreach( var group in meta.Groups.Values.Where( g => g.Options.Count > 0 ) )
|
||||
{
|
||||
doNotAdd |= group.ApplyGroupFiles( relPath, settings.Settings[ group.GroupName ], files );
|
||||
|
|
@ -34,15 +32,15 @@ namespace Penumbra.Mod
|
|||
|
||||
if( !doNotAdd )
|
||||
{
|
||||
files.Add( new GamePath( relPath ) );
|
||||
files.Add( relPath.ToGamePath() );
|
||||
}
|
||||
|
||||
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 ) )
|
||||
{
|
||||
if( option.OptionFiles.TryGetValue( relPath, out var files ) )
|
||||
|
|
@ -67,37 +65,36 @@ namespace Penumbra.Mod
|
|||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
var idx = info.Options.FindIndex( o => o.OptionName == namedSettings.Settings[ kvp.Key ].Last() );
|
||||
ret.Settings[ kvp.Key ] = idx < 0 ? 0 : idx;
|
||||
var idx = info.Options.FindIndex( o => o.OptionName == namedSettings.Settings[ setting ].Last() );
|
||||
ret.Settings[ setting ] = idx < 0 ? 0 : idx;
|
||||
}
|
||||
}
|
||||
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 ) )
|
||||
.Where( idx => idx >= 0 ) )
|
||||
{
|
||||
ret.Settings[ kvp.Key ] |= 1 << idx;
|
||||
ret.Settings[ setting ] |= 1 << idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,14 +4,14 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
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.
|
||||
public class ModMeta
|
||||
{
|
||||
// Contains descriptive data about the mod as well as possible settings and fileswaps.
|
||||
public class ModMeta
|
||||
{
|
||||
public uint FileVersion { get; set; }
|
||||
|
||||
public string Name
|
||||
|
|
@ -48,8 +48,8 @@ namespace Penumbra.Mod
|
|||
public string Version { get; set; } = "";
|
||||
public string Website { get; set; } = "";
|
||||
|
||||
[JsonProperty( ItemConverterType = typeof( GamePathConverter ) )]
|
||||
public Dictionary< GamePath, GamePath > FileSwaps { get; set; } = new();
|
||||
[JsonProperty( ItemConverterType = typeof( FullPath.FullPathConverter ) )]
|
||||
public Dictionary< Utf8GamePath, FullPath > FileSwaps { get; set; } = new();
|
||||
|
||||
public Dictionary< string, OptionGroup > Groups { get; set; } = new();
|
||||
|
||||
|
|
@ -133,5 +133,4 @@ namespace Penumbra.Mod
|
|||
PluginLog.Error( $"Could not write meta file for mod {Name} to {filePath.FullName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Structs;
|
||||
|
||||
namespace Penumbra.Mod
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
// Contains the settings for a given mod.
|
||||
public class ModSettings
|
||||
{
|
||||
// Contains the settings for a given mod.
|
||||
public class ModSettings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public Dictionary< string, int > Settings { get; set; } = new();
|
||||
|
|
@ -31,7 +30,7 @@ namespace Penumbra.Mod
|
|||
|
||||
public static ModSettings DefaultSettings( ModMeta meta )
|
||||
{
|
||||
return new()
|
||||
return new ModSettings
|
||||
{
|
||||
Enabled = false,
|
||||
Priority = 0,
|
||||
|
|
@ -71,5 +70,4 @@ namespace Penumbra.Mod
|
|||
return Settings.Keys.ToArray().Union( meta.Groups.Keys )
|
||||
.Aggregate( false, ( current, name ) => current | FixSpecificSetting( name, meta ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.Structs;
|
||||
|
||||
namespace Penumbra.Mod
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
// 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.
|
||||
// Enabled does not exist, because disabled mods would not be exported in this way.
|
||||
public class NamedModSettings
|
||||
{
|
||||
// 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.
|
||||
// Enabled does not exist, because disabled mods would not be exported in this way.
|
||||
public class NamedModSettings
|
||||
{
|
||||
public int Priority { get; set; }
|
||||
public Dictionary< string, HashSet< string > > Settings { get; set; } = new();
|
||||
|
||||
|
|
@ -44,5 +43,4 @@ namespace Penumbra.Mod
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Interop;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
|
|
@ -250,8 +249,11 @@ public class CollectionManager
|
|||
|
||||
public bool CreateCharacterCollection( string characterName )
|
||||
{
|
||||
if( !CharacterCollection.ContainsKey( characterName ) )
|
||||
if( CharacterCollection.ContainsKey( characterName ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CharacterCollection[ characterName ] = ModCollection.Empty;
|
||||
Penumbra.Config.CharacterCollections[ characterName ] = string.Empty;
|
||||
Penumbra.Config.Save();
|
||||
|
|
@ -259,9 +261,6 @@ public class CollectionManager
|
|||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveCharacterCollection( string characterName )
|
||||
{
|
||||
if( CharacterCollection.TryGetValue( characterName, out var collection ) )
|
||||
|
|
@ -299,7 +298,7 @@ public class CollectionManager
|
|||
|
||||
private bool LoadForcedCollection( Configuration config )
|
||||
{
|
||||
if( config.ForcedCollection == string.Empty )
|
||||
if( config.ForcedCollection.Length == 0 )
|
||||
{
|
||||
ForcedCollection = ModCollection.Empty;
|
||||
return false;
|
||||
|
|
@ -320,7 +319,7 @@ public class CollectionManager
|
|||
|
||||
private bool LoadDefaultCollection( Configuration config )
|
||||
{
|
||||
if( config.DefaultCollection == string.Empty )
|
||||
if( config.DefaultCollection.Length == 0 )
|
||||
{
|
||||
DefaultCollection = ModCollection.Empty;
|
||||
return false;
|
||||
|
|
@ -345,7 +344,7 @@ public class CollectionManager
|
|||
foreach( var (player, collectionName) in config.CharacterCollections.ToArray() )
|
||||
{
|
||||
Penumbra.PlayerWatcher.AddPlayerToWatch( player );
|
||||
if( collectionName == string.Empty )
|
||||
if( collectionName.Length == 0 )
|
||||
{
|
||||
CharacterCollection.Add( player, ModCollection.Empty );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods
|
||||
namespace Penumbra.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.
|
||||
// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
|
||||
// Active ModCollections build a cache of currently relevant data.
|
||||
public class ModCollection
|
||||
{
|
||||
// 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.
|
||||
// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
|
||||
// Active ModCollections build a cache of currently relevant data.
|
||||
public class ModCollection
|
||||
{
|
||||
public const string DefaultCollection = "Default";
|
||||
|
||||
public string Name { get; set; }
|
||||
|
|
@ -103,8 +102,11 @@ namespace Penumbra.Mods
|
|||
return;
|
||||
}
|
||||
|
||||
if (clear)
|
||||
if( clear )
|
||||
{
|
||||
settings.Settings.Clear();
|
||||
}
|
||||
|
||||
if( settings.FixInvalidSettings( meta ) )
|
||||
{
|
||||
Save();
|
||||
|
|
@ -188,14 +190,14 @@ namespace Penumbra.Mods
|
|||
}
|
||||
|
||||
public static DirectoryInfo CollectionDir()
|
||||
=> new( Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), "collections" ) );
|
||||
=> new(Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(), "collections" ));
|
||||
|
||||
private static FileInfo FileName( DirectoryInfo collectionDir, string name )
|
||||
=> new( Path.Combine( collectionDir.FullName, $"{name.RemoveInvalidPathSymbols()}.json" ) );
|
||||
=> new(Path.Combine( collectionDir.FullName, $"{name.RemoveInvalidPathSymbols()}.json" ));
|
||||
|
||||
public FileInfo FileName()
|
||||
=> new( Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(),
|
||||
$"{Name.RemoveInvalidPathSymbols()}.json" ) );
|
||||
=> new(Path.Combine( Dalamud.PluginInterface.GetPluginConfigDirectory(),
|
||||
$"{Name.RemoveInvalidPathSymbols()}.json" ));
|
||||
|
||||
public void Save()
|
||||
{
|
||||
|
|
@ -247,9 +249,8 @@ namespace Penumbra.Mods
|
|||
data );
|
||||
}
|
||||
|
||||
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
|
||||
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
|
||||
=> Cache?.ResolveSwappedOrReplacementPath( gameResourcePath );
|
||||
|
||||
public static readonly ModCollection Empty = new() { Name = "" };
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ using Penumbra.GameData.ByteString;
|
|||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
|
@ -21,13 +20,12 @@ public class ModCollectionCache
|
|||
{
|
||||
// Shared caches to avoid allocations.
|
||||
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();
|
||||
|
||||
private readonly SortedList< string, object? > _changedItems = new();
|
||||
public readonly Dictionary< GamePath, FullPath > ResolvedFiles = new();
|
||||
public readonly Dictionary< GamePath, GamePath > SwappedFiles = new();
|
||||
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
|
||||
public readonly HashSet< FullPath > MissingFiles = new();
|
||||
public readonly HashSet< ulong > Checksums = new();
|
||||
public readonly MetaManager MetaManipulations;
|
||||
|
|
@ -61,7 +59,6 @@ public class ModCollectionCache
|
|||
public void CalculateEffectiveFileList()
|
||||
{
|
||||
ResolvedFiles.Clear();
|
||||
SwappedFiles.Clear();
|
||||
MissingFiles.Clear();
|
||||
RegisteredFiles.Clear();
|
||||
_changedItems.Clear();
|
||||
|
|
@ -85,7 +82,7 @@ public class ModCollectionCache
|
|||
|
||||
private void SetChangedItems()
|
||||
{
|
||||
if( _changedItems.Count > 0 || ResolvedFiles.Count + SwappedFiles.Count + MetaManipulations.Count == 0 )
|
||||
if( _changedItems.Count > 0 || ResolvedFiles.Count + MetaManipulations.Count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -98,12 +95,7 @@ public class ModCollectionCache
|
|||
var identifier = GameData.GameData.GetIdentifier();
|
||||
foreach( var resolved in ResolvedFiles.Keys.Where( file => !metaFiles.Contains( file ) ) )
|
||||
{
|
||||
identifier.Identify( _changedItems, resolved );
|
||||
}
|
||||
|
||||
foreach( var swapped in SwappedFiles.Keys )
|
||||
{
|
||||
identifier.Identify( _changedItems, swapped );
|
||||
identifier.Identify( _changedItems, resolved.ToGamePath() );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
@ -134,12 +126,12 @@ public class ModCollectionCache
|
|||
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,
|
||||
// so only add those files if it is disabled.
|
||||
if( !Penumbra.Config.DisableSoundStreaming
|
||||
&& gamePath.ToString().EndsWith( ".scd", StringComparison.InvariantCultureIgnoreCase ) )
|
||||
&& gamePath.Path.EndsWith( '.', 's', 'c', 'd' ) )
|
||||
{
|
||||
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 ) )
|
||||
{
|
||||
|
|
@ -187,8 +179,7 @@ public class ModCollectionCache
|
|||
{
|
||||
foreach( var (file, paths) in option.OptionFiles )
|
||||
{
|
||||
var fullPath = new FullPath( mod.Data.BasePath,
|
||||
NewRelPath.FromString( file.ToString(), out var p ) ? p : NewRelPath.Empty ); // TODO
|
||||
var fullPath = new FullPath( mod.Data.BasePath, file );
|
||||
var idx = mod.Data.Resources.ModFiles.IndexOf( f => f.Equals( fullPath ) );
|
||||
if( idx < 0 )
|
||||
{
|
||||
|
|
@ -259,7 +250,7 @@ public class ModCollectionCache
|
|||
{
|
||||
if( file.ToGamePath( mod.Data.BasePath, out var gamePath ) )
|
||||
{
|
||||
AddFile( mod, new GamePath( gamePath.ToString() ), file ); // TODO
|
||||
AddFile( mod, gamePath, file );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -294,7 +285,7 @@ public class ModCollectionCache
|
|||
if( !RegisteredFiles.TryGetValue( key, out var oldMod ) )
|
||||
{
|
||||
RegisteredFiles.Add( key, mod );
|
||||
SwappedFiles.Add( key, value );
|
||||
ResolvedFiles.Add( key, value );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -341,54 +332,54 @@ public class ModCollectionCache
|
|||
|
||||
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 );
|
||||
if( mod.Settings.Enabled )
|
||||
if( !mod.Settings.Enabled )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateEffectiveFileList();
|
||||
if( mod.Data.Resources.MetaManipulations.Count > 0 )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
if( !AvailableMods.TryGetValue( data.BasePath.Name, out var existingMod ) )
|
||||
if( AvailableMods.ContainsKey( data.BasePath.Name ) )
|
||||
{
|
||||
var newMod = new Mod.Mod( settings, data );
|
||||
AvailableMods[ data.BasePath.Name ] = newMod;
|
||||
return;
|
||||
}
|
||||
|
||||
if( updateFileList && settings.Enabled )
|
||||
AvailableMods[ data.BasePath.Name ] = new Mod.Mod( settings, data );
|
||||
|
||||
if( !updateFileList || !settings.Enabled )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateEffectiveFileList();
|
||||
if( data.Resources.MetaManipulations.Count > 0 )
|
||||
{
|
||||
UpdateMetaManipulations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FullPath? GetCandidateForGameFile( GamePath gameResourcePath )
|
||||
public FullPath? GetCandidateForGameFile( Utf8GamePath gameResourcePath )
|
||||
{
|
||||
if( !ResolvedFiles.TryGetValue( gameResourcePath, out var candidate ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if( candidate.FullName.Length >= 260 || !candidate.Exists )
|
||||
if( candidate.InternalName.Length > Utf8GamePath.MaxGamePathLength
|
||||
|| candidate.IsRooted && !candidate.Exists )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
@ -396,9 +387,6 @@ public class ModCollectionCache
|
|||
return candidate;
|
||||
}
|
||||
|
||||
public GamePath? GetSwappedFilePath( GamePath gameResourcePath )
|
||||
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
|
||||
|
||||
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
|
||||
=> GetCandidateForGameFile( gameResourcePath )?.FullName.Replace( '\\', '/' ) ?? GetSwappedFilePath( gameResourcePath ) ?? null;
|
||||
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
|
||||
=> GetCandidateForGameFile( gameResourcePath );
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mod;
|
||||
|
|
@ -347,15 +348,7 @@ namespace Penumbra.Mods
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool CheckCrc64( ulong crc )
|
||||
{
|
||||
if( Collections.ActiveCollection.Cache?.Checksums.Contains( crc ) ?? false )
|
||||
return true;
|
||||
|
||||
return Collections.ForcedCollection.Cache?.Checksums.Contains( crc ) ?? false;
|
||||
}
|
||||
|
||||
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )
|
||||
public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath )
|
||||
{
|
||||
var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
|
||||
ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath );
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.ComponentModel;
|
|||
using System.IO;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Structs;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,22 +18,6 @@ using System.Linq;
|
|||
|
||||
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 string Name
|
||||
|
|
@ -54,6 +38,7 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
|
||||
public ResourceLoader ResourceLoader { get; }
|
||||
public ResourceLogger ResourceLogger { get; }
|
||||
|
||||
//public PathResolver PathResolver { get; }
|
||||
public SettingsInterface SettingsInterface { get; }
|
||||
|
|
@ -81,19 +66,18 @@ public class Penumbra : IDalamudPlugin
|
|||
CharacterUtility = new CharacterUtility();
|
||||
MetaDefaults = new MetaDefaults();
|
||||
ResourceLoader = new ResourceLoader( this );
|
||||
ResourceLogger = new ResourceLogger( ResourceLoader );
|
||||
PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects );
|
||||
ModManager = new ModManager();
|
||||
ModManager.DiscoverMods();
|
||||
//PathResolver = new PathResolver( ResourceLoader, gameUtils );
|
||||
PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects );
|
||||
ObjectReloader = new ObjectReloader( ModManager, Config.WaitFrames );
|
||||
//PathResolver = new PathResolver( ResourceLoader, gameUtils );
|
||||
|
||||
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
|
||||
{
|
||||
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods",
|
||||
} );
|
||||
|
||||
ResourceLoader.EnableReplacements();
|
||||
ResourceLoader.EnableLogging();
|
||||
if( Config.DebugMode )
|
||||
{
|
||||
ResourceLoader.EnableDebug();
|
||||
|
|
@ -112,7 +96,7 @@ public class Penumbra : IDalamudPlugin
|
|||
CreateWebServer();
|
||||
}
|
||||
|
||||
if( !Config.EnablePlayerWatch || !Config.IsEnabled )
|
||||
if( !Config.EnablePlayerWatch || !Config.EnableMods )
|
||||
{
|
||||
PlayerWatcher.Disable();
|
||||
}
|
||||
|
|
@ -122,16 +106,25 @@ public class Penumbra : IDalamudPlugin
|
|||
PluginLog.Debug( "Triggered Redraw of {Player}.", p.Name );
|
||||
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()
|
||||
{
|
||||
if( Config.IsEnabled )
|
||||
if( Config.EnableMods )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Config.IsEnabled = true;
|
||||
Config.EnableMods = true;
|
||||
ResourceLoader.EnableReplacements();
|
||||
ResidentResources.Reload();
|
||||
if( Config.EnablePlayerWatch )
|
||||
{
|
||||
|
|
@ -145,12 +138,13 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
public bool Disable()
|
||||
{
|
||||
if( !Config.IsEnabled )
|
||||
if( !Config.EnableMods )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Config.IsEnabled = false;
|
||||
Config.EnableMods = false;
|
||||
ResourceLoader.DisableReplacements();
|
||||
ResidentResources.Reload();
|
||||
if( Config.EnablePlayerWatch )
|
||||
{
|
||||
|
|
@ -219,8 +213,10 @@ public class Penumbra : IDalamudPlugin
|
|||
Dalamud.Commands.RemoveHandler( CommandName );
|
||||
|
||||
//PathResolver.Dispose();
|
||||
ResourceLogger.Dispose();
|
||||
ResourceLoader.Dispose();
|
||||
|
||||
|
||||
ShutdownWebServer();
|
||||
}
|
||||
|
||||
|
|
@ -322,8 +318,8 @@ public class Penumbra : IDalamudPlugin
|
|||
}
|
||||
case "toggle":
|
||||
{
|
||||
SetEnabled( !Config.IsEnabled );
|
||||
Dalamud.Chat.Print( Config.IsEnabled
|
||||
SetEnabled( !Config.EnableMods );
|
||||
Dalamud.Chat.Print( Config.EnableMods
|
||||
? modsEnabled
|
||||
: modsDisabled );
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -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." ),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
using System.Numerics;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Windows.Forms;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace Penumbra.UI.Custom
|
||||
{
|
||||
|
|
@ -20,6 +20,19 @@ namespace Penumbra.UI.Custom
|
|||
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
|
||||
|
|
|
|||
|
|
@ -21,11 +21,21 @@ public partial class SettingsInterface
|
|||
private string _filePathFilter = string.Empty;
|
||||
private string _filePathFilterLower = string.Empty;
|
||||
|
||||
private readonly float _leftTextLength =
|
||||
ImGui.CalcTextSize( "chara/human/c0000/obj/body/b0000/material/v0000/mt_c0000b0000_b.mtrl" ).X / ImGuiHelpers.GlobalScale + 40;
|
||||
private const float LeftTextLength = 600;
|
||||
|
||||
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 )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -45,13 +55,13 @@ public partial class SettingsInterface
|
|||
_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 ) )
|
||||
{
|
||||
_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 );
|
||||
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 ) )
|
||||
{
|
||||
|
|
@ -69,7 +79,7 @@ public partial class SettingsInterface
|
|||
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 ) )
|
||||
{
|
||||
|
|
@ -94,11 +104,6 @@ public partial class SettingsInterface
|
|||
void DrawFileLines( ModCollectionCache cache )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
|
|
@ -139,24 +144,27 @@ public partial class SettingsInterface
|
|||
var activeCollection = modManager.Collections.ActiveCollection.Cache;
|
||||
var forcedCollection = modManager.Collections.ForcedCollection.Cache;
|
||||
|
||||
var (activeResolved, activeSwap, activeMeta) = activeCollection != null
|
||||
? ( activeCollection.ResolvedFiles.Count, activeCollection.SwappedFiles.Count, activeCollection.MetaManipulations.Count )
|
||||
: ( 0, 0, 0 );
|
||||
var (forcedResolved, forcedSwap, forcedMeta) = forcedCollection != null
|
||||
? ( forcedCollection.ResolvedFiles.Count, forcedCollection.SwappedFiles.Count, forcedCollection.MetaManipulations.Count )
|
||||
: ( 0, 0, 0 );
|
||||
var totalLines = activeResolved + forcedResolved + activeSwap + forcedSwap + activeMeta + forcedMeta;
|
||||
var (activeResolved, activeMeta) = activeCollection != null
|
||||
? ( activeCollection.ResolvedFiles.Count, activeCollection.MetaManipulations.Count )
|
||||
: ( 0, 0 );
|
||||
var (forcedResolved, forcedMeta) = forcedCollection != null
|
||||
? ( forcedCollection.ResolvedFiles.Count, forcedCollection.MetaManipulations.Count )
|
||||
: ( 0, 0 );
|
||||
var totalLines = activeResolved + forcedResolved + activeMeta + forcedMeta;
|
||||
if( totalLines == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( ImGui.BeginTable( "##effective_changes", 2, flags, AutoFillSize ) )
|
||||
if( !ImGui.BeginTable( "##effective_changes", 2, flags, AutoFillSize ) )
|
||||
{
|
||||
raii.Push( ImGui.EndTable );
|
||||
ImGui.TableSetupColumn( "##tableGamePathCol", ImGuiTableColumnFlags.None, _leftTextLength * ImGuiHelpers.GlobalScale );
|
||||
return;
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
|
@ -180,14 +188,9 @@ public partial class SettingsInterface
|
|||
if( row < activeResolved )
|
||||
{
|
||||
var (gamePath, file) = activeCollection!.ResolvedFiles.ElementAt( row );
|
||||
DrawLine( gamePath, file.FullName );
|
||||
DrawLine( gamePath, file );
|
||||
}
|
||||
else if( ( row -= activeResolved ) < activeSwap )
|
||||
{
|
||||
var (gamePath, swap) = activeCollection!.SwappedFiles.ElementAt( row );
|
||||
DrawLine( gamePath, swap );
|
||||
}
|
||||
else if( ( row -= activeSwap ) < activeMeta )
|
||||
else if( ( row -= activeResolved ) < activeMeta )
|
||||
{
|
||||
var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
||||
DrawLine( manip.IdentifierString(), mod.Data.Meta.Name );
|
||||
|
|
@ -195,16 +198,11 @@ public partial class SettingsInterface
|
|||
else if( ( row -= activeMeta ) < forcedResolved )
|
||||
{
|
||||
var (gamePath, file) = forcedCollection!.ResolvedFiles.ElementAt( row );
|
||||
DrawLine( gamePath, file.FullName );
|
||||
}
|
||||
else if( ( row -= forcedResolved ) < forcedSwap )
|
||||
{
|
||||
var (gamePath, swap) = forcedCollection!.SwappedFiles.ElementAt( row );
|
||||
DrawLine( gamePath, swap );
|
||||
DrawLine( gamePath, file );
|
||||
}
|
||||
else
|
||||
{
|
||||
row -= forcedSwap;
|
||||
row -= forcedResolved;
|
||||
var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
||||
DrawLine( manip.IdentifierString(), mod.Data.Meta.Name );
|
||||
}
|
||||
|
|
@ -213,5 +211,4 @@ public partial class SettingsInterface
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ using Penumbra.GameData.Util;
|
|||
using Penumbra.Meta;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
using ImGui = ImGuiNET.ImGui;
|
||||
|
|
@ -56,7 +55,7 @@ public partial class SettingsInterface
|
|||
private Option? _selectedOption;
|
||||
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 SettingsInterface _base;
|
||||
|
|
@ -218,7 +217,10 @@ public partial class SettingsInterface
|
|||
indent.Push( 15f );
|
||||
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 )
|
||||
|
|
@ -258,13 +260,13 @@ public partial class SettingsInterface
|
|||
foreach( var (source, target) in Meta.FileSwaps )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiCustom.CopyOnClickSelectable( source );
|
||||
ImGuiCustom.CopyOnClickSelectable( source.Path );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltRight );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiCustom.CopyOnClickSelectable( target );
|
||||
ImGuiCustom.CopyOnClickSelectable( target.InternalName );
|
||||
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
|
@ -278,7 +280,8 @@ public partial class SettingsInterface
|
|||
}
|
||||
|
||||
_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 )
|
||||
{
|
||||
|
|
@ -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;
|
||||
var defaultIndex =
|
||||
gamePaths.IndexOf( p => ( ( string )p ).StartsWith( TextDefaultGamePath ) );
|
||||
var defaultIndex = gamePaths.IndexOf( p => p.Path.StartsWith( DefaultUtf8GamePath ) );
|
||||
if( defaultIndex < 0 )
|
||||
{
|
||||
return defaultIndex;
|
||||
}
|
||||
|
||||
string path = gamePaths[ defaultIndex ];
|
||||
var path = gamePaths[ defaultIndex ].Path;
|
||||
if( path.Length == TextDefaultGamePath.Length )
|
||||
{
|
||||
return defaultIndex;
|
||||
}
|
||||
|
||||
if( path[ TextDefaultGamePath.Length ] != '-'
|
||||
|| !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ), out removeFolders ) )
|
||||
if( path[ TextDefaultGamePath.Length ] != ( byte )'-'
|
||||
|| !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ).ToString(), out removeFolders ) )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -373,8 +375,9 @@ public partial class SettingsInterface
|
|||
|
||||
var option = ( Option )_selectedOption;
|
||||
|
||||
var gamePaths = _currentGamePaths.Split( ';' ).Select( p => new GamePath( p ) ).ToArray();
|
||||
if( gamePaths.Length == 0 || ( ( string )gamePaths[ 0 ] ).Length == 0 )
|
||||
var gamePaths = _currentGamePaths.Split( ';' )
|
||||
.Select( p => Utf8GamePath.FromString( p, out var path, false ) ? path : Utf8GamePath.Empty ).Where( p => !p.IsEmpty ).ToArray();
|
||||
if( gamePaths.Length == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -518,17 +521,17 @@ public partial class SettingsInterface
|
|||
Selectable( 0, ColorGreen );
|
||||
|
||||
using var indent = ImGuiRaii.PushIndent( indentWidth );
|
||||
var tmpPaths = gamePaths.ToArray();
|
||||
foreach( var gamePath in tmpPaths )
|
||||
foreach( var gamePath in gamePaths.ToArray() )
|
||||
{
|
||||
string tmp = gamePath;
|
||||
var tmp = gamePath.ToString();
|
||||
var old = tmp;
|
||||
if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue )
|
||||
&& tmp != gamePath )
|
||||
&& tmp != old )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Structs;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class SettingsInterface
|
||||
{
|
||||
public partial class SettingsInterface
|
||||
{
|
||||
private partial class PluginDetails
|
||||
{
|
||||
private const string LabelDescEdit = "##descedit";
|
||||
|
|
@ -24,6 +25,7 @@ namespace Penumbra.UI
|
|||
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
|
||||
private const string TextNoOptionAvailable = "[Not Available]";
|
||||
private const string TextDefaultGamePath = "default";
|
||||
private static readonly Utf8String DefaultUtf8GamePath = Utf8String.FromStringUnsafe( TextDefaultGamePath, true );
|
||||
private const char GamePathsSeparator = ';';
|
||||
|
||||
private static readonly string TooltipFilesTabEdit =
|
||||
|
|
@ -131,7 +133,7 @@ namespace Penumbra.UI
|
|||
&& newOption.Length != 0 )
|
||||
{
|
||||
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();
|
||||
if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() )
|
||||
{
|
||||
|
|
@ -212,7 +214,7 @@ namespace Penumbra.UI
|
|||
{
|
||||
OptionName = newName,
|
||||
OptionDesc = "",
|
||||
OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >(),
|
||||
OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
|
||||
} );
|
||||
_selector.SaveCurrentMod();
|
||||
}
|
||||
|
|
@ -330,24 +332,23 @@ namespace Penumbra.UI
|
|||
var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2;
|
||||
for( var idx = 0; idx < swaps.Length + 1; ++idx )
|
||||
{
|
||||
var key = idx == swaps.Length ? GamePath.GenerateUnchecked( "" ) : swaps[ idx ];
|
||||
var value = idx == swaps.Length ? GamePath.GenerateUnchecked( "" ) : Meta.FileSwaps[ key ];
|
||||
string keyString = key;
|
||||
string valueString = value;
|
||||
var key = idx == swaps.Length ? Utf8GamePath.Empty : swaps[ idx ];
|
||||
var value = idx == swaps.Length ? FullPath.Empty : Meta.FileSwaps[ key ];
|
||||
var keyString = key.ToString();
|
||||
var valueString = value.ToString();
|
||||
|
||||
ImGui.SetNextItemWidth( width );
|
||||
if( ImGui.InputTextWithHint( $"##swapLhs_{idx}", "Enter new file to be replaced...", ref keyString,
|
||||
GamePath.MaxGamePathLength, ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||
{
|
||||
var newKey = new GamePath( keyString );
|
||||
if( newKey.CompareTo( key ) != 0 )
|
||||
if( Utf8GamePath.FromString( keyString, out var newKey, true ) && newKey.CompareTo( key ) != 0 )
|
||||
{
|
||||
if( idx < swaps.Length )
|
||||
{
|
||||
Meta.FileSwaps.Remove( key );
|
||||
}
|
||||
|
||||
if( newKey != string.Empty )
|
||||
if( !newKey.IsEmpty )
|
||||
{
|
||||
Meta.FileSwaps[ newKey ] = value;
|
||||
}
|
||||
|
|
@ -371,7 +372,7 @@ namespace Penumbra.UI
|
|||
GamePath.MaxGamePathLength,
|
||||
ImGuiInputTextFlags.EnterReturnsTrue ) )
|
||||
{
|
||||
var newValue = new GamePath( valueString );
|
||||
var newValue = new FullPath( valueString.ToLowerInvariant() );
|
||||
if( newValue.CompareTo( value ) != 0 )
|
||||
{
|
||||
Meta.FileSwaps[ key ] = newValue;
|
||||
|
|
@ -382,5 +383,4 @@ namespace Penumbra.UI
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -488,7 +488,7 @@ public partial class SettingsInterface
|
|||
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
|
||||
if( ModPanel.DrawSortOrder( mod.Data, _modManager, this ) )
|
||||
if( ModPanel.DrawSortOrder( mod.Data, Penumbra.ModManager, this ) )
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
|
@ -509,7 +509,7 @@ public partial class SettingsInterface
|
|||
{
|
||||
var change = 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++ );
|
||||
if( mod != null )
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Interop;
|
||||
|
|
@ -131,7 +133,7 @@ public partial class SettingsInterface
|
|||
|
||||
private void DrawEnabledBox()
|
||||
{
|
||||
var enabled = _config.IsEnabled;
|
||||
var enabled = _config.EnableMods;
|
||||
if( ImGui.Checkbox( "Enable Mods", ref enabled ) )
|
||||
{
|
||||
_base._penumbra.SetEnabled( enabled );
|
||||
|
|
@ -317,14 +319,84 @@ public partial class SettingsInterface
|
|||
+ "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()
|
||||
{
|
||||
DrawTempFolder();
|
||||
DrawRequestedResourceLogging();
|
||||
DrawDisableSoundStreamingBox();
|
||||
DrawLogLoadedFilesBox();
|
||||
DrawDisableNotificationsBox();
|
||||
DrawEnableHttpApiBox();
|
||||
DrawReloadResourceButton();
|
||||
DrawEnableDebugModeBox();
|
||||
DrawEnableFullResourceLoggingBox();
|
||||
}
|
||||
|
||||
public static unsafe void Text( Utf8String s )
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ using Lumina.Excel.GeneratedSheets;
|
|||
using Penumbra.GameData.Enums;
|
||||
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 )
|
||||
{
|
||||
var ret = ImGui.Selectable( name ) ? MouseButton.Left : MouseButton.None;
|
||||
ret = ImGui.IsItemClicked( ImGuiMouseButton.Right ) ? MouseButton.Right : ret;
|
||||
|
|
@ -36,5 +36,4 @@ namespace Penumbra.UI
|
|||
ImGui.TextColored( new Vector4( 0.5f, 0.5f, 0.5f, 1 ), modelId );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +1,10 @@
|
|||
using System;
|
||||
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 )
|
||||
{
|
||||
for( var i = 0; i < array.Length; ++i )
|
||||
|
|
@ -39,49 +18,16 @@ namespace Penumbra.Util
|
|||
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 )
|
||||
{
|
||||
for( var i = 0; i < array.Count; ++i )
|
||||
{
|
||||
if( predicate.Invoke( array[ i ] ) )
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Penumbra.Util
|
||||
namespace Penumbra.Util;
|
||||
|
||||
public static class BinaryReaderExtensions
|
||||
{
|
||||
public static class BinaryReaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a structure from the current stream position.
|
||||
/// </summary>
|
||||
|
|
@ -47,60 +46,6 @@ namespace Penumbra.Util
|
|||
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>
|
||||
/// Seeks this BinaryReader's position to the given offset. Syntactic sugar.
|
||||
/// </summary>
|
||||
|
|
@ -108,30 +53,4 @@ namespace Penumbra.Util
|
|||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,12 @@ using System.Collections.Generic;
|
|||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Penumbra.Util
|
||||
namespace Penumbra.Util;
|
||||
|
||||
public static class ChatUtil
|
||||
{
|
||||
public static class ChatUtil
|
||||
{
|
||||
public static void LinkItem( Item item )
|
||||
{
|
||||
var payloadList = new List< Payload >
|
||||
|
|
@ -33,5 +32,4 @@ namespace Penumbra.Util
|
|||
Message = payload,
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
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 )
|
||||
{
|
||||
using var process = Process.GetCurrentProcess();
|
||||
|
|
@ -78,5 +78,4 @@ namespace Penumbra.Util
|
|||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ public static class ModelChanger
|
|||
public const string MaterialFormat = "/mt_c0201b0001_{0}.mtrl";
|
||||
public static readonly Regex MaterialRegex = new(@"/mt_c0201b0001_.*?\.mtrl", RegexOptions.Compiled);
|
||||
|
||||
|
||||
public static bool ValidStrings( string from, string to )
|
||||
=> from.Length != 0
|
||||
&& to.Length != 0
|
||||
|
|
@ -53,9 +52,9 @@ public static class ModelChanger
|
|||
var replaced = 0;
|
||||
for( var i = 0; i < mdlFile.Materials.Length; ++i )
|
||||
{
|
||||
if( compare(mdlFile.Materials[i]) )
|
||||
if( compare( mdlFile.Materials[ i ] ) )
|
||||
{
|
||||
mdlFile.Materials[i] = to;
|
||||
mdlFile.Materials[ i ] = to;
|
||||
++replaced;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Lumina;
|
||||
using Lumina.Data.Structs;
|
||||
|
||||
namespace Penumbra.Util
|
||||
namespace Penumbra.Util;
|
||||
|
||||
public class PenumbraSqPackStream : IDisposable
|
||||
{
|
||||
public class PenumbraSqPackStream : IDisposable
|
||||
{
|
||||
public Stream BaseStream { get; protected set; }
|
||||
|
||||
protected BinaryReader Reader { get; set; }
|
||||
|
|
@ -351,7 +349,7 @@ namespace Penumbra.Util
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
Reader?.Dispose();
|
||||
Reader.Dispose();
|
||||
}
|
||||
|
||||
public class PenumbraFileInfo
|
||||
|
|
@ -375,15 +373,10 @@ namespace Penumbra.Util
|
|||
|
||||
public byte[] Data { get; internal set; } = new byte[0];
|
||||
|
||||
public Span< byte > DataSpan
|
||||
=> Data.AsSpan();
|
||||
|
||||
public MemoryStream? FileStream { get; internal set; }
|
||||
|
||||
public BinaryReader? Reader { get; internal set; }
|
||||
|
||||
public ParsedFilePath? FilePath { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called once the files are read out from the dats. Used to further parse the file into usable data structures.
|
||||
/// </summary>
|
||||
|
|
@ -391,25 +384,6 @@ namespace Penumbra.Util
|
|||
{
|
||||
// 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 )]
|
||||
|
|
@ -430,5 +404,4 @@ namespace Penumbra.Util
|
|||
public uint BlockOffset;
|
||||
public uint BlockCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
|||
using Newtonsoft.Json;
|
||||
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 )
|
||||
=> objectType == typeof( HashSet< T > );
|
||||
|
||||
|
|
@ -42,5 +42,4 @@ namespace Penumbra.Util
|
|||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,15 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
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());
|
||||
|
||||
public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" )
|
||||
{
|
||||
StringBuilder sb = new( s.Length );
|
||||
StringBuilder sb = new(s.Length);
|
||||
foreach( var c in s )
|
||||
{
|
||||
if( Invalid.Contains( c ) )
|
||||
|
|
@ -31,7 +31,7 @@ namespace Penumbra.Util
|
|||
|
||||
public static string ReplaceNonAsciiSymbols( this string s, string replacement = "_" )
|
||||
{
|
||||
StringBuilder sb = new( s.Length );
|
||||
StringBuilder sb = new(s.Length);
|
||||
foreach( var c in s )
|
||||
{
|
||||
if( c >= 128 )
|
||||
|
|
@ -49,7 +49,7 @@ namespace Penumbra.Util
|
|||
|
||||
public static string ReplaceBadXivSymbols( this string s, string replacement = "_" )
|
||||
{
|
||||
StringBuilder sb = new( s.Length );
|
||||
StringBuilder sb = new(s.Length);
|
||||
foreach( var c in s )
|
||||
{
|
||||
if( c >= 128 || Invalid.Contains( c ) )
|
||||
|
|
@ -64,5 +64,4 @@ namespace Penumbra.Util
|
|||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
using System.IO;
|
||||
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 = "" )
|
||||
{
|
||||
const uint maxTries = 15;
|
||||
|
|
@ -30,5 +30,4 @@ namespace Penumbra.Util
|
|||
fileName.Refresh();
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue