Skip to content

Commit fac6cba

Browse files
Improve PMP JSON serialization correctness
1 parent dce0ee1 commit fac6cba

File tree

3 files changed

+171
-65
lines changed

3 files changed

+171
-65
lines changed

xivModdingFramework/Mods/FileTypes/PMP.cs

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,11 @@ await IOUtil.UnzipFiles(path, tempFolder, (file) =>
125125

126126

127127

128-
var defaultOption = JsonConvert.DeserializeObject<PMPOptionJson>(File.ReadAllText(defModPath), new JsonSerializerSettings
128+
var defaultOption = JsonConvert.DeserializeObject<PmpDefaultMod>(File.ReadAllText(defModPath), new JsonSerializerSettings
129129
{
130130
NullValueHandling = NullValueHandling.Ignore
131131
});
132+
defaultOption.Name = "Default";
132133

133134
var groups = new List<PMPGroupJson>();
134135

@@ -368,12 +369,15 @@ private static void ValidateOption(PmpStandardOptionJson op)
368369
}
369370
else if(group.Type == "Multi")
370371
{
371-
var ordered = group.Options.OrderBy(x => ((PmpStandardOptionJson)x).Priority).ToList();
372+
var ordered = group.Options.OrderBy(x => ((PmpMultiOptionJson)x).Priority).ToList();
372373

373374
// Bitmask options. Install in priority order.
374375
foreach(var op in ordered)
375376
{
376-
var i = group.Options.IndexOf(op);
377+
var multiGroup = group as PMPMultiGroupJson;
378+
var multiOpt = op as PmpMultiOptionJson;
379+
380+
var i = multiGroup.OptionData.IndexOf(multiOpt);
377381
var value = 1UL << i;
378382
if ((selected & value) > 0)
379383
{
@@ -750,13 +754,14 @@ public static async Task CreateSimplePmp(string destination, BaseModpackData mod
750754
{
751755
Meta = new PMPMetaJson(),
752756
Groups = new List<PMPGroupJson>(),
753-
DefaultMod = new PMPOptionJson(),
757+
DefaultMod = new PmpDefaultMod(),
754758
};
755759

756760

757761
var files = await FileIdentifier.IdentifierListFromDictionary(fileInfos);
758762

759-
pmp.DefaultMod = await CreatePmpStandardOption(workingPath, "Default", "The only option.", files, otherManipulations);
763+
pmp.DefaultMod = new PmpDefaultMod();
764+
await PopulatePmpStandardOption(pmp.DefaultMod, workingPath, files, otherManipulations);
760765

761766
pmp.Meta.Author = modpackMeta.Author;
762767
pmp.Meta.Name = modpackMeta.Name;
@@ -823,17 +828,11 @@ public static async Task WritePmp(PMPJson pmp, string workingDirectory, string z
823828
}
824829
}
825830

826-
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)
827832
{
828-
var opt = new PmpStandardOptionJson()
829-
{
830-
Name = name,
831-
Description = description,
832-
Files = new Dictionary<string, string>(),
833-
FileSwaps = new Dictionary<string, string>(),
834-
Manipulations = new List<PMPManipulationWrapperJson>(),
835-
Priority = priority,
836-
};
833+
opt.Files = new();
834+
opt.FileSwaps = new();
835+
opt.Manipulations = new();
837836

838837
// TODO - Could paralell this? Unsure how big the gains would really be though,
839838
// since the primary tasks are already paralelled internally, and there's little else heavy going on.
@@ -886,7 +885,6 @@ public static async Task<PmpStandardOptionJson> CreatePmpStandardOption(string w
886885
opt.Manipulations.Add(manip);
887886
}
888887
}
889-
return opt;
890888
}
891889

892890

@@ -1271,7 +1269,7 @@ await Task.Run(async () =>
12711269
public class PMPJson
12721270
{
12731271
public PMPMetaJson Meta { get; set; }
1274-
public PMPOptionJson DefaultMod { get; set; }
1272+
public PmpDefaultMod DefaultMod { get; set; }
12751273
public List<PMPGroupJson> Groups { get; set; }
12761274

12771275
[JsonIgnore]
@@ -1322,14 +1320,17 @@ public class PMPMetaJson
13221320
}
13231321

13241322
[JsonConverter(typeof(JsonSubtypes), "Type")]
1323+
[JsonSubtypes.KnownSubType(typeof(PMPSingleGroupJson), "Single")]
1324+
[JsonSubtypes.KnownSubType(typeof(PMPMultiGroupJson), "Multi")]
13251325
[JsonSubtypes.KnownSubType(typeof(PMPImcGroupJson), "Imc")]
13261326
public class PMPGroupJson
13271327
{
1328+
public int Version = 0;
13281329
public string Name = "";
13291330
public string Description = "";
1330-
public int Priority;
13311331
public string Image = "";
13321332
public int Page;
1333+
public int Priority;
13331334

13341335
// "Multi", "Single", or "Imc"
13351336
public string Type = "";
@@ -1340,7 +1341,24 @@ public class PMPGroupJson
13401341
// Either single Index or Bitflag.
13411342
public ulong DefaultSettings;
13421343

1343-
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;
13441362
}
13451363

13461364
public class PMPImcGroupJson : PMPGroupJson
@@ -1350,6 +1368,11 @@ public class PMPImcGroupJson : PMPGroupJson
13501368
public bool AllVariants;
13511369
public bool OnlyAttributes;
13521370

1371+
[JsonProperty(PropertyName = "Options", Order = 99)]
1372+
public List<PmpImcOptionJson> OptionData = new();
1373+
1374+
public override IReadOnlyList<PMPOptionJson> Options => OptionData;
1375+
13531376
public XivDependencyRoot GetRoot()
13541377
{
13551378
var root = PMPExtensions.GetRootFromPenumbraValues(Identifier.ObjectType, Identifier.PrimaryId, Identifier.BodySlot, Identifier.SecondaryId, Identifier.EquipSlot);
@@ -1396,35 +1419,73 @@ public static PmpIdentifierJson FromRoot(XivDependencyRootInfo root, int variant
13961419
}
13971420
}
13981421

1399-
[JsonConverter(typeof(JsonSubtypes))]
1400-
[JsonSubtypes.FallBackSubType(typeof(PmpStandardOptionJson))]
1401-
[JsonSubtypes.KnownSubTypeWithProperty(typeof(PmpDisableImcOptionJson), "IsDisableSubMod")]
1402-
[JsonSubtypes.KnownSubTypeWithProperty(typeof(PmpImcOptionJson), "AttributeMask")]
1422+
// This type will not be deserialized directly, as the correct sub-type will be known in advance
14031423
public class PMPOptionJson
14041424
{
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)]
14051428
public string Name = "";
1429+
[JsonProperty(Order = -10)]
14061430
public string Description = "";
1431+
[JsonProperty(Order = -10)]
14071432
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; }
14081440
}
14091441

