@@ -50,6 +50,7 @@ public class TTMP
50
50
51
51
private readonly string _currentWizardTTMPVersion = "1.3w" ;
52
52
private readonly string _currentSimpleTTMPVersion = "1.3s" ;
53
+ private readonly string _currentBackupTTMPVersion = "1.0b" ;
53
54
private const string _minimumAssembly = "1.3.0.0" ;
54
55
55
56
private string _tempMPD , _tempMPL , _source ;
@@ -335,6 +336,122 @@ public async Task<int> CreateSimpleModPack(SimpleModPackData modPackData, Direct
335
336
return processCount ;
336
337
}
337
338
339
+ /// <summary>
340
+ /// Creates a backup modpack which retains the original modpacks on import
341
+ /// </summary>
342
+ /// <param name="backupModpackData">The data that will go into the mod pack</param>
343
+ /// <param name="gameDirectory">The game directory</param>
344
+ /// <param name="progress">The progress of the mod pack creation</param>
345
+ /// <param name="overwriteModpack">Whether or not to overwrite an existing modpack with the same name</param>
346
+ /// <returns>The number of mods processed for the mod pack</returns>
347
+ public async Task < int > CreateBackupModpack ( BackupModPackData backupModpackData , DirectoryInfo gameDirectory , IProgress < ( int current , int total , string message ) > progress , bool overwriteModpack )
348
+ {
349
+ var processCount = await Task . Run < int > ( ( ) =>
350
+ {
351
+ var dat = new Dat ( gameDirectory ) ;
352
+
353
+ var guid = Guid . NewGuid ( ) ;
354
+
355
+ var dir = Path . Combine ( Path . GetTempPath ( ) , guid . ToString ( ) ) ;
356
+ Directory . CreateDirectory ( dir ) ;
357
+
358
+
359
+ _tempMPD = Path . Combine ( dir , "TTMPD.mpd" ) ;
360
+ _tempMPL = Path . Combine ( dir , "TTMPL.mpl" ) ;
361
+
362
+ var modCount = 0 ;
363
+
364
+ var modPackJson = new ModPackJson
365
+ {
366
+ TTMPVersion = _currentBackupTTMPVersion ,
367
+ Name = backupModpackData . Name ,
368
+ Author = backupModpackData . Author ,
369
+ Version = backupModpackData . Version . ToString ( ) ,
370
+ MinimumFrameworkVersion = _minimumAssembly ,
371
+ Url = backupModpackData . Url ,
372
+ Description = backupModpackData . Description ,
373
+ SimpleModsList = new List < ModsJson > ( )
374
+ } ;
375
+
376
+ try
377
+ {
378
+ using ( var binaryWriter = new BinaryWriter ( File . Open ( _tempMPD , FileMode . Create ) ) )
379
+ {
380
+ foreach ( var backupModData in backupModpackData . ModsToBackup )
381
+ {
382
+ if ( ForbiddenModTypes . Contains ( Path . GetExtension ( backupModData . SimpleModData . FullPath ) ) ) continue ;
383
+
384
+ var modsJson = new ModsJson
385
+ {
386
+ Name = backupModData . SimpleModData . Name ,
387
+ Category = backupModData . SimpleModData . Category . GetEnDisplayName ( ) ,
388
+ FullPath = backupModData . SimpleModData . FullPath ,
389
+ ModSize = backupModData . SimpleModData . ModSize ,
390
+ DatFile = backupModData . SimpleModData . DatFile ,
391
+ IsDefault = backupModData . SimpleModData . IsDefault ,
392
+ ModOffset = binaryWriter . BaseStream . Position ,
393
+ ModPackEntry = backupModData . ModPack ,
394
+ } ;
395
+
396
+ var rawData = dat . GetRawData ( backupModData . SimpleModData . ModOffset ,
397
+ XivDataFiles . GetXivDataFile ( backupModData . SimpleModData . DatFile ) ,
398
+ backupModData . SimpleModData . ModSize ) ;
399
+
400
+ if ( rawData == null )
401
+ {
402
+ throw new Exception ( "Unable to obtain data for the following mod\n \n " +
403
+ $ "Name: { backupModData . SimpleModData . Name } \n Full Path: { backupModData . SimpleModData . FullPath } \n " +
404
+ $ "Mod Offset: { backupModData . SimpleModData . ModOffset } \n Data File: { backupModData . SimpleModData . DatFile } \n \n " +
405
+ $ "Unselect the above mod and try again.") ;
406
+ }
407
+
408
+ binaryWriter . Write ( rawData ) ;
409
+
410
+ modPackJson . SimpleModsList . Add ( modsJson ) ;
411
+
412
+ progress ? . Report ( ( ++ modCount , backupModpackData . ModsToBackup . Count , string . Empty ) ) ;
413
+ }
414
+ }
415
+
416
+ progress ? . Report ( ( 0 , backupModpackData . ModsToBackup . Count , GeneralStrings . TTMP_Creating ) ) ;
417
+
418
+ File . WriteAllText ( _tempMPL , JsonConvert . SerializeObject ( modPackJson ) ) ;
419
+
420
+ var modPackPath = Path . Combine ( _modPackDirectory . FullName , $ "{ backupModpackData . Name } .ttmp2") ;
421
+
422
+ if ( File . Exists ( modPackPath ) && ! overwriteModpack )
423
+ {
424
+ var fileNum = 1 ;
425
+ modPackPath = Path . Combine ( _modPackDirectory . FullName , $ "{ backupModpackData . Name } ({ fileNum } ).ttmp2") ;
426
+ while ( File . Exists ( modPackPath ) )
427
+ {
428
+ fileNum ++ ;
429
+ modPackPath = Path . Combine ( _modPackDirectory . FullName , $ "{ backupModpackData . Name } ({ fileNum } ).ttmp2") ;
430
+ }
431
+ }
432
+ else if ( File . Exists ( modPackPath ) && overwriteModpack )
433
+ {
434
+ File . Delete ( modPackPath ) ;
435
+ }
436
+
437
+ var zf = new ZipFile ( ) ;
438
+ zf . UseZip64WhenSaving = Zip64Option . AsNecessary ;
439
+ zf . CompressionLevel = Ionic . Zlib . CompressionLevel . None ;
440
+ zf . AddFile ( _tempMPL , "" ) ;
441
+ zf . AddFile ( _tempMPD , "" ) ;
442
+ zf . Save ( modPackPath ) ;
443
+ }
444
+ finally
445
+ {
446
+ Directory . Delete ( dir , true ) ;
447
+ }
448
+
449
+ return modCount ;
450
+ } ) ;
451
+
452
+ return processCount ;
453
+ }
454
+
338
455
/// <summary>
339
456
/// Gets the data from a mod pack including images if present
340
457
/// </summary>
@@ -461,8 +578,9 @@ public static string GetVersion(DirectoryInfo modPackDirectory)
461
578
/// <param name="progress">The progress of the import</param>
462
579
/// <param name="GetRootConversionsFunction">Function called part-way through import to resolve rood conversions, if any are desired. Function takes a List of files, the in-progress modified index and modlist files, and returns a dictionary of conversion data. If this function throws and OperationCancelledException, the import is cancelled.</param>
463
580
/// <returns>The number of total mods imported</returns>
464
- public async Task < ( int ImportCount , int ErrorCount , string Errors , float Duration ) > ImportModPackAsync ( DirectoryInfo modPackDirectory , List < ModsJson > modsJson ,
465
- DirectoryInfo gameDirectory , DirectoryInfo modListDirectory , IProgress < ( int current , int total , string message ) > progress , Func < HashSet < string > , Dictionary < XivDataFile , IndexFile > , ModList , Task < Dictionary < XivDependencyRoot , ( XivDependencyRoot Root , int Variant ) > > > GetRootConversionsFunction = null )
581
+ public async Task < ( int ImportCount , int ErrorCount , string Errors , float Duration ) > ImportModPackAsync (
582
+ DirectoryInfo modPackDirectory , List < ModsJson > modsJson , DirectoryInfo gameDirectory , DirectoryInfo modListDirectory , IProgress < ( int current , int total , string message ) > progress ,
583
+ Func < HashSet < string > , Dictionary < XivDataFile , IndexFile > , ModList , Task < Dictionary < XivDependencyRoot , ( XivDependencyRoot Root , int Variant ) > > > GetRootConversionsFunction = null )
466
584
{
467
585
if ( modsJson == null || modsJson . Count == 0 ) return ( 0 , 0 , "" , 0 ) ;
468
586
@@ -490,7 +608,7 @@ public static string GetVersion(DirectoryInfo modPackDirectory)
490
608
// the *LAST* mod json entry for each file path.
491
609
// This keeps us from having to constantly re-query the mod list file, and filters out redundant imports.
492
610
var filePaths = new HashSet < string > ( ) ;
493
- var newList = new List < ModsJson > ( modsJson . Count ) ;
611
+ var filteredModsJson = new List < ModsJson > ( modsJson . Count ) ;
494
612
for ( int i = modsJson . Count - 1 ; i >= 0 ; i -- )
495
613
{
496
614
var mj = modsJson [ i ] ;
@@ -504,11 +622,10 @@ public static string GetVersion(DirectoryInfo modPackDirectory)
504
622
if ( ForbiddenModTypes . Contains ( Path . GetExtension ( mj . FullPath ) ) ) continue ;
505
623
506
624
filePaths . Add ( mj . FullPath ) ;
507
- newList . Add ( mj ) ;
625
+ filteredModsJson . Add ( mj ) ;
508
626
}
509
- modsJson = newList ;
510
627
511
- if ( modsJson . Count == 0 )
628
+ if ( filteredModsJson . Count == 0 )
512
629
{
513
630
return ( 0 , 0 , "" , 0 ) ;
514
631
}
@@ -577,7 +694,7 @@ await Task.Run(async () =>
577
694
progress . Report ( ( 0 , 0 , "Writing new mod data to DAT files..." ) ) ;
578
695
using ( var binaryReader = new BinaryReader ( new FileStream ( _tempMPD , FileMode . Open ) ) )
579
696
{
580
- foreach ( var modJson in modsJson )
697
+ foreach ( var modJson in filteredModsJson )
581
698
{
582
699
try
583
700
{
@@ -598,7 +715,7 @@ await Task.Run(async () =>
598
715
Mod mod = null ;
599
716
if ( modsByFile . ContainsKey ( modJson . FullPath ) )
600
717
{
601
- mod = modsByFile [ modJson . FullPath ] ;
718
+ mod = modsByFile [ modJson . FullPath ] ;
602
719
}
603
720
604
721
// Always write data to end of file during modpack imports in case we need
@@ -669,17 +786,25 @@ await Task.Run(async () =>
669
786
progress . Report ( ( count , totalFiles , "Updating Index file references..." ) ) ;
670
787
}
671
788
789
+ // Add entries for new modpacks to the mod list
790
+ foreach ( var modsJson in filteredModsJson )
791
+ {
792
+ if ( modsJson . ModPackEntry == null ) continue ;
672
793
673
- var modPackExists = modList . ModPacks . Any ( modpack => modpack . name == modsJson [ 0 ] . ModPackEntry . name ) ;
794
+ var modPackExists = modList . ModPacks . Any ( modpack => modpack . name == modsJson . ModPackEntry . name ) ;
674
795
675
- if ( ! modPackExists )
676
- {
677
- modList . ModPacks . Add ( modsJson [ 0 ] . ModPackEntry ) ;
796
+ if ( ! modPackExists )
797
+ {
798
+ modList . ModPacks . Add ( modsJson . ModPackEntry ) ;
799
+ }
678
800
}
679
- var modPack = modList . ModPacks . First ( x => x . name == modsJson [ 0 ] . ModPackEntry . name ) ;
801
+
680
802
681
803
if ( GetRootConversionsFunction != null )
682
804
{
805
+ // Get the modpack to list the conversions under, this is the just the modpack entry of the first modsJson since they're all the same unless it's a backup
806
+ // However, this code shouldn't be used when importing backup modpacks since they already had the choice to change the destination item after the initial import
807
+ var modPack = modList . ModPacks . First ( x => x . name == filteredModsJson [ 0 ] . ModPackEntry ? . name ) ;
683
808
684
809
Dictionary < XivDependencyRoot , ( XivDependencyRoot Root , int Variant ) > rootConversions = null ;
685
810
try
@@ -730,7 +855,7 @@ await Task.Run(async () =>
730
855
foreach ( var fileKv in convertedFiles )
731
856
{
732
857
// Remove the file from our json list, the conversion already handled everything we needed to do with it.
733
- var json = modsJson . RemoveAll ( x => x . FullPath == fileKv . Key ) ;
858
+ var json = filteredModsJson . RemoveAll ( x => x . FullPath == fileKv . Key ) ;
734
859
735
860
if ( fileKv . Key != fileKv . Value )
736
861
{
@@ -784,15 +909,15 @@ await Task.Run(async () =>
784
909
785
910
// Update the Mod List file.
786
911
787
-
788
912
foreach ( var file in filePaths )
789
913
{
790
914
if ( ErroneousFiles . Contains ( file ) ) continue ;
791
915
try
792
916
{
793
- var json = modsJson . FirstOrDefault ( x => x . FullPath == file ) ;
917
+ var json = filteredModsJson . FirstOrDefault ( x => x . FullPath == file ) ;
794
918
if ( json == null ) continue ;
795
919
920
+
796
921
var mod = modList . Mods . FirstOrDefault ( x => x . fullPath == file ) ;
797
922
var longOffset = ( ( long ) DatOffsets [ file ] ) * 8L ;
798
923
var originalOffset = OriginalOffsets [ file ] ;
@@ -827,13 +952,12 @@ await Task.Run(async () =>
827
952
mod . data . originalOffset = ( fileAdditionMod ? longOffset : longOriginal ) ;
828
953
mod . data . dataType = fileType ;
829
954
mod . enabled = true ;
830
- mod . modPack = modPack ;
955
+ mod . modPack = json . ModPackEntry ;
831
956
modList . Mods . Add ( mod ) ;
832
957
833
958
}
834
959
else
835
960
{
836
-
837
961
var fileAdditionMod = originalOffset == 0 || mod . IsCustomFile ( ) ;
838
962
if ( fileAdditionMod )
839
963
{
@@ -843,7 +967,7 @@ await Task.Run(async () =>
843
967
mod . data . modSize = size ;
844
968
mod . data . modOffset = longOffset ;
845
969
mod . enabled = true ;
846
- mod . modPack = modPack ;
970
+ mod . modPack = json . ModPackEntry ;
847
971
mod . data . dataType = fileType ;
848
972
mod . name = json . Name ;
849
973
mod . category = json . Category ;
@@ -938,7 +1062,7 @@ await Task.Run(async () =>
938
1062
count = 0 ;
939
1063
progress . Report ( ( 0 , 0 , "Queuing Cache Updates..." ) ) ;
940
1064
// Metadata files expanded, last thing is to queue everthing up for the Cache.
941
- var files = modsJson . Select ( x => x . FullPath ) . ToList ( ) ;
1065
+ var files = filteredModsJson . Select ( x => x . FullPath ) . ToList ( ) ;
942
1066
try
943
1067
{
944
1068
XivCache . QueueDependencyUpdate ( files ) ;
0 commit comments