Add an option to reduplicate and normalize a mod.

This commit is contained in:
Ottermandias 2022-11-24 21:54:02 +01:00
parent eedd3e2dac
commit 7033b65d33
5 changed files with 290 additions and 708 deletions

View file

@ -1,70 +0,0 @@
namespace Penumbra.Mods;
public partial class Mod
{
public partial class Editor
{
public void Normalize()
{}
public void AutoGenerateGroups()
{
//ClearEmptySubDirectories( _mod.BasePath );
//for( var i = _mod.Groups.Count - 1; i >= 0; --i )
//{
// if (_mod.Groups.)
// Penumbra.ModManager.DeleteModGroup( _mod, i );
//}
//Penumbra.ModManager.OptionSetFiles( _mod, -1, 0, new Dictionary< Utf8GamePath, FullPath >() );
//
//foreach( var groupDir in _mod.BasePath.EnumerateDirectories() )
//{
// var groupName = groupDir.Name;
// foreach( var optionDir in groupDir.EnumerateDirectories() )
// { }
//}
//var group = new OptionGroup
// {
// GroupName = groupDir.Name,
// SelectionType = SelectType.Single,
// Options = new List< Option >(),
// };
//
// foreach( var optionDir in groupDir.EnumerateDirectories() )
// {
// var option = new Option
// {
// OptionDesc = string.Empty,
// OptionName = optionDir.Name,
// OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
// };
// foreach( var file in optionDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
// {
// 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.Count > 0 )
// {
// group.Options.Add( option );
// }
// }
//
// if( group.Options.Count > 0 )
// {
// meta.Groups.Add( groupDir.Name, group );
// }
//}
//
//var idx = Penumbra.ModManager.Mods.IndexOf( m => m.Meta == meta );
//foreach( var collection in Penumbra.CollectionManager )
//{
// collection.Settings[ idx ]?.FixInvalidSettings( meta );
//}
}
}
}

View file

