Move mod creation functions to own subclass.

This commit is contained in:
Ottermandias 2023-03-03 13:43:00 +01:00
parent c2ac745d72
commit 1f942491ac
13 changed files with 237 additions and 233 deletions

View file

@ -829,7 +829,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public PenumbraApiEc CreateNamedTemporaryCollection( string name ) public PenumbraApiEc CreateNamedTemporaryCollection( string name )
{ {
CheckInitialized(); CheckInitialized();
if( name.Length == 0 || Mod.ReplaceBadXivSymbols( name ) != name ) if( name.Length == 0 || Mod.Creator.ReplaceBadXivSymbols( name ) != name )
{ {
return PenumbraApiEc.InvalidArgument; return PenumbraApiEc.InvalidArgument;
} }

View file

@ -44,7 +44,7 @@ public partial class TexToolsImporter
}; };
Penumbra.Log.Information( $" -> Importing {archive.Type} Archive." ); Penumbra.Log.Information( $" -> Importing {archive.Type} Archive." );
_currentModDirectory = Mod.CreateModFolder( _baseDirectory, Path.GetRandomFileName() ); _currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, Path.GetRandomFileName() );
var options = new ExtractionOptions() var options = new ExtractionOptions()
{ {
ExtractFullPath = true, ExtractFullPath = true,
@ -99,13 +99,13 @@ public partial class TexToolsImporter
// Use either the top-level directory as the mods base name, or the (fixed for path) name in the json. // Use either the top-level directory as the mods base name, or the (fixed for path) name in the json.
if( leadDir ) if( leadDir )
{ {
_currentModDirectory = Mod.CreateModFolder( _baseDirectory, baseName, false ); _currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, baseName, false );
Directory.Move( Path.Combine( oldName, baseName ), _currentModDirectory.FullName ); Directory.Move( Path.Combine( oldName, baseName ), _currentModDirectory.FullName );
Directory.Delete( oldName ); Directory.Delete( oldName );
} }
else else
{ {
_currentModDirectory = Mod.CreateModFolder( _baseDirectory, name, false ); _currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, name, false );
Directory.Move( oldName, _currentModDirectory.FullName ); Directory.Move( oldName, _currentModDirectory.FullName );
} }
@ -114,6 +114,7 @@ public partial class TexToolsImporter
return _currentModDirectory; return _currentModDirectory;
} }
// Search the archive for the meta.json file which needs to exist. // Search the archive for the meta.json file which needs to exist.
private static string FindArchiveModMeta( IArchive archive, out bool leadDir ) private static string FindArchiveModMeta( IArchive archive, out bool leadDir )
{ {

View file

@ -34,14 +34,14 @@ public partial class TexToolsImporter
var modList = modListRaw.Select( m => JsonConvert.DeserializeObject< SimpleMod >( m, JsonSettings )! ).ToList(); var modList = modListRaw.Select( m => JsonConvert.DeserializeObject< SimpleMod >( m, JsonSettings )! ).ToList();
_currentModDirectory = Mod.CreateModFolder( _baseDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) ); _currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) );
// Create a new ModMeta from the TTMP mod list info // Create a new ModMeta from the TTMP mod list info
Mod.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null ); Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
// Open the mod data file from the mod pack as a SqPackStream // Open the mod data file from the mod pack as a SqPackStream
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" ); _streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
ExtractSimpleModList( _currentModDirectory, modList ); ExtractSimpleModList( _currentModDirectory, modList );
Mod.CreateDefaultFiles( _currentModDirectory ); Mod.Creator.CreateDefaultFiles( _currentModDirectory );
ResetStreamDisposer(); ResetStreamDisposer();
return _currentModDirectory; return _currentModDirectory;
} }
@ -90,15 +90,15 @@ public partial class TexToolsImporter
_currentOptionName = DefaultTexToolsData.DefaultOption; _currentOptionName = DefaultTexToolsData.DefaultOption;
Penumbra.Log.Information( " -> Importing Simple V2 ModPack" ); Penumbra.Log.Information( " -> Importing Simple V2 ModPack" );
_currentModDirectory = Mod.CreateModFolder( _baseDirectory, _currentModName ); _currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
Mod.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description ) Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
? "Mod imported from TexTools mod pack" ? "Mod imported from TexTools mod pack"
: modList.Description, modList.Version, modList.Url ); : modList.Description, modList.Version, modList.Url );
// Open the mod data file from the mod pack as a SqPackStream // Open the mod data file from the mod pack as a SqPackStream
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" ); _streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList ); ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList );
Mod.CreateDefaultFiles( _currentModDirectory ); Mod.Creator.CreateDefaultFiles( _currentModDirectory );
ResetStreamDisposer(); ResetStreamDisposer();
return _currentModDirectory; return _currentModDirectory;
} }
@ -135,8 +135,8 @@ public partial class TexToolsImporter
_currentNumOptions = GetOptionCount( modList ); _currentNumOptions = GetOptionCount( modList );
_currentModName = modList.Name; _currentModName = modList.Name;
_currentModDirectory = Mod.CreateModFolder( _baseDirectory, _currentModName ); _currentModDirectory = Mod.Creator.CreateModFolder( _baseDirectory, _currentModName );
Mod.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url ); Mod.Creator.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
if( _currentNumOptions == 0 ) if( _currentNumOptions == 0 )
{ {
@ -173,7 +173,7 @@ public partial class TexToolsImporter
{ {
var name = numGroups == 1 ? _currentGroupName : $"{_currentGroupName}, Part {groupId + 1}"; var name = numGroups == 1 ? _currentGroupName : $"{_currentGroupName}, Part {groupId + 1}";
options.Clear(); options.Clear();
var groupFolder = Mod.NewSubFolderName( _currentModDirectory, name ) var groupFolder = Mod.Creator.NewSubFolderName( _currentModDirectory, name )
?? new DirectoryInfo( Path.Combine( _currentModDirectory.FullName, ?? new DirectoryInfo( Path.Combine( _currentModDirectory.FullName,
numGroups == 1 ? $"Group {groupPriority + 1}" : $"Group {groupPriority + 1}, Part {groupId + 1}" ) ); numGroups == 1 ? $"Group {groupPriority + 1}" : $"Group {groupPriority + 1}, Part {groupId + 1}" ) );
@ -183,10 +183,10 @@ public partial class TexToolsImporter
var option = allOptions[ i + optionIdx ]; var option = allOptions[ i + optionIdx ];
_token.ThrowIfCancellationRequested(); _token.ThrowIfCancellationRequested();
_currentOptionName = option.Name; _currentOptionName = option.Name;
var optionFolder = Mod.NewSubFolderName( groupFolder, option.Name ) var optionFolder = Mod.Creator.NewSubFolderName( groupFolder, option.Name )
?? new DirectoryInfo( Path.Combine( groupFolder.FullName, $"Option {i + optionIdx + 1}" ) ); ?? new DirectoryInfo( Path.Combine( groupFolder.FullName, $"Option {i + optionIdx + 1}" ) );
ExtractSimpleModList( optionFolder, option.ModsJsons ); ExtractSimpleModList( optionFolder, option.ModsJsons );
options.Add( Mod.CreateSubMod( _currentModDirectory, optionFolder, option ) ); options.Add( Mod.Creator.CreateSubMod( _currentModDirectory, optionFolder, option ) );
if( option.IsChecked ) if( option.IsChecked )
{ {
defaultSettings = group.SelectionType == GroupType.Multi defaultSettings = group.SelectionType == GroupType.Multi
@ -207,12 +207,12 @@ public partial class TexToolsImporter
if( empty != null ) if( empty != null )
{ {
_currentOptionName = empty.Name; _currentOptionName = empty.Name;
options.Insert( 0, Mod.CreateEmptySubMod( empty.Name ) ); options.Insert( 0, Mod.Creator.CreateEmptySubMod( empty.Name ) );
defaultSettings = defaultSettings == null ? 0 : defaultSettings.Value + 1; defaultSettings = defaultSettings == null ? 0 : defaultSettings.Value + 1;
} }
} }
Mod.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority, Mod.Creator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority,
defaultSettings ?? 0, group.Description, options ); defaultSettings ?? 0, group.Description, options );
++groupPriority; ++groupPriority;
} }
@ -220,7 +220,7 @@ public partial class TexToolsImporter
} }
ResetStreamDisposer(); ResetStreamDisposer();
Mod.CreateDefaultFiles( _currentModDirectory ); Mod.Creator.CreateDefaultFiles( _currentModDirectory );
return _currentModDirectory; return _currentModDirectory;
} }

