Penumbra/Penumbra/Mods/Mod.Creation.cs

161 lines
No EOL
5.8 KiB
C#

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Dalamud.Utility;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.GameData.ByteString;
using Penumbra.Import;
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.
internal static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName )
{
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." );
}
Directory.CreateDirectory( newModFolder );
return new DirectoryInfo( newModFolder );
}
// 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.
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, SelectType type, string name,
int priority, int index, string desc, IEnumerable< ISubMod > subMods )
{
switch( type )
{
case SelectType.Multi:
{
var group = new MultiModGroup()
{
Name = name,
Description = desc,
Priority = priority,
};
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
IModGroup.Save( group, baseFolder, index );
break;
}
case SelectType.Single:
{
var group = new SingleModGroup()
{
Name = name,
Description = desc,
Priority = priority,
};
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
{
Name = option.Name,
};
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()
{
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 );
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 ) ));
// XIV can not deal with non-ascii symbols in a path,
// and the path must obviously be valid itself.
public static string ReplaceBadXivSymbols( string s, string replacement = "_" )
{
StringBuilder sb = new(s.Length);
foreach( var c in s )
{
if( c.IsInvalidAscii() || c.IsInvalidInPath() )
{
sb.Append( replacement );
}
else
{
sb.Append( c );
}
}
return sb.ToString();
}
}