Skip to content

Commit 4ca0ee0

Browse files
authored
Merge pull request #77 from senko-forks/pr-pmp
Merging Senko's work on fixing/updating PMP Read/Write
2 parents 15d272e + 110230e commit 4ca0ee0

File tree

3 files changed

+242
-111
lines changed

3 files changed

+242
-111
lines changed

xivModdingFramework/Mods/FileTypes/PMP.cs

Lines changed: 123 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,20 @@ await IOUtil.UnzipFiles(path, tempFolder, (file) =>
116116
var metaPath = Path.Combine(path, "meta.json");
117117

118118
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+
});
120123

121124
string image = null;
122125

123126

124127

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";
126133

127134
var groups = new List<PMPGroupJson>();
128135

@@ -132,7 +139,10 @@ await IOUtil.UnzipFiles(path, tempFolder, (file) =>
132139
{
133140
if (Path.GetFileName(file).StartsWith("group_") && Path.GetFileName(file).ToLower().EndsWith(".json"))
134141
{
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+
});
136146
if (group != null)
137147
{
138148
groups.Add(group);
@@ -341,30 +351,34 @@ private static void ValidateOption(PmpStandardOptionJson op)
341351
var selected = group.DefaultSettings;
342352

343353
// If the user selected custom settings, use those.
344-
if (group.SelectedSettings >= 0)
354+
if (group.SelectedSettings.HasValue)
345355
{
346-
selected = group.SelectedSettings;
356+
selected = group.SelectedSettings.Value;
347357
}
348358

349359
if (group.Type == "Single")
350360
{
351-
if (selected < 0 || selected >= group.Options.Count)
361+
var selectedIdx = (int)selected;
362+
if (selected < 0 || selectedIdx >= group.Options.Count)
352363
{
353364
selected = 0;
354365
}
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);
356367
UnionDict(imported, groupRes.Imported);
357368
notImported.UnionWith(groupRes.NotImported);
358369
}
359370
else if(group.Type == "Multi")
360371
{
361-
var ordered = group.Options.OrderBy(x => ((PmpStandardOptionJson)x).Priority).ToList();
372+
var ordered = group.Options.OrderBy(x => ((PmpMultiOptionJson)x).Priority).ToList();
362373

363374
// Bitmask options. Install in priority order.
364375
foreach(var op in ordered)
365376
{
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;
368382
if ((selected & value) > 0)
369383
{
370384
var groupRes = await ImportOption(group.Options[i], unzippedPath, tx, progress, groupIdx, optionIdx);
@@ -384,7 +398,7 @@ private static void ValidateOption(PmpStandardOptionJson op)
384398
// Bitmask options.
385399
for (int i = 0; i < group.Options.Count; i++)
386400
{
387-
var value = 1 << i;
401+
var value = 1UL << i;
388402
if ((selected & value) > 0)
389403
{
390404
var disableOpt = group.Options[i] as PmpDisableImcOptionJson;
@@ -740,13 +754,14 @@ public static async Task CreateSimplePmp(string destination, BaseModpackData mod
740754
{
741755
Meta = new PMPMetaJson(),
742756
Groups = new List<PMPGroupJson>(),
743-
DefaultMod = new PMPOptionJson(),
757+
DefaultMod = new PmpDefaultMod(),
744758
};
745759

746760

747761
var files = await FileIdentifier.IdentifierListFromDictionary(fileInfos);
748762

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);
750765

751766
pmp.Meta.Author = modpackMeta.Author;
752767
pmp.Meta.Name = modpackMeta.Name;
@@ -813,17 +828,11 @@ public static async Task WritePmp(PMPJson pmp, string workingDirectory, string z
813828
}
814829
}
815830

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)
817832
{
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();
827836

828837
// TODO - Could paralell this? Unsure how big the gains would really be though,
829838
// 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
876885
opt.Manipulations.Add(manip);
877886
}
878887
}
879-
return opt;
880888
}
881889

882890

@@ -1261,7 +1269,7 @@ await Task.Run(async () =>
12611269
public class PMPJson
12621270
{
12631271
public PMPMetaJson Meta { get; set; }
1264-
public PMPOptionJson DefaultMod { get; set; }
1272+
public PmpDefaultMod DefaultMod { get; set; }
12651273
public List<PMPGroupJson> Groups { get; set; }
12661274

12671275
[JsonIgnore]
@@ -1300,46 +1308,71 @@ public string GetHeaderImage()
13001308
public class PMPMetaJson
13011309
{
13021310
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 = "";
13091317

13101318
// These exist.
13111319
public List<string> ModTags;
13121320
}
13131321

13141322
[JsonConverter(typeof(JsonSubtypes), "Type")]
1323+
[JsonSubtypes.KnownSubType(typeof(PMPSingleGroupJson), "Single")]
1324+
[JsonSubtypes.KnownSubType(typeof(PMPMultiGroupJson), "Multi")]
13151325
[JsonSubtypes.KnownSubType(typeof(PMPImcGroupJson), "Imc")]
13161326
public class PMPGroupJson
13171327
{
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 = "";
13221332
public int Page;
1333+
public int Priority;
13231334

13241335
// "Multi", "Single", or "Imc"
1325-
public string Type;
1336+
public string Type = "";
13261337

13271338
// Only used internally when the user is selecting options during install/application.
1328-
[JsonIgnore] public int SelectedSettings = -1;
1339+
[JsonIgnore] public ulong? SelectedSettings = null;
13291340

13301341
// Either single Index or Bitflag.
1331-
public int DefaultSettings;
1342+
public ulong DefaultSettings;
13321343

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;
13341362
}
13351363

13361364
public class PMPImcGroupJson : PMPGroupJson
13371365
{
1338-
public PMPImcManipulationJson.PMPImcEntry DefaultEntry;
13391366
public PmpIdentifierJson Identifier;
1367+
public PMPImcManipulationJson.PMPImcEntry DefaultEntry;
13401368
public bool AllVariants;
13411369
public bool OnlyAttributes;
13421370

1371+
[JsonProperty(PropertyName = "Options", Order = 99)]
1372+
public List<PmpImcOptionJson> OptionData = new();
1373+
1374+
public override IReadOnlyList<PMPOptionJson> Options => OptionData;
1375+
13431376
public XivDependencyRoot GetRoot()
13441377
{
13451378
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
13861419
}
13871420
}
13881421

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
13931423
public class PMPOptionJson
13941424
{
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; }
13981440
}
13991441

14001442
public class PmpStandardOptionJson : PMPOptionJson
14011443
{
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();
14061450

14071451
[JsonIgnore] public bool IsEmptyOption => !(
14081452
(FileSwaps != null && FileSwaps.Count > 0) ||
14091453
(Manipulations != null && Manipulations.Count > 0) ||
14101454
(Files != null && Files.Count > 0)
14111455
);
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;
14121480
}
14131481

14141482
public class PmpDisableImcOptionJson : PMPOptionJson
14151483
{
1416-
public bool IsDisableSubMod;
1484+
public bool IsDisableSubMod = true;
14171485
}
1486+
1487+
[JsonConverter(typeof(JsonSubtypes))]
1488+
[JsonSubtypes.KnownSubTypeWithProperty(typeof(PmpDisableImcOptionJson), "IsDisableSubMod")]
14181489
public class PmpImcOptionJson : PMPOptionJson
14191490
{
14201491
public ushort AttributeMask;

xivModdingFramework/Mods/FileTypes/PmpManipulation.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ public struct PMPImcEntry
315315
public byte VfxId;
316316
public byte MaterialAnimationId;
317317

318-
// Are these redundantly stored?
318+
[JsonIgnore]
319319
public ushort AttributeAndSound;
320320
public ushort AttributeMask;
321321
public byte SoundId;
@@ -345,10 +345,10 @@ public static PMPImcEntry FromXivImc(XivImc imc)
345345
}
346346
}
347347
public PMPImcEntry Entry;
348+
public PMPObjectType ObjectType;
348349
public uint PrimaryId;
349350
public uint SecondaryId;
350351
public uint Variant;
351-
public PMPObjectType ObjectType;
352352
public PMPEquipSlot EquipSlot;
353353
public PMPObjectType BodySlot;
354354

@@ -432,6 +432,7 @@ public class PMPEqdpManipulationJson : IPMPItemMetadata
432432
public uint SetId;
433433
public PMPEquipSlot Slot;
434434

435+
[JsonIgnore]
435436
public ushort ShiftedEntry
436437
{
437438
get

0 commit comments

Comments
 (0)