View file

@ -150,11 +150,11 @@ public partial class Mod
foreach( var (group, groupIdx) in _mod.Groups.WithIndex() ) foreach( var (group, groupIdx) in _mod.Groups.WithIndex() )
{ {
_redirections[ groupIdx + 1 ] = new Dictionary< Utf8GamePath, FullPath >[group.Count]; _redirections[ groupIdx + 1 ] = new Dictionary< Utf8GamePath, FullPath >[group.Count];
var groupDir = CreateModFolder( directory, group.Name ); var groupDir = Creator.CreateModFolder( directory, group.Name );
foreach( var option in group.OfType< SubMod >() ) foreach( var option in group.OfType< SubMod >() )
{ {
var optionDir = CreateModFolder( groupDir, option.Name ); var optionDir = Creator.CreateModFolder( groupDir, option.Name );
newDict = new Dictionary< Utf8GamePath, FullPath >( option.FileData.Count ); newDict = new Dictionary< Utf8GamePath, FullPath >( option.FileData.Count );
_redirections[ groupIdx + 1 ][ option.OptionIdx ] = newDict; _redirections[ groupIdx + 1 ][ option.OptionIdx ] = newDict;
foreach( var (gamePath, fullPath) in option.FileData ) foreach( var (gamePath, fullPath) in option.FileData )

View file

@ -64,12 +64,6 @@ public static class EquipmentSwap
var imcFileTo = new ImcFile( imcManip); var imcFileTo = new ImcFile( imcManip);
var isAccessory = slot.IsAccessory(); var isAccessory = slot.IsAccessory();
var estType = slot switch
{
EquipSlot.Head => EstManipulation.EstType.Head,
EquipSlot.Body => EstManipulation.EstType.Body,
_ => ( EstManipulation.EstType )0,
};
var skipFemale = false; var skipFemale = false;
var skipMale = false; var skipMale = false;
@ -89,12 +83,6 @@ public static class EquipmentSwap
continue; continue;
} }
var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo );
if( est != null )
{
swaps.Add( est );
}
try try
{ {
var eqdp = CreateEqdp( redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo ); var eqdp = CreateEqdp( redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo );
@ -140,6 +128,19 @@ public static class EquipmentSwap
{ {
var mdl = CreateMdl( redirections, slot, gr, idFrom, idTo, mtrlTo ); var mdl = CreateMdl( redirections, slot, gr, idFrom, idTo, mtrlTo );
meta.ChildSwaps.Add( mdl ); meta.ChildSwaps.Add( mdl );
var estType = slot switch
{
EquipSlot.Head => EstManipulation.EstType.Head,
EquipSlot.Body => EstManipulation.EstType.Body,
_ => ( EstManipulation.EstType )0,
};
var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo );
if( est != null )
{
meta.ChildSwaps.Add( est );
}
} }
else if( !ownMtrl && meta.SwapAppliedIsDefault ) else if( !ownMtrl && meta.SwapAppliedIsDefault )
{ {

View file

@ -175,7 +175,7 @@ public partial class Mod
return NewDirectoryState.Identical; return NewDirectoryState.Identical;
} }
var fixedNewName = ReplaceBadXivSymbols( newName ); var fixedNewName = Creator.ReplaceBadXivSymbols( newName );
if( fixedNewName != newName ) if( fixedNewName != newName )
{ {
return NewDirectoryState.ContainsInvalidSymbols; return NewDirectoryState.ContainsInvalidSymbols;

View file

@ -1,187 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Dalamud.Utility;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Import;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
public partial class Mod
{
/// <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;
if( name.Length == 0 )
{
name = "_";
}
var newModFolderBase = NewOptionDirectory( outDirectory, name );
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
if( newModFolder.Length == 0 )
{
throw new IOException( "Could not create mod folder: too many folders of the same name exist." );
}
if( create )
{
Directory.CreateDirectory( newModFolder );
}
return new DirectoryInfo( newModFolder );
}
/// <summary>
/// Create the name for a group or option subfolder based on its parent folder and given name.
/// subFolderName should never be empty, and the result is unique and contains no invalid symbols.
/// </summary>
internal static DirectoryInfo? NewSubFolderName( DirectoryInfo parentFolder, string subFolderName )
{
var newModFolderBase = NewOptionDirectory( parentFolder, subFolderName );
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
return newModFolder.Length == 0 ? null : new DirectoryInfo( newModFolder );
}
// Create the file containing the meta information about a mod from scratch.
internal static void CreateMeta( DirectoryInfo directory, string? name, string? author, string? description, string? version,
string? website )
{
var mod = new Mod( directory );
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString( name! );
mod.Author = author != null ? new LowerString( author ) : mod.Author;
mod.Description = description ?? mod.Description;
mod.Version = version ?? mod.Version;
mod.Website = website ?? mod.Website;
mod.SaveMetaFile(); // Not delayed.
}
// Create a file for an option group from given data.
internal static void CreateOptionGroup( DirectoryInfo baseFolder, GroupType type, string name,
int priority, int index, uint defaultSettings, string desc, IEnumerable< ISubMod > subMods )
{
switch( type )
{
case GroupType.Multi:
{
var group = new MultiModGroup()
{
Name = name,
Description = desc,
Priority = priority,
DefaultSettings = defaultSettings,
};
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
IModGroup.Save( group, baseFolder, index );
break;
}
case GroupType.Single:
{
var group = new SingleModGroup()
{
Name = name,
Description = desc,
Priority = priority,
DefaultSettings = defaultSettings,
};
group.OptionData.AddRange( subMods.OfType< SubMod >() );
IModGroup.Save( group, baseFolder, index );
break;
}
}
}
// Create the data for a given sub mod from its data and the folder it is based on.
internal static ISubMod CreateSubMod( DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option )
{
var list = optionFolder.EnumerateFiles( "*.*", SearchOption.AllDirectories )
.Select( f => ( Utf8GamePath.FromFile( f, optionFolder, out var gamePath, true ), gamePath, new FullPath( f ) ) )
.Where( t => t.Item1 );
var mod = new SubMod( null! ) // Mod is irrelevant here, only used for saving.
{
Name = option.Name,
Description = option.Description,
};
foreach( var (_, gamePath, file) in list )
{
mod.FileData.TryAdd( gamePath, file );
}
mod.IncorporateMetaChanges( baseFolder, true );
return mod;
}
// Create an empty sub mod for single groups with None options.
internal static ISubMod CreateEmptySubMod( string name )
=> new SubMod( null! ) // Mod is irrelevant here, only used for saving.
{
Name = name,
};
// Create the default data file from all unused files that were not handled before
// and are used in sub mods.
internal static void CreateDefaultFiles( DirectoryInfo directory )
{
var mod = new Mod( directory );
mod.Reload( false, out _ );
foreach( var file in mod.FindUnusedFiles() )
{
if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) )
{
mod._default.FileData.TryAdd( gamePath, file );
}
}
mod._default.IncorporateMetaChanges( directory, true );
mod.SaveDefaultMod();
}
// Return the name of a new valid directory based on the base directory and the given name.
private static DirectoryInfo NewOptionDirectory( DirectoryInfo baseDir, string optionName )
=> new(Path.Combine( baseDir.FullName, ReplaceBadXivSymbols( optionName ) ));
// Normalize for nicer names, and remove invalid symbols or invalid paths.
public static string ReplaceBadXivSymbols( string s, string replacement = "_" )
{
if( s == "." )
{
return replacement;
}
if( s == ".." )
{
return replacement + replacement;
}
StringBuilder sb = new(s.Length);
foreach( var c in s.Normalize( NormalizationForm.FormKC ) )
{
if( c.IsInvalidInPath() )
{
sb.Append( replacement );
}
else
{
sb.Append( c );
}
}
return sb.ToString();
}
}

View file

@ -0,0 +1,188 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Dalamud.Utility;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
using Penumbra.Import;
using Penumbra.String.Classes;
namespace Penumbra.Mods;
public partial class Mod
{
internal static class Creator
{
/// <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>
public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName, bool create = true )
{
var name = modListName;
if( name.Length == 0 )
{
name = "_";
}
var newModFolderBase = NewOptionDirectory( outDirectory, name );
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
if( newModFolder.Length == 0 )
{
throw new IOException( "Could not create mod folder: too many folders of the same name exist." );
}
if( create )
{
Directory.CreateDirectory( newModFolder );
}
return new DirectoryInfo( newModFolder );
}
/// <summary>
/// Create the name for a group or option subfolder based on its parent folder and given name.
/// subFolderName should never be empty, and the result is unique and contains no invalid symbols.
/// </summary>
public static DirectoryInfo? NewSubFolderName( DirectoryInfo parentFolder, string subFolderName )
{
var newModFolderBase = NewOptionDirectory( parentFolder, subFolderName );
var newModFolder = newModFolderBase.FullName.ObtainUniqueFile();
return newModFolder.Length == 0 ? null : new DirectoryInfo( newModFolder );
}
/// <summary> Create the file containing the meta information about a mod from scratch. </summary>
public static void CreateMeta( DirectoryInfo directory, string? name, string? author, string? description, string? version,
string? website )
{
var mod = new Mod( directory );
mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString( name! );
mod.Author = author != null ? new LowerString( author ) : mod.Author;
mod.Description = description ?? mod.Description;
mod.Version = version ?? mod.Version;
mod.Website = website ?? mod.Website;
mod.SaveMetaFile(); // Not delayed.
}
/// <summary> Create a file for an option group from given data. </summary>
public static void CreateOptionGroup( DirectoryInfo baseFolder, GroupType type, string name,
int priority, int index, uint defaultSettings, string desc, IEnumerable< ISubMod > subMods )
{
switch( type )
{
case GroupType.Multi:
{
var group = new MultiModGroup()
{
Name = name,
Description = desc,
Priority = priority,
DefaultSettings = defaultSettings,
};
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
IModGroup.Save( group, baseFolder, index );
break;
}
case GroupType.Single:
{
var group = new SingleModGroup()
{
Name = name,
Description = desc,
Priority = priority,
DefaultSettings = defaultSettings,
};
group.OptionData.AddRange( subMods.OfType< SubMod >() );
IModGroup.Save( group, baseFolder, index );
break;
}
}
}
/// <summary> Create the data for a given sub mod from its data and the folder it is based on. </summary>
public static ISubMod CreateSubMod( DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option )
{
var list = optionFolder.EnumerateFiles( "*.*", SearchOption.AllDirectories )
.Select( f => ( Utf8GamePath.FromFile( f, optionFolder, out var gamePath, true ), gamePath, new FullPath( f ) ) )
.Where( t => t.Item1 );
var mod = new SubMod( null! ) // Mod is irrelevant here, only used for saving.
{
Name = option.Name,
Description = option.Description,
};
foreach( var (_, gamePath, file) in list )
{
mod.FileData.TryAdd( gamePath, file );
}
mod.IncorporateMetaChanges( baseFolder, true );
return mod;
}
/// <summary> Create an empty sub mod for single groups with None options. </summary>
internal static ISubMod CreateEmptySubMod( string name )
=> new SubMod( null! ) // Mod is irrelevant here, only used for saving.
{
Name = name,
};
/// <summary>
/// Create the default data file from all unused files that were not handled before
/// and are used in sub mods.
/// </summary>
internal static void CreateDefaultFiles( DirectoryInfo directory )
{
var mod = new Mod( directory );
mod.Reload( false, out _ );
foreach( var file in mod.FindUnusedFiles() )
{
if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) )
{
mod._default.FileData.TryAdd( gamePath, file );
}
}
mod._default.IncorporateMetaChanges( directory, true );
mod.SaveDefaultMod();
}
/// <summary> Return the name of a new valid directory based on the base directory and the given name. </summary>
public static DirectoryInfo NewOptionDirectory( DirectoryInfo baseDir, string optionName )
=> new(Path.Combine( baseDir.FullName, ReplaceBadXivSymbols( optionName ) ));
/// <summary> Normalize for nicer names, and remove invalid symbols or invalid paths. </summary>
public static string ReplaceBadXivSymbols( string s, string replacement = "_" )
{
switch( s )
{
case ".": return replacement;
case "..": return replacement + replacement;
}
StringBuilder sb = new(s.Length);
foreach( var c in s.Normalize( NormalizationForm.FormKC ) )
{
if( c.IsInvalidInPath() )
{
sb.Append( replacement );
}
else
{
sb.Append( c );
}
}
return sb.ToString();
}
}
}

