mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
161 lines
No EOL
5.8 KiB
C#
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();
|
|
}
|
|
} |