14101442
public class PmpStandardOptionJson : PMPOptionJson
14111443
{
1412-
public int Priority = 0;
1444+
[JsonProperty(Order = 10)]
14131445
public Dictionary<string, string> Files = new();
1446+
[JsonProperty(Order = 10)]
14141447
public Dictionary<string, string> FileSwaps = new();
1448+
[JsonProperty(Order = 10)]
14151449
public List<PMPManipulationWrapperJson> Manipulations = new();
14161450

14171451
[JsonIgnore] public bool IsEmptyOption => !(
14181452
(FileSwaps != null && FileSwaps.Count > 0) ||
14191453
(Manipulations != null && Manipulations.Count > 0) ||
14201454
(Files != null && Files.Count > 0)
14211455
);
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;
14221480
}
14231481

14241482
public class PmpDisableImcOptionJson : PMPOptionJson
14251483
{
1426-
public bool IsDisableSubMod;
1484+
public bool IsDisableSubMod = true;
14271485
}
1486+
1487+
[JsonConverter(typeof(JsonSubtypes))]
1488+
[JsonSubtypes.KnownSubTypeWithProperty(typeof(PmpDisableImcOptionJson), "IsDisableSubMod")]
14281489
public class PmpImcOptionJson : PMPOptionJson
14291490
{
14301491
public ushort AttributeMask;

xivModdingFramework/Mods/FileTypes/PmpManipulation.cs

Lines changed: 2 additions & 1 deletion
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;
@@ -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)