@@ -116,13 +116,20 @@ await IOUtil.UnzipFiles(path, tempFolder, (file) =>
116
116
var metaPath = Path . Combine ( path , "meta.json" ) ;
117
117
118
118
var text = File . ReadAllText ( metaPath ) ;
119
- var meta = JsonConvert . DeserializeObject < PMPMetaJson > ( text ) ;
119
+ var meta = JsonConvert . DeserializeObject < PMPMetaJson > ( text , new JsonSerializerSettings
120
+ {
121
+ NullValueHandling = NullValueHandling . Ignore
122
+ } ) ;
120
123
121
124
string image = null ;
122
125
123
126
124
127
125
- var defaultOption = JsonConvert . DeserializeObject < PMPOptionJson > ( File . ReadAllText ( defModPath ) ) ;
128
+ var defaultOption = JsonConvert . DeserializeObject < PmpDefaultMod > ( File . ReadAllText ( defModPath ) , new JsonSerializerSettings
129
+ {
130
+ NullValueHandling = NullValueHandling . Ignore
131
+ } ) ;
132
+ defaultOption . Name = "Default" ;
126
133
127
134
var groups = new List < PMPGroupJson > ( ) ;
128
135
@@ -132,7 +139,10 @@ await IOUtil.UnzipFiles(path, tempFolder, (file) =>
132
139
{
133
140
if ( Path . GetFileName ( file ) . StartsWith ( "group_" ) && Path . GetFileName ( file ) . ToLower ( ) . EndsWith ( ".json" ) )
134
141
{
135
- var group = JsonConvert . DeserializeObject < PMPGroupJson > ( File . ReadAllText ( file ) ) ;
142
+ var group = JsonConvert . DeserializeObject < PMPGroupJson > ( File . ReadAllText ( file ) , new JsonSerializerSettings
143
+ {
144
+ NullValueHandling = NullValueHandling . Ignore
145
+ } ) ;
136
146
if ( group != null )
137
147
{
138
148
groups . Add ( group ) ;
@@ -341,30 +351,34 @@ private static void ValidateOption(PmpStandardOptionJson op)
341
351
var selected = group . DefaultSettings ;
342
352
343
353
// If the user selected custom settings, use those.
344
- if ( group . SelectedSettings >= 0 )
354
+ if ( group . SelectedSettings . HasValue )
345
355
{
346
- selected = group . SelectedSettings ;
356
+ selected = group . SelectedSettings . Value ;
347
357
}
348
358
349
359
if ( group . Type == "Single" )
350
360
{
351
- if ( selected < 0 || selected >= group . Options . Count )
361
+ var selectedIdx = ( int ) selected ;
362
+ if ( selected < 0 || selectedIdx >= group . Options . Count )
352
363
{
353
364
selected = 0 ;
354
365
}
355
- var groupRes = await ImportOption ( group . Options [ selected ] , unzippedPath , tx , progress , groupIdx , optionIdx ) ;
366
+ var groupRes = await ImportOption ( group . Options [ selectedIdx ] , unzippedPath , tx , progress , groupIdx , optionIdx ) ;
356
367
UnionDict ( imported , groupRes . Imported ) ;
357
368
notImported . UnionWith ( groupRes . NotImported ) ;
358
369
}
359
370
else if ( group . Type == "Multi" )
360
371
{
361
- var ordered = group . Options . OrderBy ( x => ( ( PmpStandardOptionJson ) x ) . Priority ) . ToList ( ) ;
372
+ var ordered = group . Options . OrderBy ( x => ( ( PmpMultiOptionJson ) x ) . Priority ) . ToList ( ) ;
362
373
363
374
// Bitmask options. Install in priority order.
364
375
foreach ( var op in ordered )
365
376
{
366
- var i = group . Options . IndexOf ( op ) ;
367
- var value = 1 << i ;
377
+ var multiGroup = group as PMPMultiGroupJson ;
378
+ var multiOpt = op as PmpMultiOptionJson ;
379
+
380
+ var i = multiGroup . OptionData . IndexOf ( multiOpt ) ;
381
+ var value = 1UL << i ;
368
382
if ( ( selected & value ) > 0 )
369
383
{
370
384
var groupRes = await ImportOption ( group . Options [ i ] , unzippedPath , tx , progress , groupIdx , optionIdx ) ;
@@ -384,7 +398,7 @@ private static void ValidateOption(PmpStandardOptionJson op)
384
398
// Bitmask options.
385
399
for ( int i = 0 ; i < group . Options . Count ; i ++ )
386
400
{
387
- var value = 1 << i ;
401
+ var value = 1UL << i ;
388
402
if ( ( selected & value ) > 0 )
389
403
{
390
404
var disableOpt = group . Options [ i ] as PmpDisableImcOptionJson ;
@@ -740,13 +754,14 @@ public static async Task CreateSimplePmp(string destination, BaseModpackData mod
740
754
{
741
755
Meta = new PMPMetaJson ( ) ,
742
756
Groups = new List < PMPGroupJson > ( ) ,
743
- DefaultMod = new PMPOptionJson ( ) ,
757
+ DefaultMod = new PmpDefaultMod ( ) ,
744
758
} ;
745
759
746
760
747
761
var files = await FileIdentifier . IdentifierListFromDictionary ( fileInfos ) ;
748
762
749
- pmp . DefaultMod = await CreatePmpStandardOption ( workingPath , "Default" , "The only option." , files , otherManipulations ) ;
763
+ pmp . DefaultMod = new PmpDefaultMod ( ) ;
764
+ await PopulatePmpStandardOption ( pmp . DefaultMod , workingPath , files , otherManipulations ) ;
750
765
751
766
pmp . Meta . Author = modpackMeta . Author ;
752
767
pmp . Meta . Name = modpackMeta . Name ;
@@ -813,17 +828,11 @@ public static async Task WritePmp(PMPJson pmp, string workingDirectory, string z
813
828
}
814
829
}
815
830
816
- public static async Task < PmpStandardOptionJson > CreatePmpStandardOption ( string workingPath , string name , string description , IEnumerable < FileIdentifier > files , IEnumerable < PMPManipulationWrapperJson > otherManipulations = null , string imagePath = null , int priority = 0 )
831
+ public static async Task PopulatePmpStandardOption ( PmpStandardOptionJson opt , string workingPath , IEnumerable < FileIdentifier > files , IEnumerable < PMPManipulationWrapperJson > otherManipulations = null )
817
832
{
818
- var opt = new PmpStandardOptionJson ( )
819
- {
820
- Name = name ,
821
- Description = description ,
822
- Files = new Dictionary < string , string > ( ) ,
823
- FileSwaps = new Dictionary < string , string > ( ) ,
824
- Manipulations = new List < PMPManipulationWrapperJson > ( ) ,
825
- Priority = priority ,
826
- } ;
833
+ opt . Files = new ( ) ;
834
+ opt . FileSwaps = new ( ) ;
835
+ opt . Manipulations = new ( ) ;
827
836
828
837
// TODO - Could paralell this? Unsure how big the gains would really be though,
829
838
// since the primary tasks are already paralelled internally, and there's little else heavy going on.
@@ -876,7 +885,6 @@ public static async Task<PmpStandardOptionJson> CreatePmpStandardOption(string w
876
885
opt . Manipulations . Add ( manip ) ;
877
886
}
878
887
}
879
- return opt ;
880
888
}
881
889
882
890
@@ -1261,7 +1269,7 @@ await Task.Run(async () =>
1261
1269
public class PMPJson
1262
1270
{
1263
1271
public PMPMetaJson Meta { get ; set ; }
1264
- public PMPOptionJson DefaultMod { get ; set ; }
1272
+ public PmpDefaultMod DefaultMod { get ; set ; }
1265
1273
public List < PMPGroupJson > Groups { get ; set ; }
1266
1274
1267
1275
[ JsonIgnore ]
@@ -1300,46 +1308,71 @@ public string GetHeaderImage()
1300
1308
public class PMPMetaJson
1301
1309
{
1302
1310
public int FileVersion ;
1303
- public string Name ;
1304
- public string Author ;
1305
- public string Description ;
1306
- public string Version ;
1307
- public string Website ;
1308
- public string Image ;
1311
+ public string Name = "" ;
1312
+ public string Author = "" ;
1313
+ public string Description = "" ;
1314
+ public string Version = "" ;
1315
+ public string Website = "" ;
1316
+ public string Image = "" ;
1309
1317
1310
1318
// These exist.
1311
1319
public List < string > ModTags ;
1312
1320
}
1313
1321
1314
1322
[ JsonConverter ( typeof ( JsonSubtypes ) , "Type" ) ]
1323
+ [ JsonSubtypes . KnownSubType ( typeof ( PMPSingleGroupJson ) , "Single" ) ]
1324
+ [ JsonSubtypes . KnownSubType ( typeof ( PMPMultiGroupJson ) , "Multi" ) ]
1315
1325
[ JsonSubtypes . KnownSubType ( typeof ( PMPImcGroupJson ) , "Imc" ) ]
1316
1326
public class PMPGroupJson
1317
1327
{
1318
- public string Name ;
1319
- public string Description ;
1320
- public int Priority ;
1321
- public string Image ;
1328
+ public int Version = 0 ;
1329
+ public string Name = "" ;
1330
+ public string Description = "" ;
1331
+ public string Image = "" ;
1322
1332
public int Page ;
1333
+ public int Priority ;
1323
1334
1324
1335
// "Multi", "Single", or "Imc"
1325
- public string Type ;
1336
+ public string Type = "" ;
1326
1337
1327
1338
// Only used internally when the user is selecting options during install/application.
1328
- [ JsonIgnore ] public int SelectedSettings = - 1 ;
1339
+ [ JsonIgnore ] public ulong ? SelectedSettings = null ;
1329
1340
1330
1341
// Either single Index or Bitflag.
1331
- public int DefaultSettings ;
1342
+ public ulong DefaultSettings ;
1332
1343
1333
- public List < PMPOptionJson > Options = new List < PMPOptionJson > ( ) ;
1344
+ [ JsonIgnore ]
1345
+ public virtual IReadOnlyList < PMPOptionJson > Options => throw new NotImplementedException ( $ "Unimplemented PMP group type: { Type } ") ;
1346
+ }
1347
+
1348
+ public class PMPSingleGroupJson : PMPGroupJson
1349
+ {
1350
+ [ JsonProperty ( PropertyName = "Options" , Order = 99 ) ]
1351
+ public List < PmpSingleOptionJson > OptionData = new ( ) ;
1352
+
1353
+ public override IReadOnlyList < PMPOptionJson > Options => OptionData ;
1354
+ }
1355
+
1356
+ public class PMPMultiGroupJson : PMPGroupJson
1357
+ {
1358
+ [ JsonProperty ( PropertyName = "Options" , Order = 99 ) ]
1359
+ public List < PmpMultiOptionJson > OptionData = new ( ) ;
1360
+
1361
+ public override IReadOnlyList < PMPOptionJson > Options => OptionData ;
1334
1362
}
1335
1363
1336
1364
public class PMPImcGroupJson : PMPGroupJson
1337
1365
{
1338
- public PMPImcManipulationJson . PMPImcEntry DefaultEntry ;
1339
1366
public PmpIdentifierJson Identifier ;
1367
+ public PMPImcManipulationJson . PMPImcEntry DefaultEntry ;
1340
1368
public bool AllVariants ;
1341
1369
public bool OnlyAttributes ;
1342
1370
1371
+ [ JsonProperty ( PropertyName = "Options" , Order = 99 ) ]
1372
+ public List < PmpImcOptionJson > OptionData = new ( ) ;
1373
+
1374
+ public override IReadOnlyList < PMPOptionJson > Options => OptionData ;
1375
+
1343
1376
public XivDependencyRoot GetRoot ( )
1344
1377
{
1345
1378
var root = PMPExtensions . GetRootFromPenumbraValues ( Identifier . ObjectType , Identifier . PrimaryId , Identifier . BodySlot , Identifier . SecondaryId , Identifier . EquipSlot ) ;
@@ -1386,35 +1419,73 @@ public static PmpIdentifierJson FromRoot(XivDependencyRootInfo root, int variant
1386
1419
}
1387
1420
}
1388
1421
1389
- [ JsonConverter ( typeof ( JsonSubtypes ) ) ]
1390
- [ JsonSubtypes . KnownSubTypeWithProperty ( typeof ( PmpStandardOptionJson ) , "Files" ) ]
1391
- [ JsonSubtypes . KnownSubTypeWithProperty ( typeof ( PmpDisableImcOptionJson ) , "IsDisableSubMod" ) ]
1392
- [ JsonSubtypes . KnownSubTypeWithProperty ( typeof ( PmpImcOptionJson ) , "AttributeMask" ) ]
1422
+ // This type will not be deserialized directly, as the correct sub-type will be known in advance
1393
1423
public class PMPOptionJson
1394
1424
{
1395
- public string Name ;
1396
- public string Description ;
1397
- public string Image ;
1425
+ // For some reason the default order is that base class fields ordered last instead of first ...
1426
+ // Manually specifying the order of a bunch of option-related fields to fix that
1427
+ [ JsonProperty ( Order = - 10 ) ]
1428
+ public string Name = "" ;
1429
+ [ JsonProperty ( Order = - 10 ) ]
1430
+ public string Description = "" ;
1431
+ [ JsonProperty ( Order = - 10 ) ]
1432
+ public string Image = "" ;
1433
+
1434
+ // TexTools loads/saves default_mod.json as this type, but these fields should not be present in default_mod
1435
+ [ JsonIgnore ] public virtual bool IsDataContainerOnly => false ;
1436
+
1437
+ public bool ShouldSerializeName ( ) { return ! IsDataContainerOnly ; }
1438
+ public bool ShouldSerializeDescription ( ) { return ! IsDataContainerOnly ; }
1439
+ public bool ShouldSerializeImage ( ) { return ! IsDataContainerOnly ; }
1398
1440
}
1399
1441
1400
1442
public class PmpStandardOptionJson : PMPOptionJson
1401
1443
{
1402
- public Dictionary < string , string > Files ;
1403
- public Dictionary < string , string > FileSwaps ;
1404
- public List < PMPManipulationWrapperJson > Manipulations ;
1405
- public int Priority ;
1444
+ [ JsonProperty ( Order = 10 ) ]
1445
+ public Dictionary < string , string > Files = new ( ) ;
1446
+ [ JsonProperty ( Order = 10 ) ]
1447
+ public Dictionary < string , string > FileSwaps = new ( ) ;
1448
+ [ JsonProperty ( Order = 10 ) ]
1449
+ public List < PMPManipulationWrapperJson > Manipulations = new ( ) ;
1406
1450
1407
1451
[ JsonIgnore ] public bool IsEmptyOption => ! (
1408
1452
( FileSwaps != null && FileSwaps . Count > 0 ) ||
1409
1453
( Manipulations != null && Manipulations . Count > 0 ) ||
1410
1454
( Files != null && Files . Count > 0 )
1411
1455
) ;
1456
+
1457
+ // TODO: Comment this out in the future to mimic Penumbra's behavior
1458
+ /*
1459
+ public bool ShouldSerializeFiles() { return Files != null && Files.Count > 0; }
1460
+ public bool ShouldSerializeFileSwaps() { return FileSwaps != null && FileSwaps.Count > 0; }
1461
+ public bool ShouldSerializeManipulations() { return Manipulations != null && Manipulations.Count > 0; }
1462
+ */
1463
+ }
1464
+
1465
+ public class PmpDefaultMod : PmpStandardOptionJson
1466
+ {
1467
+ [ JsonProperty ( Order = - 99 ) ]
1468
+ public int Version = 0 ;
1469
+ [ JsonIgnore ] public override bool IsDataContainerOnly => true ;
1470
+ }
1471
+
1472
+ public class PmpSingleOptionJson : PmpStandardOptionJson
1473
+ {
1474
+ }
1475
+
1476
+ public class PmpMultiOptionJson : PmpStandardOptionJson
1477
+ {
1478
+ [ JsonProperty ( Order = 2 ) ]
1479
+ public int Priority = 0 ;
1412
1480
}
1413
1481
1414
1482
public class PmpDisableImcOptionJson : PMPOptionJson
1415
1483
{
1416
- public bool IsDisableSubMod ;
1484
+ public bool IsDisableSubMod = true ;
1417
1485
}
1486
+
1487
+ [ JsonConverter ( typeof ( JsonSubtypes ) ) ]
1488
+ [ JsonSubtypes . KnownSubTypeWithProperty ( typeof ( PmpDisableImcOptionJson ) , "IsDisableSubMod" ) ]
1418
1489
public class PmpImcOptionJson : PMPOptionJson
1419
1490
{
1420
1491
public ushort AttributeMask ;
0 commit comments