@ -0,0 +1,262 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods;
public partial class Mod
{
public void Normalize( Manager manager )
=> ModNormalizer.Normalize( manager, this );
private struct ModNormalizer
{
private readonly Mod _mod;
private readonly string _normalizationDirName;
private readonly string _oldDirName;
private Dictionary< Utf8GamePath, FullPath >[][]? _redirections = null;
private ModNormalizer( Mod mod )
{
_mod = mod;
_normalizationDirName = Path.Combine( _mod.ModPath.FullName, "TmpNormalization" );
_oldDirName = Path.Combine( _mod.ModPath.FullName, "TmpNormalizationOld" );
}
public static void Normalize( Manager manager, Mod mod )
{
var normalizer = new ModNormalizer( mod );
try
{
Penumbra.Log.Debug( $"[Normalization] Starting Normalization of {mod.ModPath.Name}..." );
if( !normalizer.CheckDirectories() )
{
return;
}
Penumbra.Log.Debug( "[Normalization] Copying files to temporary directory structure..." );
if( !normalizer.CopyNewFiles() )
{
return;
}
Penumbra.Log.Debug( "[Normalization] Moving old files out of the way..." );
if( !normalizer.MoveOldFiles() )
{
return;
}
Penumbra.Log.Debug( "[Normalization] Moving new directory structure in place..." );
if( !normalizer.MoveNewFiles() )
{
return;
}
Penumbra.Log.Debug( "[Normalization] Applying new redirections..." );
normalizer.ApplyRedirections( manager );
}
catch( Exception e )
{
ChatUtil.NotificationMessage( $"Could not normalize mod:\n{e}", "Failure", NotificationType.Error );
}
finally
{
Penumbra.Log.Debug( "[Normalization] Cleaning up remaining directories..." );
normalizer.Cleanup();
}
}
private bool CheckDirectories()
{
if( Directory.Exists( _normalizationDirName ) )
{
ChatUtil.NotificationMessage( "Could not normalize mod:\n"
+ "The directory TmpNormalization may not already exist when normalizing a mod.", "Failure",
NotificationType.Error );
return false;
}
if( Directory.Exists( _oldDirName ) )
{
ChatUtil.NotificationMessage( "Could not normalize mod:\n"
+ "The directory TmpNormalizationOld may not already exist when normalizing a mod.", "Failure",
NotificationType.Error );
return false;
}
return true;
}
private void Cleanup()
{
if( Directory.Exists( _normalizationDirName ) )
{
try
{
Directory.Delete( _normalizationDirName, true );
}
catch
{
// ignored
}
}
if( Directory.Exists( _oldDirName ) )
{
try
{
foreach( var dir in new DirectoryInfo( _oldDirName ).EnumerateDirectories() )
{
dir.MoveTo( Path.Combine( _mod.ModPath.FullName, dir.Name ) );
}
Directory.Delete( _oldDirName, true );
}
catch
{
// ignored
}
}
}
private bool CopyNewFiles()
{
// We copy all files to a temporary folder to ensure that we can revert the operation on failure.
try
{
var directory = Directory.CreateDirectory( _normalizationDirName );
_redirections = new Dictionary< Utf8GamePath, FullPath >[_mod.Groups.Count + 1][];
_redirections[ 0 ] = new Dictionary< Utf8GamePath, FullPath >[] { new(_mod.Default.Files.Count) };
// Normalize the default option.
var newDict = new Dictionary< Utf8GamePath, FullPath >( _mod.Default.Files.Count );
_redirections[ 0 ][ 0 ] = newDict;
foreach( var (gamePath, fullPath) in _mod._default.FileData )
{
var relPath = new Utf8RelPath( gamePath ).ToString();
var newFullPath = Path.Combine( directory.FullName, relPath );
var redirectPath = new FullPath( Path.Combine( _mod.ModPath.FullName, relPath ) );
Directory.CreateDirectory( Path.GetDirectoryName( newFullPath )! );
File.Copy( fullPath.FullName, newFullPath, true );
newDict.Add( gamePath, redirectPath );
}
// Normalize all other options.
foreach( var (group, groupIdx) in _mod.Groups.WithIndex() )
{
_redirections[ groupIdx + 1 ] = new Dictionary< Utf8GamePath, FullPath >[group.Count];
var groupDir = CreateModFolder( directory, group.Name );
foreach( var option in group.OfType< SubMod >() )
{
var optionDir = CreateModFolder( groupDir, option.Name );
newDict = new Dictionary< Utf8GamePath, FullPath >( option.FileData.Count );
_redirections[ groupIdx + 1 ][ option.OptionIdx ] = newDict;
foreach( var (gamePath, fullPath) in option.FileData )
{
var relPath = new Utf8RelPath( gamePath ).ToString();
var newFullPath = Path.Combine( optionDir.FullName, relPath );
var redirectPath = new FullPath( Path.Combine( _mod.ModPath.FullName, groupDir.Name, optionDir.Name, relPath ) );
Directory.CreateDirectory( Path.GetDirectoryName( newFullPath )! );
File.Copy( fullPath.FullName, newFullPath, true );
newDict.Add( gamePath, redirectPath );
}
}
}
return true;
}
catch( Exception e )
{
ChatUtil.NotificationMessage( $"Could not normalize mod:\n{e}", "Failure", NotificationType.Error );
_redirections = null;
}
return false;
}
private bool MoveOldFiles()
{
try
{
// Clean old directories and files.
var oldDirectory = Directory.CreateDirectory( _oldDirName );
foreach( var dir in _mod.ModPath.EnumerateDirectories() )
{
if( dir.FullName.Equals( _oldDirName, StringComparison.OrdinalIgnoreCase )
|| dir.FullName.Equals( _normalizationDirName, StringComparison.OrdinalIgnoreCase ) )
{
continue;
}
dir.MoveTo( Path.Combine( oldDirectory.FullName, dir.Name ) );
}
return true;
}
catch( Exception e )
{
ChatUtil.NotificationMessage( $"Could not move old files out of the way while normalizing mod mod:\n{e}", "Failure", NotificationType.Error );
}
return false;
}
private bool MoveNewFiles()
{
try
{
var mainDir = new DirectoryInfo( _normalizationDirName );
foreach( var dir in mainDir.EnumerateDirectories() )
{
dir.MoveTo( Path.Combine( _mod.ModPath.FullName, dir.Name ) );
}
mainDir.Delete();
Directory.Delete( _oldDirName, true );
return true;
}
catch( Exception e )
{
ChatUtil.NotificationMessage( $"Could not move new files into the mod while normalizing mod mod:\n{e}", "Failure", NotificationType.Error );
foreach( var dir in _mod.ModPath.EnumerateDirectories() )
{
if( dir.FullName.Equals( _oldDirName, StringComparison.OrdinalIgnoreCase )
|| dir.FullName.Equals( _normalizationDirName, StringComparison.OrdinalIgnoreCase ) )
{
continue;
}
try
{
dir.Delete( true );
}
catch
{
// ignored
}
}
}
return false;
}
private void ApplyRedirections( Manager manager )
{
if( _redirections == null )
{
return;
}
foreach( var option in _mod.AllSubMods.OfType< SubMod >() )
{
manager.OptionSetFiles( _mod, option.GroupIdx, option.OptionIdx, _redirections[ option.GroupIdx + 1 ][ option.OptionIdx ] );
}
}
}
}

