Move Mod.Manager and ModCollection.Manager to outer scope and required changes.

This commit is contained in:
Ottermandias 2023-03-27 15:22:39 +02:00
parent ccdafcf85d
commit 1253079968
59 changed files with 2562 additions and 2615 deletions

View file

@ -15,103 +15,105 @@ namespace Penumbra.Mods;
public partial class Mod
{
// Groups that allow all available options to be selected at once.
private sealed class MultiModGroup : IModGroup
}
/// <summary> Groups that allow all available options to be selected at once. </summary>
public sealed class MultiModGroup : IModGroup
{
public GroupType Type
=> GroupType.Multi;
public string Name { get; set; } = "Group";
public string Description { get; set; } = "A non-exclusive group of settings.";
public int Priority { get; set; }
public uint DefaultSettings { get; set; }
public int OptionPriority(Index idx)
=> PrioritizedOptions[idx].Priority;
public ISubMod this[Index idx]
=> PrioritizedOptions[idx].Mod;
[JsonIgnore]
public int Count
=> PrioritizedOptions.Count;
public readonly List<(SubMod Mod, int Priority)> PrioritizedOptions = new();
public IEnumerator<ISubMod> GetEnumerator()
=> PrioritizedOptions.Select(o => o.Mod).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx)
{
public GroupType Type
=> GroupType.Multi;
public string Name { get; set; } = "Group";
public string Description { get; set; } = "A non-exclusive group of settings.";
public int Priority { get; set; }
public uint DefaultSettings { get; set; }
public int OptionPriority(Index idx)
=> PrioritizedOptions[idx].Priority;
public ISubMod this[Index idx]
=> PrioritizedOptions[idx].Mod;
[JsonIgnore]
public int Count
=> PrioritizedOptions.Count;
public readonly List<(SubMod Mod, int Priority)> PrioritizedOptions = new();
public IEnumerator<ISubMod> GetEnumerator()
=> PrioritizedOptions.Select(o => o.Mod).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx)
var ret = new MultiModGroup()
{
var ret = new MultiModGroup()
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
Priority = json[nameof(Priority)]?.ToObject<int>() ?? 0,
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<uint>() ?? 0,
};
if (ret.Name.Length == 0)
return null;
var options = json["Options"];
if (options != null)
foreach (var child in options.Children())
{
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
Priority = json[nameof(Priority)]?.ToObject<int>() ?? 0,
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<uint>() ?? 0,
};
if (ret.Name.Length == 0)
return null;
var options = json["Options"];
if (options != null)
foreach (var child in options.Children())
if (ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions)
{
if (ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions)
{
Penumbra.ChatService.NotificationMessage(
$"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", "Warning",
NotificationType.Warning);
break;
}
var subMod = new SubMod(mod);
subMod.SetPosition(groupIdx, ret.PrioritizedOptions.Count);
subMod.Load(mod.ModPath, child, out var priority);
ret.PrioritizedOptions.Add((subMod, priority));
Penumbra.ChatService.NotificationMessage(
$"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", "Warning",
NotificationType.Warning);
break;
}
ret.DefaultSettings = (uint)(ret.DefaultSettings & ((1ul << ret.Count) - 1));
return ret;
}
public IModGroup Convert(GroupType type)
{
switch (type)
{
case GroupType.Multi: return this;
case GroupType.Single:
var multi = new SingleModGroup()
{
Name = Name,
Description = Description,
Priority = Priority,
DefaultSettings = (uint)Math.Max(Math.Min(Count - 1, BitOperations.TrailingZeroCount(DefaultSettings)), 0),
};
multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod));
return multi;
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
var subMod = new SubMod(mod);
subMod.SetPosition(groupIdx, ret.PrioritizedOptions.Count);
subMod.Load(mod.ModPath, child, out var priority);
ret.PrioritizedOptions.Add((subMod, priority));
}
}
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
ret.DefaultSettings = (uint)(ret.DefaultSettings & ((1ul << ret.Count) - 1));
return ret;
}
public IModGroup Convert(GroupType type)
{
switch (type)
{
if (!PrioritizedOptions.Move(optionIdxFrom, optionIdxTo))
return false;
DefaultSettings = Functions.MoveBit(DefaultSettings, optionIdxFrom, optionIdxTo);
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
return true;
}
public void UpdatePositions(int from = 0)
{
foreach (var ((o, _), i) in PrioritizedOptions.WithIndex().Skip(from))
o.SetPosition(o.GroupIdx, i);
case GroupType.Multi: return this;
case GroupType.Single:
var multi = new SingleModGroup()
{
Name = Name,
Description = Description,
Priority = Priority,
DefaultSettings = (uint)Math.Max(Math.Min(Count - 1, BitOperations.TrailingZeroCount(DefaultSettings)), 0),
};
multi.OptionData.AddRange(PrioritizedOptions.Select(p => p.Mod));
return multi;
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
public bool MoveOption(int optionIdxFrom, int optionIdxTo)
{
if (!PrioritizedOptions.Move(optionIdxFrom, optionIdxTo))
return false;
DefaultSettings = Functions.MoveBit(DefaultSettings, optionIdxFrom, optionIdxTo);
UpdatePositions(Math.Min(optionIdxFrom, optionIdxTo));
return true;
}
public void UpdatePositions(int from = 0)
{
foreach (var ((o, _), i) in PrioritizedOptions.WithIndex().Skip(from))
o.SetPosition(o.GroupIdx, i);
}
}

View file

@ -10,122 +10,119 @@ using Penumbra.Api.Enums;
namespace Penumbra.Mods;
public partial class Mod
/// <summary> Groups that allow only one of their available options to be selected. </summary>
public sealed class SingleModGroup : IModGroup
{
// Groups that allow only one of their available options to be selected.
private sealed class SingleModGroup : IModGroup
public GroupType Type
=> GroupType.Single;
public string Name { get; set; } = "Option";
public string Description { get; set; } = "A mutually exclusive group of settings.";
public int Priority { get; set; }
public uint DefaultSettings { get; set; }
public readonly List< SubMod > OptionData = new();
public int OptionPriority( Index _ )
=> Priority;
public ISubMod this[ Index idx ]
=> OptionData[ idx ];
[JsonIgnore]
public int Count
=> OptionData.Count;
public IEnumerator< ISubMod > GetEnumerator()
=> OptionData.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public static SingleModGroup? Load( Mod mod, JObject json, int groupIdx )
{
public GroupType Type
=> GroupType.Single;
public string Name { get; set; } = "Option";
public string Description { get; set; } = "A mutually exclusive group of settings.";
public int Priority { get; set; }
public uint DefaultSettings { get; set; }
public readonly List< SubMod > OptionData = new();
public int OptionPriority( Index _ )
=> Priority;
public ISubMod this[ Index idx ]
=> OptionData[ idx ];
[JsonIgnore]
public int Count
=> OptionData.Count;
public IEnumerator< ISubMod > GetEnumerator()
=> OptionData.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public static SingleModGroup? Load( Mod mod, JObject json, int groupIdx )
var options = json[ "Options" ];
var ret = new SingleModGroup
{
var options = json[ "Options" ];
var ret = new SingleModGroup
{
Name = json[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty,
Description = json[ nameof( Description ) ]?.ToObject< string >() ?? string.Empty,
Priority = json[ nameof( Priority ) ]?.ToObject< int >() ?? 0,
DefaultSettings = json[ nameof( DefaultSettings ) ]?.ToObject< uint >() ?? 0u,
};
if( ret.Name.Length == 0 )
{
return null;
}
Name = json[ nameof( Name ) ]?.ToObject< string >() ?? string.Empty,
Description = json[ nameof( Description ) ]?.ToObject< string >() ?? string.Empty,
Priority = json[ nameof( Priority ) ]?.ToObject< int >() ?? 0,
DefaultSettings = json[ nameof( DefaultSettings ) ]?.ToObject< uint >() ?? 0u,
};
if( ret.Name.Length == 0 )
{
return null;
}
if( options != null )
if( options != null )
{
foreach( var child in options.Children() )
{
foreach( var child in options.Children() )
var subMod = new SubMod( mod );
subMod.SetPosition( groupIdx, ret.OptionData.Count );
subMod.Load( mod.ModPath, child, out _ );
ret.OptionData.Add( subMod );
}
}
if( ( int )ret.DefaultSettings >= ret.Count )
ret.DefaultSettings = 0;
return ret;
}
public IModGroup Convert( GroupType type )
{
switch( type )
{
case GroupType.Single: return this;
case GroupType.Multi:
var multi = new MultiModGroup()
{
var subMod = new SubMod( mod );
subMod.SetPosition( groupIdx, ret.OptionData.Count );
subMod.Load( mod.ModPath, child, out _ );
ret.OptionData.Add( subMod );
}
}
Name = Name,
Description = Description,
Priority = Priority,
DefaultSettings = 1u << ( int )DefaultSettings,
};
multi.PrioritizedOptions.AddRange( OptionData.Select( ( o, i ) => ( o, i ) ) );
return multi;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
}
}
if( ( int )ret.DefaultSettings >= ret.Count )
ret.DefaultSettings = 0;
return ret;
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
{
if( !OptionData.Move( optionIdxFrom, optionIdxTo ) )
{
return false;
}
public IModGroup Convert( GroupType type )
// Update default settings with the move.
if( DefaultSettings == optionIdxFrom )
{
switch( type )
DefaultSettings = ( uint )optionIdxTo;
}
else if( optionIdxFrom < optionIdxTo )
{
if( DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo )
{
case GroupType.Single: return this;
case GroupType.Multi:
var multi = new MultiModGroup()
{
Name = Name,
Description = Description,
Priority = Priority,
DefaultSettings = 1u << ( int )DefaultSettings,
};
multi.PrioritizedOptions.AddRange( OptionData.Select( ( o, i ) => ( o, i ) ) );
return multi;
default: throw new ArgumentOutOfRangeException( nameof( type ), type, null );
--DefaultSettings;
}
}
public bool MoveOption( int optionIdxFrom, int optionIdxTo )
else if( DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo )
{
if( !OptionData.Move( optionIdxFrom, optionIdxTo ) )
{
return false;
}
// Update default settings with the move.
if( DefaultSettings == optionIdxFrom )
{
DefaultSettings = ( uint )optionIdxTo;
}
else if( optionIdxFrom < optionIdxTo )
{
if( DefaultSettings > optionIdxFrom && DefaultSettings <= optionIdxTo )
{
--DefaultSettings;
}
}
else if( DefaultSettings < optionIdxFrom && DefaultSettings >= optionIdxTo )
{
++DefaultSettings;
}
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
return true;
++DefaultSettings;
}
public void UpdatePositions( int from = 0 )
UpdatePositions( Math.Min( optionIdxFrom, optionIdxTo ) );
return true;
}
public void UpdatePositions( int from = 0 )
{
foreach( var (o, i) in OptionData.WithIndex().Skip( from ) )
{
foreach( var (o, i) in OptionData.WithIndex().Skip( from ) )
{
o.SetPosition( o.GroupIdx, i );
}
o.SetPosition( o.GroupIdx, i );
}
}
}

