mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add an option to reduplicate and normalize a mod.
This commit is contained in:
parent
eedd3e2dac
commit
7033b65d33
5 changed files with 290 additions and 708 deletions
|
|
@ -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 );
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
262
Penumbra/Mods/Editor/Mod.Normalization.cs
Normal file
262
Penumbra/Mods/Editor/Mod.Normalization.cs
Normal 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 ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
|
@ -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() )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue