Add Filesystem Compression as a toggle and button. Also some auto-formatting.

This commit is contained in:
Ottermandias 2023-09-14 17:23:54 +02:00
parent e26873934b
commit 4e704770cb
21 changed files with 385 additions and 344 deletions

View file

@ -17,7 +17,7 @@ public partial class TexToolsImporter
private DirectoryInfo? _currentModDirectory;
// Version 1 mod packs are a simple collection of files without much information.
private DirectoryInfo ImportV1ModPack( FileInfo modPackFile, ZipArchive extractedModPack, string modRaw )
private DirectoryInfo ImportV1ModPack(FileInfo modPackFile, ZipArchive extractedModPack, string modRaw)
{
_currentOptionIdx = 0;
_currentNumOptions = 1;
@ -25,174 +25,171 @@ public partial class TexToolsImporter
_currentGroupName = string.Empty;
_currentOptionName = DefaultTexToolsData.DefaultOption;
Penumbra.Log.Information( " -> Importing V1 ModPack" );
Penumbra.Log.Information(" -> Importing V1 ModPack");
var modListRaw = modRaw.Split(
new[] { "\r\n", "\r", "\n" },
new[]
{
"\r\n",
"\r",
"\n",
},
StringSplitOptions.RemoveEmptyEntries
);
var modList = modListRaw.Select( m => JsonConvert.DeserializeObject< SimpleMod >( m, JsonSettings )! ).ToList();
var modList = modListRaw.Select(m => JsonConvert.DeserializeObject<SimpleMod>(m, JsonSettings)!).ToList();
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, Path.GetFileNameWithoutExtension( modPackFile.Name ) );
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, Path.GetFileNameWithoutExtension(modPackFile.Name));
// Create a new ModMeta from the TTMP mod list info
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description, null, null );
_modManager.DataEditor.CreateMeta(_currentModDirectory, _currentModName, DefaultTexToolsData.Author, DefaultTexToolsData.Description,
null, null);
// Open the mod data file from the mod pack as a SqPackStream
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
ExtractSimpleModList( _currentModDirectory, modList );
_modManager.Creator.CreateDefaultFiles( _currentModDirectory );
_streamDisposer = GetSqPackStreamStream(extractedModPack, "TTMPD.mpd");
ExtractSimpleModList(_currentModDirectory, modList);
_modManager.Creator.CreateDefaultFiles(_currentModDirectory);
ResetStreamDisposer();
return _currentModDirectory;
}
// Version 2 mod packs can either be simple or extended, import accordingly.
private DirectoryInfo ImportV2ModPack( FileInfo _, ZipArchive extractedModPack, string modRaw )
private DirectoryInfo ImportV2ModPack(FileInfo _, ZipArchive extractedModPack, string modRaw)
{
var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw, JsonSettings )!;
var modList = JsonConvert.DeserializeObject<SimpleModPack>(modRaw, JsonSettings)!;
if( modList.TtmpVersion.EndsWith( "s" ) )
{
return ImportSimpleV2ModPack( extractedModPack, modList );
}
if (modList.TtmpVersion.EndsWith("s"))
return ImportSimpleV2ModPack(extractedModPack, modList);
if( modList.TtmpVersion.EndsWith( "w" ) )
{
return ImportExtendedV2ModPack( extractedModPack, modRaw );
}
if (modList.TtmpVersion.EndsWith("w"))
return ImportExtendedV2ModPack(extractedModPack, modRaw);
try
{
Penumbra.Log.Warning( $"Unknown TTMPVersion <{modList.TtmpVersion}> given, trying to export as simple mod pack." );
return ImportSimpleV2ModPack( extractedModPack, modList );
Penumbra.Log.Warning($"Unknown TTMPVersion <{modList.TtmpVersion}> given, trying to export as simple mod pack.");
return ImportSimpleV2ModPack(extractedModPack, modList);
}
catch( Exception e1 )
catch (Exception e1)
{
Penumbra.Log.Warning( $"Exporting as simple mod pack failed with following error, retrying as extended mod pack:\n{e1}" );
Penumbra.Log.Warning($"Exporting as simple mod pack failed with following error, retrying as extended mod pack:\n{e1}");
try
{
return ImportExtendedV2ModPack( extractedModPack, modRaw );
return ImportExtendedV2ModPack(extractedModPack, modRaw);
}
catch( Exception e2 )
catch (Exception e2)
{
throw new IOException( "Exporting as extended mod pack failed, too. Version unsupported or file defect.", e2 );
throw new IOException("Exporting as extended mod pack failed, too. Version unsupported or file defect.", e2);
}
}
}
// Simple V2 mod packs are basically the same as V1 mod packs.
private DirectoryInfo ImportSimpleV2ModPack( ZipArchive extractedModPack, SimpleModPack modList )
private DirectoryInfo ImportSimpleV2ModPack(ZipArchive extractedModPack, SimpleModPack modList)
{
_currentOptionIdx = 0;
_currentNumOptions = 1;
_currentModName = modList.Name;
_currentGroupName = string.Empty;
_currentOptionName = DefaultTexToolsData.DefaultOption;
Penumbra.Log.Information( " -> Importing Simple V2 ModPack" );
Penumbra.Log.Information(" -> Importing Simple V2 ModPack");
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, _currentModName );
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty( modList.Description )
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, _currentModName);
_modManager.DataEditor.CreateMeta(_currentModDirectory, _currentModName, modList.Author, string.IsNullOrEmpty(modList.Description)
? "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
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList );
_modManager.Creator.CreateDefaultFiles( _currentModDirectory );
_streamDisposer = GetSqPackStreamStream(extractedModPack, "TTMPD.mpd");
ExtractSimpleModList(_currentModDirectory, modList.SimpleModsList);
_modManager.Creator.CreateDefaultFiles(_currentModDirectory);
ResetStreamDisposer();
return _currentModDirectory;
}
// Obtain the number of relevant options to extract.
private static int GetOptionCount( ExtendedModPack pack )
=> ( pack.SimpleModsList.Length > 0 ? 1 : 0 )
private static int GetOptionCount(ExtendedModPack pack)
=> (pack.SimpleModsList.Length > 0 ? 1 : 0)
+ pack.ModPackPages
.Sum( page => page.ModGroups
.Where( g => g.GroupName.Length > 0 && g.OptionList.Length > 0 )
.Sum( group => group.OptionList
.Count( o => o.Name.Length > 0 && o.ModsJsons.Length > 0 )
+ ( group.OptionList.Any( o => o.Name.Length > 0 && o.ModsJsons.Length == 0 ) ? 1 : 0 ) ) );
.Sum(page => page.ModGroups
.Where(g => g.GroupName.Length > 0 && g.OptionList.Length > 0)
.Sum(group => group.OptionList
.Count(o => o.Name.Length > 0 && o.ModsJsons.Length > 0)
+ (group.OptionList.Any(o => o.Name.Length > 0 && o.ModsJsons.Length == 0) ? 1 : 0)));
private static string GetGroupName( string groupName, ISet< string > names )
private static string GetGroupName(string groupName, ISet<string> names)
{
var baseName = groupName;
var i = 2;
while( !names.Add( groupName ) )
{
while (!names.Add(groupName))
groupName = $"{baseName} ({i++})";
}
return groupName;
}
// Extended V2 mod packs contain multiple options that need to be handled separately.
private DirectoryInfo ImportExtendedV2ModPack( ZipArchive extractedModPack, string modRaw )
private DirectoryInfo ImportExtendedV2ModPack(ZipArchive extractedModPack, string modRaw)
{
_currentOptionIdx = 0;
Penumbra.Log.Information( " -> Importing Extended V2 ModPack" );
Penumbra.Log.Information(" -> Importing Extended V2 ModPack");
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( modRaw, JsonSettings )!;
_currentNumOptions = GetOptionCount( modList );
var modList = JsonConvert.DeserializeObject<ExtendedModPack>(modRaw, JsonSettings)!;
_currentNumOptions = GetOptionCount(modList);
_currentModName = modList.Name;
_currentModDirectory = ModCreator.CreateModFolder( _baseDirectory, _currentModName );
_modManager.DataEditor.CreateMeta( _currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version, modList.Url );
_currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, _currentModName);
_modManager.DataEditor.CreateMeta(_currentModDirectory, _currentModName, modList.Author, modList.Description, modList.Version,
modList.Url);
if( _currentNumOptions == 0 )
{
if (_currentNumOptions == 0)
return _currentModDirectory;
}
// Open the mod data file from the mod pack as a SqPackStream
_streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" );
_streamDisposer = GetSqPackStreamStream(extractedModPack, "TTMPD.mpd");
// It can contain a simple list, still.
if( modList.SimpleModsList.Length > 0 )
if (modList.SimpleModsList.Length > 0)
{
_currentGroupName = string.Empty;
_currentOptionName = "Default";
ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList );
ExtractSimpleModList(_currentModDirectory, modList.SimpleModsList);
}
// Iterate through all pages
var options = new List< ISubMod >();
var options = new List<ISubMod>();
var groupPriority = 0;
var groupNames = new HashSet< string >();
foreach( var page in modList.ModPackPages )
var groupNames = new HashSet<string>();
foreach (var page in modList.ModPackPages)
{
foreach( var group in page.ModGroups.Where( group => group.GroupName.Length > 0 && group.OptionList.Length > 0 ) )
foreach (var group in page.ModGroups.Where(group => group.GroupName.Length > 0 && group.OptionList.Length > 0))
{
var allOptions = group.OptionList.Where( option => option.Name.Length > 0 && option.ModsJsons.Length > 0 ).ToList();
var allOptions = group.OptionList.Where(option => option.Name.Length > 0 && option.ModsJsons.Length > 0).ToList();
var (numGroups, maxOptions) = group.SelectionType == GroupType.Single
? ( 1, allOptions.Count )
: ( 1 + allOptions.Count / IModGroup.MaxMultiOptions, IModGroup.MaxMultiOptions );
_currentGroupName = GetGroupName( group.GroupName, groupNames );
? (1, allOptions.Count)
: (1 + allOptions.Count / IModGroup.MaxMultiOptions, IModGroup.MaxMultiOptions);
_currentGroupName = GetGroupName(group.GroupName, groupNames);
var optionIdx = 0;
for( var groupId = 0; groupId < numGroups; ++groupId )
var optionIdx = 0;
for (var groupId = 0; groupId < numGroups; ++groupId)
{
var name = numGroups == 1 ? _currentGroupName : $"{_currentGroupName}, Part {groupId + 1}";
var name = numGroups == 1 ? _currentGroupName : $"{_currentGroupName}, Part {groupId + 1}";
options.Clear();
var groupFolder = ModCreator.NewSubFolderName( _currentModDirectory, name )
?? new DirectoryInfo( Path.Combine( _currentModDirectory.FullName,
numGroups == 1 ? $"Group {groupPriority + 1}" : $"Group {groupPriority + 1}, Part {groupId + 1}" ) );
var groupFolder = ModCreator.NewSubFolderName(_currentModDirectory, name)
?? new DirectoryInfo(Path.Combine(_currentModDirectory.FullName,
numGroups == 1 ? $"Group {groupPriority + 1}" : $"Group {groupPriority + 1}, Part {groupId + 1}"));
uint? defaultSettings = group.SelectionType == GroupType.Multi ? 0u : null;
for( var i = 0; i + optionIdx < allOptions.Count && i < maxOptions; ++i )
for (var i = 0; i + optionIdx < allOptions.Count && i < maxOptions; ++i)
{
var option = allOptions[ i + optionIdx ];
var option = allOptions[i + optionIdx];
_token.ThrowIfCancellationRequested();
_currentOptionName = option.Name;
var optionFolder = ModCreator.NewSubFolderName( groupFolder, option.Name )
?? new DirectoryInfo( Path.Combine( groupFolder.FullName, $"Option {i + optionIdx + 1}" ) );
ExtractSimpleModList( optionFolder, option.ModsJsons );
options.Add( _modManager.Creator.CreateSubMod( _currentModDirectory, optionFolder, option ) );
if( option.IsChecked )
{
var optionFolder = ModCreator.NewSubFolderName(groupFolder, option.Name)
?? new DirectoryInfo(Path.Combine(groupFolder.FullName, $"Option {i + optionIdx + 1}"));
ExtractSimpleModList(optionFolder, option.ModsJsons);
options.Add(_modManager.Creator.CreateSubMod(_currentModDirectory, optionFolder, option));
if (option.IsChecked)
defaultSettings = group.SelectionType == GroupType.Multi
? ( defaultSettings!.Value | ( 1u << i ) )
: ( uint )i;
}
? defaultSettings!.Value | (1u << i)
: (uint)i;
++_currentOptionIdx;
}
@ -201,30 +198,30 @@ public partial class TexToolsImporter
// Handle empty options for single select groups without creating a folder for them.
// We only want one of those at most, and it should usually be the first option.
if( group.SelectionType == GroupType.Single )
if (group.SelectionType == GroupType.Single)
{
var empty = group.OptionList.FirstOrDefault( o => o.Name.Length > 0 && o.ModsJsons.Length == 0 );
if( empty != null )
var empty = group.OptionList.FirstOrDefault(o => o.Name.Length > 0 && o.ModsJsons.Length == 0);
if (empty != null)
{
_currentOptionName = empty.Name;
options.Insert( 0, ModCreator.CreateEmptySubMod( empty.Name ) );
options.Insert(0, ModCreator.CreateEmptySubMod(empty.Name));
defaultSettings = defaultSettings == null ? 0 : defaultSettings.Value + 1;
}
}
_modManager.Creator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority,
defaultSettings ?? 0, group.Description, options );
_modManager.Creator.CreateOptionGroup(_currentModDirectory, group.SelectionType, name, groupPriority, groupPriority,
defaultSettings ?? 0, group.Description, options);
++groupPriority;
}
}
}
ResetStreamDisposer();
_modManager.Creator.CreateDefaultFiles( _currentModDirectory );
_modManager.Creator.CreateDefaultFiles(_currentModDirectory);
return _currentModDirectory;
}
private void ExtractSimpleModList( DirectoryInfo outDirectory, ICollection< SimpleMod > mods )
private void ExtractSimpleModList(DirectoryInfo outDirectory, ICollection<SimpleMod> mods)
{
State = ImporterState.ExtractingModFiles;
@ -232,51 +229,47 @@ public partial class TexToolsImporter
_currentNumFiles = mods.Count(m => m.FullPath.Length > 0);
// Extract each SimpleMod into the new mod folder
foreach( var simpleMod in mods.Where(m => m.FullPath.Length > 0 ) )
foreach (var simpleMod in mods.Where(m => m.FullPath.Length > 0))
{
ExtractMod( outDirectory, simpleMod );
ExtractMod(outDirectory, simpleMod);
++_currentFileIdx;
}
}
private void ExtractMod( DirectoryInfo outDirectory, SimpleMod mod )
private void ExtractMod(DirectoryInfo outDirectory, SimpleMod mod)
{
if( _streamDisposer is not PenumbraSqPackStream stream )
{
if (_streamDisposer is not PenumbraSqPackStream stream)
return;
}
Penumbra.Log.Information( $" -> Extracting {mod.FullPath} at {mod.ModOffset:X}" );
Penumbra.Log.Information($" -> Extracting {mod.FullPath} at {mod.ModOffset:X}");
_token.ThrowIfCancellationRequested();
var data = stream.ReadFile< PenumbraSqPackStream.PenumbraFileResource >( mod.ModOffset );
var data = stream.ReadFile<PenumbraSqPackStream.PenumbraFileResource>(mod.ModOffset);
_currentFileName = mod.FullPath;
var extractedFile = new FileInfo( Path.Combine( outDirectory.FullName, mod.FullPath ) );
var extractedFile = new FileInfo(Path.Combine(outDirectory.FullName, mod.FullPath));
extractedFile.Directory?.Create();
if( extractedFile.FullName.EndsWith( ".mdl" ) )
{
ProcessMdl( data.Data );
}
if (extractedFile.FullName.EndsWith(".mdl"))
ProcessMdl(data.Data);
File.WriteAllBytes( extractedFile.FullName, data.Data );
_compactor.WriteAllBytesAsync(extractedFile.FullName, data.Data, _token).Wait(_token);
}
private static void ProcessMdl( byte[] mdl )
private static void ProcessMdl(byte[] mdl)
{
const int modelHeaderLodOffset = 22;
// Model file header LOD num
mdl[ 64 ] = 1;
mdl[64] = 1;
// Model header LOD num
var stackSize = BitConverter.ToUInt32( mdl, 4 );
var runtimeBegin = stackSize + 0x44;
var stackSize = BitConverter.ToUInt32(mdl, 4);
var runtimeBegin = stackSize + 0x44;
var stringsLengthOffset = runtimeBegin + 4;
var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset );
var stringsLength = BitConverter.ToUInt32(mdl, (int)stringsLengthOffset);
var modelHeaderStart = stringsLengthOffset + stringsLength + 4;
mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1;
mdl[modelHeaderStart + modelHeaderLodOffset] = 1;
}
}
}