View file

@ -36,7 +36,7 @@ public partial class Mod
ISubMod.WriteSubMod( j, serializer, _default, ModPath, 0 );
}
private void SaveDefaultModDelayed()
internal void SaveDefaultModDelayed()
=> Penumbra.Framework.RegisterDelayed( nameof( SaveDefaultMod ) + ModPath.Name, SaveDefaultMod );
private void LoadDefaultOption()
@ -92,233 +92,237 @@ public partial class Mod
}
// A sub mod is a collection of
// - file replacements
// - file swaps
// - meta manipulations
// that can be used either as an option or as the default data for a mod.
// It can be loaded and reloaded from Json.
// Nothing is checked for existence or validity when loading.
// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides.
public sealed class SubMod : ISubMod
}
/// <summary>
/// A sub mod is a collection of
/// - file replacements
/// - file swaps
/// - meta manipulations
/// that can be used either as an option or as the default data for a mod.
/// It can be loaded and reloaded from Json.
/// Nothing is checked for existence or validity when loading.
/// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides.
/// </summary>
public sealed class SubMod : ISubMod
{
public string Name { get; set; } = "Default";
public string FullName
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[ GroupIdx ].Name}: {Name}";
public string Description { get; set; } = string.Empty;
internal IMod ParentMod { get; private init; }
internal int GroupIdx { get; private set; }
internal int OptionIdx { get; private set; }
public bool IsDefault
=> GroupIdx < 0;
public Dictionary< Utf8GamePath, FullPath > FileData = new();
public Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
public HashSet< MetaManipulation > ManipulationData = new();
public SubMod( IMod parentMod )
=> ParentMod = parentMod;
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
=> FileData;
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps
=> FileSwapData;
public IReadOnlySet< MetaManipulation > Manipulations
=> ManipulationData;
public void SetPosition( int groupIdx, int optionIdx )
{
public string Name { get; set; } = "Default";
GroupIdx = groupIdx;
OptionIdx = optionIdx;
}
public string FullName
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[ GroupIdx ].Name}: {Name}";
public void Load( DirectoryInfo basePath, JToken json, out int priority )
{
FileData.Clear();
FileSwapData.Clear();
ManipulationData.Clear();
public string Description { get; set; } = string.Empty;
// Every option has a name, but priorities are only relevant for multi group options.
Name = json[ nameof( ISubMod.Name ) ]?.ToObject< string >() ?? string.Empty;
Description = json[ nameof( ISubMod.Description ) ]?.ToObject< string >() ?? string.Empty;
priority = json[ nameof( IModGroup.Priority ) ]?.ToObject< int >() ?? 0;
internal IMod ParentMod { get; private init; }
internal int GroupIdx { get; private set; }
internal int OptionIdx { get; private set; }
public bool IsDefault
=> GroupIdx < 0;
public Dictionary< Utf8GamePath, FullPath > FileData = new();
public Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
public HashSet< MetaManipulation > ManipulationData = new();
public SubMod( IMod parentMod )
=> ParentMod = parentMod;
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
=> FileData;
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps
=> FileSwapData;
public IReadOnlySet< MetaManipulation > Manipulations
=> ManipulationData;
public void SetPosition( int groupIdx, int optionIdx )
var files = ( JObject? )json[ nameof( Files ) ];
if( files != null )
{
GroupIdx = groupIdx;
OptionIdx = optionIdx;
}
public void Load( DirectoryInfo basePath, JToken json, out int priority )
{
FileData.Clear();
FileSwapData.Clear();
ManipulationData.Clear();
// Every option has a name, but priorities are only relevant for multi group options.
Name = json[ nameof( ISubMod.Name ) ]?.ToObject< string >() ?? string.Empty;
Description = json[ nameof( ISubMod.Description ) ]?.ToObject< string >() ?? string.Empty;
priority = json[ nameof( IModGroup.Priority ) ]?.ToObject< int >() ?? 0;
var files = ( JObject? )json[ nameof( Files ) ];
if( files != null )
foreach( var property in files.Properties() )
{
foreach( var property in files.Properties() )
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
{
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
{
FileData.TryAdd( p, new FullPath( basePath, property.Value.ToObject< Utf8RelPath >() ) );
}
}
}
var swaps = ( JObject? )json[ nameof( FileSwaps ) ];
if( swaps != null )
{
foreach( var property in swaps.Properties() )
{
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
{
FileSwapData.TryAdd( p, new FullPath( property.Value.ToObject< string >()! ) );
}
}
}
var manips = json[ nameof( Manipulations ) ];
if( manips != null )
{
foreach( var s in manips.Children().Select( c => c.ToObject< MetaManipulation >() ).Where( m => m.ManipulationType != MetaManipulation.Type.Unknown ) )
{
ManipulationData.Add( s );
FileData.TryAdd( p, new FullPath( basePath, property.Value.ToObject< Utf8RelPath >() ) );
}
}
}
// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
// If delete is true, the files are deleted afterwards.
public (bool Changes, List< string > DeleteList) IncorporateMetaChanges( DirectoryInfo basePath, bool delete )
var swaps = ( JObject? )json[ nameof( FileSwaps ) ];
if( swaps != null )
{
var deleteList = new List< string >();
var oldSize = ManipulationData.Count;
var deleteString = delete ? "with deletion." : "without deletion.";
foreach( var (key, file) in Files.ToList() )
foreach( var property in swaps.Properties() )
{
var ext1 = key.Extension().AsciiToLower().ToString();
var ext2 = file.Extension.ToLowerInvariant();
try
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
{
if( ext1 == ".meta" || ext2 == ".meta" )
{
FileData.Remove( key );
if( !file.Exists )
{
continue;
}
var meta = new TexToolsMeta( Penumbra.GamePathParser, File.ReadAllBytes( file.FullName ), Penumbra.Config.KeepDefaultMetaChanges );
Penumbra.Log.Verbose( $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}" );
deleteList.Add( file.FullName );
ManipulationData.UnionWith( meta.MetaManipulations );
}
else if( ext1 == ".rgsp" || ext2 == ".rgsp" )
{
FileData.Remove( key );
if( !file.Exists )
{
continue;
}
var rgsp = TexToolsMeta.FromRgspFile( file.FullName, File.ReadAllBytes( file.FullName ), Penumbra.Config.KeepDefaultMetaChanges );
Penumbra.Log.Verbose( $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}" );
deleteList.Add( file.FullName );
ManipulationData.UnionWith( rgsp.MetaManipulations );
}
}
catch( Exception e )
{
Penumbra.Log.Error( $"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}" );
}
}
DeleteDeleteList( deleteList, delete );
return ( oldSize < ManipulationData.Count, deleteList );
}
internal static void DeleteDeleteList( IEnumerable< string > deleteList, bool delete )
{
if( !delete )
{
return;
}
foreach( var file in deleteList )
{
try
{
File.Delete( file );
}
catch( Exception e )
{
Penumbra.Log.Error( $"Could not delete incorporated meta file {file}:\n{e}" );
FileSwapData.TryAdd( p, new FullPath( property.Value.ToObject< string >()! ) );
}
}
}
public void WriteTexToolsMeta( DirectoryInfo basePath, bool test = false )
var manips = json[ nameof( Manipulations ) ];
if( manips != null )
{
var files = TexToolsMeta.ConvertToTexTools( Manipulations );
foreach( var (file, data) in files )
foreach( var s in manips.Children().Select( c => c.ToObject< MetaManipulation >() ).Where( m => m.ManipulationType != MetaManipulation.Type.Unknown ) )
{
var path = Path.Combine( basePath.FullName, file );
try
{
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
File.WriteAllBytes( path, data );
}
catch( Exception e )
{
Penumbra.Log.Error( $"Could not write meta file {path}:\n{e}" );
}
}
if( test )
{
TestMetaWriting( files );
}
}
[Conditional( "DEBUG" )]
private void TestMetaWriting( Dictionary< string, byte[] > files )
{
var meta = new HashSet< MetaManipulation >( Manipulations.Count );
foreach( var (file, data) in files )
{
try
{
var x = file.EndsWith( "rgsp" )
? TexToolsMeta.FromRgspFile( file, data, Penumbra.Config.KeepDefaultMetaChanges )
: new TexToolsMeta( Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges );
meta.UnionWith( x.MetaManipulations );
}
catch
{
// ignored
}
}
if( !Manipulations.SetEquals( meta ) )
{
Penumbra.Log.Information( "Meta Sets do not equal." );
foreach( var (m1, m2) in Manipulations.Zip( meta ) )
{
Penumbra.Log.Information( $"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}" );
}
foreach( var m in Manipulations.Skip( meta.Count ) )
{
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
}
foreach( var m in meta.Skip( Manipulations.Count ) )
{
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
}
}
else
{
Penumbra.Log.Information( "Meta Sets are equal." );
ManipulationData.Add( s );
}
}
}
// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
// If delete is true, the files are deleted afterwards.
public (bool Changes, List< string > DeleteList) IncorporateMetaChanges( DirectoryInfo basePath, bool delete )
{
var deleteList = new List< string >();
var oldSize = ManipulationData.Count;
var deleteString = delete ? "with deletion." : "without deletion.";
foreach( var (key, file) in Files.ToList() )
{
var ext1 = key.Extension().AsciiToLower().ToString();
var ext2 = file.Extension.ToLowerInvariant();
try
{
if( ext1 == ".meta" || ext2 == ".meta" )
{
FileData.Remove( key );
if( !file.Exists )
{
continue;
}
var meta = new TexToolsMeta( Penumbra.GamePathParser, File.ReadAllBytes( file.FullName ), Penumbra.Config.KeepDefaultMetaChanges );
Penumbra.Log.Verbose( $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}" );
deleteList.Add( file.FullName );
ManipulationData.UnionWith( meta.MetaManipulations );
}
else if( ext1 == ".rgsp" || ext2 == ".rgsp" )
{
FileData.Remove( key );
if( !file.Exists )
{
continue;
}
var rgsp = TexToolsMeta.FromRgspFile( file.FullName, File.ReadAllBytes( file.FullName ), Penumbra.Config.KeepDefaultMetaChanges );
Penumbra.Log.Verbose( $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}" );
deleteList.Add( file.FullName );
ManipulationData.UnionWith( rgsp.MetaManipulations );
}
}
catch( Exception e )
{
Penumbra.Log.Error( $"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}" );
}
}
DeleteDeleteList( deleteList, delete );
return ( oldSize < ManipulationData.Count, deleteList );
}
internal static void DeleteDeleteList( IEnumerable< string > deleteList, bool delete )
{
if( !delete )
{
return;
}
foreach( var file in deleteList )
{
try
{
File.Delete( file );
}
catch( Exception e )
{
Penumbra.Log.Error( $"Could not delete incorporated meta file {file}:\n{e}" );
}
}
}
public void WriteTexToolsMeta( DirectoryInfo basePath, bool test = false )
{
var files = TexToolsMeta.ConvertToTexTools( Manipulations );
foreach( var (file, data) in files )
{
var path = Path.Combine( basePath.FullName, file );
try
{
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
File.WriteAllBytes( path, data );
}
catch( Exception e )
{
Penumbra.Log.Error( $"Could not write meta file {path}:\n{e}" );
}
}
if( test )
{
TestMetaWriting( files );
}
}
[Conditional("DEBUG" )]
private void TestMetaWriting( Dictionary< string, byte[] > files )
{
var meta = new HashSet< MetaManipulation >( Manipulations.Count );
foreach( var (file, data) in files )
{
try
{
var x = file.EndsWith( "rgsp" )
? TexToolsMeta.FromRgspFile( file, data, Penumbra.Config.KeepDefaultMetaChanges )
: new TexToolsMeta( Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges );
meta.UnionWith( x.MetaManipulations );
}
catch
{
// ignored
}
}
if( !Manipulations.SetEquals( meta ) )
{
Penumbra.Log.Information( "Meta Sets do not equal." );
foreach( var (m1, m2) in Manipulations.Zip( meta ) )
{
Penumbra.Log.Information( $"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}" );
}
foreach( var m in Manipulations.Skip( meta.Count ) )
{
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
}
foreach( var m in meta.Skip( Manipulations.Count ) )
{
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
}
}
else
{
Penumbra.Log.Information( "Meta Sets are equal." );
}
}
}

View file

@ -10,7 +10,7 @@ using Penumbra.String.Classes;
namespace Penumbra.Mods;
// Contains the settings for a given mod.
/// <summary> Contains the settings for a given mod. </summary>
public class ModSettings
{
public static readonly ModSettings Empty = new();