View file

@ -51,9 +51,9 @@ public sealed partial class Mod
DirectoryInfo? dir = null; DirectoryInfo? dir = null;
try try
{ {
dir = CreateModFolder( Penumbra.ModManager.BasePath, collection.Name ); dir = Creator.CreateModFolder( Penumbra.ModManager.BasePath, collection.Name );
var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) ); var fileDir = Directory.CreateDirectory( Path.Combine( dir.FullName, "files" ) );
CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor, Creator.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor,
$"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null ); $"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null );
var mod = new Mod( dir ); var mod = new Mod( dir );
var defaultMod = mod._default; var defaultMod = mod._default;

View file

@ -3,11 +3,13 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Internal.Notifications;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Util;
namespace Penumbra.Mods; namespace Penumbra.Mods;
@ -63,8 +65,7 @@ public partial class Mod
{ {
if( ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions ) if( ret.PrioritizedOptions.Count == IModGroup.MaxMultiOptions )
{ {
Penumbra.Log.Warning( ChatUtil.NotificationMessage( $"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options.", "Warning", NotificationType.Warning );
$"Multi Group {ret.Name} has more than {IModGroup.MaxMultiOptions} options, ignoring excessive options." );
break; break;
} }

View file

@ -67,7 +67,7 @@ public partial class Mod
_default.WriteTexToolsMeta( ModPath ); _default.WriteTexToolsMeta( ModPath );
foreach( var group in Groups ) foreach( var group in Groups )
{ {
var dir = NewOptionDirectory( ModPath, group.Name ); var dir = Creator.NewOptionDirectory( ModPath, group.Name );
if( !dir.Exists ) if( !dir.Exists )
{ {
dir.Create(); dir.Create();
@ -75,7 +75,7 @@ public partial class Mod
foreach( var option in group.OfType< SubMod >() ) foreach( var option in group.OfType< SubMod >() )
{ {
var optionDir = NewOptionDirectory( dir, option.Name ); var optionDir = Creator.NewOptionDirectory( dir, option.Name );
if( !optionDir.Exists ) if( !optionDir.Exists )
{ {
optionDir.Create(); optionDir.Create();

View file

@ -216,9 +216,9 @@ public class ItemSwapWindow : IDisposable
private void CreateMod() private void CreateMod()
{ {
var newDir = Mod.CreateModFolder( Penumbra.ModManager.BasePath, _newModName ); var newDir = Mod.Creator.CreateModFolder( Penumbra.ModManager.BasePath, _newModName );
Mod.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty ); Mod.Creator.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty );
Mod.CreateDefaultFiles( newDir ); Mod.Creator.CreateDefaultFiles( newDir );
Penumbra.ModManager.AddMod( newDir ); Penumbra.ModManager.AddMod( newDir );
if( !_swapData.WriteMod( Penumbra.ModManager.Last(), _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps ) ) if( !_swapData.WriteMod( Penumbra.ModManager.Last(), _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps ) )
{ {
@ -239,7 +239,7 @@ public class ItemSwapWindow : IDisposable
DirectoryInfo? optionFolderName = null; DirectoryInfo? optionFolderName = null;
try try
{ {
optionFolderName = Mod.NewSubFolderName( new DirectoryInfo( Path.Combine( _mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName ) ), _newOptionName ); optionFolderName = Mod.Creator.NewSubFolderName( new DirectoryInfo( Path.Combine( _mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName ) ), _newOptionName );
if( optionFolderName?.Exists == true ) if( optionFolderName?.Exists == true )
{ {
throw new Exception( $"The folder {optionFolderName.FullName} for the option already exists." ); throw new Exception( $"The folder {optionFolderName.FullName} for the option already exists." );

View file

@ -92,9 +92,9 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
{ {
try try
{ {
var newDir = Mod.CreateModFolder( Penumbra.ModManager.BasePath, _newModName ); var newDir = Mod.Creator.CreateModFolder( Penumbra.ModManager.BasePath, _newModName );
Mod.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty ); Mod.Creator.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty );
Mod.CreateDefaultFiles( newDir ); Mod.Creator.CreateDefaultFiles( newDir );
Penumbra.ModManager.AddMod( newDir ); Penumbra.ModManager.AddMod( newDir );
_newModName = string.Empty; _newModName = string.Empty;
} }