View file

@ -1,629 +0,0 @@
namespace Penumbra.Mods;
public partial class Mod
{
public partial class Manager
{
//public class Normalizer
//{
// private Dictionary< Utf8GamePath, (FullPath Path, int GroupPriority) > Files = new();
// private Dictionary< Utf8GamePath, (FullPath Path, int GroupPriority) > Swaps = new();
// private HashSet< (MetaManipulation Manipulation, int GroupPriority) > Manips = new();
//
// public Normalizer( Mod mod )
// {
// // Default changes are irrelevant since they can only be overwritten.
// foreach( var group in mod.Groups )
// {
// foreach( var option in group )
// {
// foreach( var (key, value) in option.Files )
// {
// if( !Files.TryGetValue( key, out var list ) )
// {
// list = new List< (FullPath Path, IModGroup Group, ISubMod Option) > { ( value, @group, option ) };
// Files[ key ] = list;
// }
// else
// {
// list.Add( ( value, @group, option ) );
// }
// }
// }
// }
// }
//
// // Normalize a mod, this entails:
// // - If
// public static void Normalize( Mod mod )
// {
// NormalizeOptions( mod );
// MergeSingleGroups( mod );
// DeleteEmptyGroups( mod );
// }
//
//
// // Delete every option group that has either no options,
// // or exclusively empty options.
// // Triggers changes through calling ModManager.
// private static void DeleteEmptyGroups( Mod mod )
// {
// for( var i = 0; i < mod.Groups.Count; ++i )
// {
// DeleteIdenticalOptions( mod, i );
// var group = mod.Groups[ i ];
// if( group.Count == 0 || group.All( o => o.FileSwaps.Count == 0 && o.Files.Count == 0 && o.Manipulations.Count == 0 ) )
// {
// Penumbra.ModManager.DeleteModGroup( mod, i-- );
// }
// }
// }
//
// // Merge every non-optional group into the default mod.
// // Overwrites default mod entries if necessary.
// // Deletes the non-optional group afterwards.
// // Triggers changes through calling ModManager.
// private static void MergeSingleGroup( Mod mod )
// {
// var defaultMod = ( SubMod )mod.Default;
// for( var i = 0; i < mod.Groups.Count; ++i )
// {
// var group = mod.Groups[ i ];
// if( group.Type == SelectType.Single && group.Count == 1 )
// {
// defaultMod.MergeIn( group[ 0 ] );
//
// Penumbra.ModManager.DeleteModGroup( mod, i-- );
// }
// }
// }
//
// private static void NotifyChanges( Mod mod, int groupIdx, ModOptionChangeType type, ref bool anyChanges )
// {
// if( anyChanges )
// {
// for( var i = 0; i < mod.Groups[ groupIdx ].Count; ++i )
// {
// Penumbra.ModManager.ModOptionChanged.Invoke( type, mod, groupIdx, i, -1 );
// }
//
// anyChanges = false;
// }
// }
//
// private static void NormalizeOptions( Mod mod )
// {
// var defaultMod = ( SubMod )mod.Default;
//
// for( var i = 0; i < mod.Groups.Count; ++i )
// {
// var group = mod.Groups[ i ];
// if( group.Type == SelectType.Multi || group.Count < 2 )
// {
// continue;
// }
//
// var firstOption = mod.Groups[ i ][ 0 ];
// var anyChanges = false;
// foreach( var (key, value) in firstOption.Files.ToList() )
// {
// if( group.Skip( 1 ).All( o => o.Files.TryGetValue( key, out var v ) && v.Equals( value ) ) )
// {
// anyChanges = true;
// defaultMod.FileData[ key ] = value;
// foreach( var option in group.Cast< SubMod >() )
// {
// option.FileData.Remove( key );
// }
// }
// }
//
// NotifyChanges( mod, i, ModOptionChangeType.OptionFilesChanged, ref anyChanges );
//
// foreach( var (key, value) in firstOption.FileSwaps.ToList() )
// {
// if( group.Skip( 1 ).All( o => o.FileSwaps.TryGetValue( key, out var v ) && v.Equals( value ) ) )
// {
// anyChanges = true;
// defaultMod.FileData[ key ] = value;
// foreach( var option in group.Cast< SubMod >() )
// {
// option.FileSwapData.Remove( key );
// }
// }
// }
//
// NotifyChanges( mod, i, ModOptionChangeType.OptionSwapsChanged, ref anyChanges );
//
// anyChanges = false;
// foreach( var manip in firstOption.Manipulations.ToList() )
// {
// if( group.Skip( 1 ).All( o => ( ( HashSet< MetaManipulation > )o.Manipulations ).TryGetValue( manip, out var m )
// && manip.EntryEquals( m ) ) )
// {
// anyChanges = true;
// defaultMod.ManipulationData.Remove( manip );
// defaultMod.ManipulationData.Add( manip );
// foreach( var option in group.Cast< SubMod >() )
// {
// option.ManipulationData.Remove( manip );
// }
// }
// }
//
// NotifyChanges( mod, i, ModOptionChangeType.OptionMetaChanged, ref anyChanges );
// }
// }
//
//
// // Delete all options that are entirely identical.
// // Deletes the later occurring option.
// private static void DeleteIdenticalOptions( Mod mod, int groupIdx )
// {
// var group = mod.Groups[ groupIdx ];
// for( var i = 0; i < group.Count; ++i )
// {
// var option = group[ i ];
// for( var j = i + 1; j < group.Count; ++j )
// {
// var option2 = group[ j ];
// if( option.Files.SetEquals( option2.Files )
// && option.FileSwaps.SetEquals( option2.FileSwaps )
// && option.Manipulations.SetEquals( option2.Manipulations ) )
// {
// Penumbra.ModManager.DeleteOption( mod, groupIdx, j-- );
// }
// }
// }
// }
//}
}
}
// TODO Everything
//ublic class ModCleanup
//
// private const string Duplicates = "Duplicates";
// private const string Required = "Required";
//
// private readonly DirectoryInfo _baseDir;
// private readonly ModMeta _mod;
//
// private readonly Dictionary< long, List< FileInfo > > _filesBySize = new();
//
//
// private ModCleanup( DirectoryInfo baseDir, ModMeta mod )
// {
// _baseDir = baseDir;
// _mod = mod;
// BuildDict();
// }
//
// private void BuildDict()
// {
// foreach( var file in _baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
// {
// var fileLength = file.Length;
// if( _filesBySize.TryGetValue( fileLength, out var files ) )
// {
// files.Add( file );
// }
// else
// {
// _filesBySize[ fileLength ] = new List< FileInfo > { file };
// }
// }
// }
//
// private static DirectoryInfo CreateNewModDir( Mod mod, string optionGroup, string option )
// {
// var newName = $"{mod.BasePath.Name}_{optionGroup}_{option}";
// return TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config.ModDirectory ), newName );
// }
//
// private static Mod CreateNewMod( DirectoryInfo newDir, string newSortOrder )
// {
// var idx = Penumbra.ModManager.AddMod( newDir );
// var newMod = Penumbra.ModManager.Mods[ idx ];
// newMod.Move( newSortOrder );
// newMod.ComputeChangedItems();
// ModFileSystem.InvokeChange();
// return newMod;
// }
//
// private static ModMeta CreateNewMeta( DirectoryInfo newDir, Mod mod, string name, string optionGroup, string option )
// {
// var newMeta = new ModMeta
// {
// Author = mod.Meta.Author,
// Name = name,
// Description = $"Split from {mod.Meta.Name} Group {optionGroup} Option {option}.",
// };
// var metaFile = new FileInfo( Path.Combine( newDir.FullName, "meta.json" ) );
// newMeta.SaveToFile( metaFile );
// return newMeta;
// }
//
// private static void CreateModSplit( HashSet< string > unseenPaths, Mod mod, OptionGroup group, Option option )
// {
// try
// {
// 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 );
// foreach( var (fileName, paths) in option.OptionFiles )
// {
// 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.ToString() );
// Directory.CreateDirectory( Path.GetDirectoryName( newPath )! );
// File.Copy( oldPath, newPath, true );
// }
// }
// }
//
// var newSortOrder = group.SelectionType == SelectType.Single
// ? $"{mod.Order.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName}/{option.OptionName}"
// : $"{mod.Order.ParentFolder.FullName}/{mod.Meta.Name}/{group.GroupName} - {option.OptionName}";
// CreateNewMod( newDir, newSortOrder );
// }
// catch( Exception e )
// {
// Penumbra.Log.Error( $"Could not split Mod:\n{e}" );
// }
// }
//
// public static void SplitMod( Mod mod )
// {
// if( mod.Meta.Groups.Count == 0 )
// {
// return;
// }
//
// var unseenPaths = mod.Resources.ModFiles.Select( f => f.FullName ).ToHashSet();
// foreach( var group in mod.Meta.Groups.Values )
// {
// foreach( var option in group.Options )
// {
// CreateModSplit( unseenPaths, mod, group, option );
// }
// }
//
// if( unseenPaths.Count == 0 )
// {
// return;
// }
//
// var defaultGroup = new OptionGroup()
// {
// GroupName = "Default",
// SelectionType = SelectType.Multi,
// };
// var defaultOption = new Option()
// {
// OptionName = "Files",
// 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 );
// }
//
// private static Option FindOrCreateDuplicates( ModMeta meta )
// {
// static Option RequiredOption()
// => new()
// {
// OptionName = Required,
// OptionDesc = "",
// OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
// };
//
// if( meta.Groups.TryGetValue( Duplicates, out var duplicates ) )
// {
// var idx = duplicates.Options.FindIndex( o => o.OptionName == Required );
// if( idx >= 0 )
// {
// return duplicates.Options[ idx ];
// }
//
// duplicates.Options.Add( RequiredOption() );
// return duplicates.Options.Last();
// }
//
// meta.Groups.Add( Duplicates, new OptionGroup
// {
// GroupName = Duplicates,
// SelectionType = SelectType.Single,
// Options = new List< Option > { RequiredOption() },
// } );
//
// return meta.Groups[ Duplicates ].Options.First();
// }
//
//
// private void ReplaceFile( FileInfo f1, FileInfo f2 )
// {
// if( !Utf8RelPath.FromFile( f1, _baseDir, out var relName1 )
// || !Utf8RelPath.FromFile( f2, _baseDir, out var relName2 ) )
// {
// return;
// }
//
// var inOption1 = false;
// var inOption2 = false;
// foreach( var option in _mod.Groups.SelectMany( g => g.Value.Options ) )
// {
// if( option.OptionFiles.ContainsKey( relName1 ) )
// {
// inOption1 = true;
// }
//
// if( !option.OptionFiles.TryGetValue( relName2, out var values ) )
// {
// continue;
// }
//
// inOption2 = true;
//
// foreach( var value in values )
// {
// option.AddFile( relName1, value );
// }
//
// option.OptionFiles.Remove( relName2 );
// }
//
// if( !inOption1 || !inOption2 )
// {
// var duplicates = FindOrCreateDuplicates( _mod );
// if( !inOption1 )
// {
// duplicates.AddFile( relName1, relName2.ToGamePath() );
// }
//
// if( !inOption2 )
// {
// duplicates.AddFile( relName1, relName1.ToGamePath() );
// }
// }
//
// Penumbra.Log.Information( $"File {relName1} and {relName2} are identical. Deleting the second." );
// f2.Delete();
// }
//
//
//
// private static bool FileIsInAnyGroup( ModMeta meta, Utf8RelPath relPath, bool exceptDuplicates = false )
// {
// var groupEnumerator = exceptDuplicates
// ? meta.Groups.Values.Where( g => g.GroupName != Duplicates )
// : meta.Groups.Values;
// return groupEnumerator.SelectMany( group => group.Options )
// .Any( option => option.OptionFiles.ContainsKey( relPath ) );
// }
//
// private static void CleanUpDuplicates( ModMeta meta )
// {
// if( !meta.Groups.TryGetValue( Duplicates, out var info ) )
// {
// return;
// }
//
// var requiredIdx = info.Options.FindIndex( o => o.OptionName == Required );
// if( requiredIdx >= 0 )
// {
// var required = info.Options[ requiredIdx ];
// foreach( var (key, value) in required.OptionFiles.ToArray() )
// {
// if( value.Count > 1 || FileIsInAnyGroup( meta, key, true ) )
// {
// continue;
// }
//
// if( value.Count == 0 || value.First().CompareTo( key.ToGamePath() ) == 0 )
// {
// required.OptionFiles.Remove( key );
// }
// }
//
// if( required.OptionFiles.Count == 0 )
// {
// info.Options.RemoveAt( requiredIdx );
// }
// }
//
// if( info.Options.Count == 0 )
// {
// meta.Groups.Remove( Duplicates );
// }
// }
//
// public enum GroupType
// {
// Both = 0,
// Single = 1,
// Multi = 2,
// };
//
// private static void RemoveFromGroups( ModMeta meta, Utf8RelPath relPath, Utf8GamePath gamePath, GroupType type = GroupType.Both,
// bool skipDuplicates = true )
// {
// if( meta.Groups.Count == 0 )
// {
// return;
// }
//
// var enumerator = type switch
// {
// GroupType.Both => meta.Groups.Values,
// GroupType.Single => meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ),
// GroupType.Multi => meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ),
// _ => throw new InvalidEnumArgumentException( "Invalid Enum in RemoveFromGroups" ),
// };
// foreach( var group in enumerator )
// {
// var optionEnum = skipDuplicates
// ? group.Options.Where( o => group.GroupName != Duplicates || o.OptionName != Required )
// : group.Options;
// foreach( var option in optionEnum )
// {
// if( option.OptionFiles.TryGetValue( relPath, out var gamePaths ) && gamePaths.Remove( gamePath ) && gamePaths.Count == 0 )
// {
// option.OptionFiles.Remove( relPath );
// }
// }
// }
// }
//
// public static bool MoveFile( ModMeta meta, string basePath, Utf8RelPath oldRelPath, Utf8RelPath newRelPath )
// {
// if( oldRelPath.Equals( newRelPath ) )
// {
// return true;
// }
//
// try
// {
// var newFullPath = Path.Combine( basePath, newRelPath.ToString() );
// new FileInfo( newFullPath ).Directory!.Create();
// File.Move( Path.Combine( basePath, oldRelPath.ToString() ), newFullPath );
// }
// catch( Exception e )
// {
// Penumbra.Log.Error( $"Could not move file from {oldRelPath} to {newRelPath}:\n{e}" );
// return false;
// }
//
// foreach( var option in meta.Groups.Values.SelectMany( group => group.Options ) )
// {
// if( option.OptionFiles.TryGetValue( oldRelPath, out var gamePaths ) )
// {
// option.OptionFiles.Add( newRelPath, gamePaths );
// option.OptionFiles.Remove( oldRelPath );
// }
// }
//
// return true;
// }
//
//
// private static void RemoveUselessGroups( ModMeta meta )
// {
// meta.Groups = meta.Groups.Where( kvp => kvp.Value.Options.Any( o => o.OptionFiles.Count > 0 ) )
// .ToDictionary( kvp => kvp.Key, kvp => kvp.Value );
// }
//
// // Goes through all Single-Select options and checks if file links are in each of them.
// // If they are, it moves those files to the root folder and removes them from the groups (and puts them to duplicates, if necessary).
// public static void Normalize( DirectoryInfo baseDir, ModMeta meta )
// {
// foreach( var group in meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single && g.GroupName != Duplicates ) )
// {
// var firstOption = true;
// HashSet< (Utf8RelPath, Utf8GamePath) > groupList = new();
// foreach( var option in group.Options )
// {
// 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 ) ) );
// }
//
// if( firstOption )
// {
// groupList = optionList;
// }
// else
// {
// groupList.IntersectWith( optionList );
// }
//
// firstOption = false;
// }
//
// var newPath = new Dictionary< Utf8RelPath, Utf8GamePath >();
// foreach( var (path, gamePath) in groupList )
// {
// var relPath = new Utf8RelPath( gamePath );
// if( newPath.TryGetValue( path, out var usedGamePath ) )
// {
// var required = FindOrCreateDuplicates( meta );
// var usedRelPath = new Utf8RelPath( usedGamePath );
// required.AddFile( usedRelPath, gamePath );
// required.AddFile( usedRelPath, usedGamePath );
// RemoveFromGroups( meta, relPath, gamePath, GroupType.Single );
// }
// else if( MoveFile( meta, baseDir.FullName, path, relPath ) )
// {
// newPath[ path ] = gamePath;
// if( FileIsInAnyGroup( meta, relPath ) )
// {
// FindOrCreateDuplicates( meta ).AddFile( relPath, gamePath );
// }
//
// RemoveFromGroups( meta, relPath, gamePath, GroupType.Single );
// }
// }
// }
//
// RemoveUselessGroups( meta );
// ClearEmptySubDirectories( baseDir );
// }
//
// public static void AutoGenerateGroups( DirectoryInfo baseDir, ModMeta meta )
// {
// meta.Groups.Clear();
// ClearEmptySubDirectories( baseDir );
// foreach( var groupDir in baseDir.EnumerateDirectories() )
// {
// var group = new OptionGroup
// {
// GroupName = groupDir.Name,
// SelectionType = SelectType.Single,
// Options = new List< Option >(),
// };
//
// foreach( var optionDir in groupDir.EnumerateDirectories() )
// {
// var option = new Option
// {
// OptionDesc = string.Empty,
// OptionName = optionDir.Name,
// OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(),
// };
// foreach( var file in optionDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
// {
// 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.Count > 0 )
// {
// group.Options.Add( option );
// }
// }
//
// if( group.Options.Count > 0 )
// {
// meta.Groups.Add( groupDir.Name, group );
// }
// }
//
// var idx = Penumbra.ModManager.Mods.IndexOf( m => m.Meta == meta );
// foreach( var collection in Penumbra.CollectionManager )
// {
// collection.Settings[ idx ]?.FixInvalidSettings( meta );
// }
// }
//

View file

@ -13,10 +13,17 @@ namespace Penumbra.Mods;
public partial class Mod
{
// Create and return a new directory based on the given directory and name, that is
// - Not Empty
// - Unique, by appending (digit) for duplicates.
// - Containing no symbols invalid for FFXIV or windows paths.
/// <summary>
/// Create and return a new directory based on the given directory and name, that is <br/>
/// - Not Empty.<br/>
/// - Unique, by appending (digit) for duplicates.<br/>
/// - Containing no symbols invalid for FFXIV or windows paths.<br/>
/// </summary>
/// <param name="outDirectory"></param>
/// <param name="modListName"></param>
/// <param name="create"></param>
/// <returns></returns>
/// <exception cref="IOException"></exception>
internal static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName, bool create = true )
{
var name = modListName;
@ -160,8 +167,9 @@ public partial class Mod
{
return replacement + replacement;
}
StringBuilder sb = new(s.Length);
foreach( var c in s.Normalize(NormalizationForm.FormKC) )
foreach( var c in s.Normalize( NormalizationForm.FormKC ) )
{
if( c.IsInvalidAscii() || c.IsInvalidInPath() )
{

View file

@ -20,10 +20,11 @@ namespace Penumbra.UI.Classes;
public partial class ModEditWindow : Window, IDisposable
{
private const string WindowBaseLabel = "###SubModEdit";
private Editor? _editor;
private Mod? _mod;
private Vector2 _iconSize = Vector2.Zero;
private const string WindowBaseLabel = "###SubModEdit";
private Editor? _editor;
private Mod? _mod;
private Vector2 _iconSize = Vector2.Zero;
private bool _allowReduplicate = false;
public void ChangeMod( Mod mod )
{
@ -118,6 +119,7 @@ public partial class ModEditWindow : Window, IDisposable
sb.Append( $" | {swaps} Swaps" );
}
_allowReduplicate = redirections != _editor.AvailableFiles.Count || _editor.MissingFiles.Count > 0;
sb.Append( WindowBaseLabel );
WindowName = sb.ToString();
}
@ -288,6 +290,15 @@ public partial class ModEditWindow : Window, IDisposable
_editor.StartDuplicateCheck();
}
const string desc = "Tries to create a unique copy of a file for every game path manipulated and put them in [Groupname]/[Optionname]/[GamePath] order.\n"
+ "This will also delete all unused files and directories if it succeeds.\n"
+ "Care was taken that a failure should not destroy the mod but revert to its original state, but you use this at your own risk anyway.";
if( ImGuiUtil.DrawDisabledButton( "Re-Duplicate and Normalize Mod", Vector2.Zero, desc, !_allowReduplicate ) )
{
_mod!.Normalize( Penumbra.ModManager );
_editor.RevertFiles();
}
if( !_editor.DuplicatesFinished )
{
ImGui.SameLine();