diff --git a/SabreTools.Library/DatFiles/Json.cs b/SabreTools.Library/DatFiles/Json.cs
index 899e79cb..d0367882 100644
--- a/SabreTools.Library/DatFiles/Json.cs
+++ b/SabreTools.Library/DatFiles/Json.cs
@@ -25,6 +25,735 @@ namespace SabreTools.Library.DatFiles
{
}
+ ///
+ /// Parse a Logiqx XML DAT and return all found games and roms within
+ ///
+ /// Name of the file to be parsed
+ /// System ID for the DAT
+ /// Source ID for the DAT
+ /// True if full pathnames are to be kept, false otherwise (default)
+ /// True if game names are sanitized, false otherwise (default)
+ /// True if we should remove non-ASCII characters from output, false otherwise (default)
+ public override void ParseFile(
+ // Standard Dat parsing
+ string filename,
+ int sysid,
+ int srcid,
+
+ // Miscellaneous
+ bool keep,
+ bool clean,
+ bool remUnicode)
+ {
+ // Prepare all internal variables
+ Encoding enc = Utilities.GetEncoding(filename);
+ StreamReader sr = new StreamReader(Utilities.TryOpenRead(filename), new UTF8Encoding(false));
+ JsonTextReader jtr = new JsonTextReader(sr);
+
+ // If we got a null reader, just return
+ if (jtr == null)
+ return;
+
+ // Otherwise, read the file to the end
+ try
+ {
+ jtr.Read();
+ while (!sr.EndOfStream)
+ {
+ // Skip everything not a property name
+ if (jtr.TokenType != JsonToken.PropertyName)
+ {
+ jtr.Read();
+ continue;
+ }
+
+ switch (jtr.Value)
+ {
+ // Header value
+ case "header":
+ ReadHeader(sr, jtr, keep);
+ jtr.Read();
+ break;
+
+ // Machine array
+ case "machines":
+ ReadMachines(sr, jtr, clean, remUnicode);
+ jtr.Read();
+ break;
+
+ default:
+ jtr.Read();
+ break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Globals.Logger.Warning($"Exception found while parsing '{filename}': {ex}");
+ }
+
+ jtr.Close();
+ }
+
+ ///
+ /// Read header information
+ ///
+ /// StreamReader to use to parse the header
+ /// JsonTextReader to use to parse the header
+ /// True if full pathnames are to be kept, false otherwise (default)
+ private void ReadHeader(StreamReader sr, JsonTextReader jtr, bool keep)
+ {
+ bool superdat = false;
+
+ // If the reader is invalid, skip
+ if (jtr == null)
+ return;
+
+ jtr.Read();
+ while (!sr.EndOfStream)
+ {
+ // If we hit the end of the header, return
+ if (jtr.TokenType == JsonToken.EndObject)
+ return;
+
+ // We don't care about anything except property names
+ if (jtr.TokenType != JsonToken.PropertyName)
+ {
+ jtr.Read();
+ continue;
+ }
+
+ // Get all header items (ONLY OVERWRITE IF THERE'S NO DATA)
+ string content = string.Empty;
+ switch (jtr.Value)
+ {
+ case "name":
+ content = jtr.ReadAsString();
+ Name = (string.IsNullOrWhiteSpace(Name) ? content : Name);
+ superdat = superdat || content.Contains(" - SuperDAT");
+ if (keep && superdat)
+ {
+ Type = (string.IsNullOrWhiteSpace(Type) ? "SuperDAT" : Type);
+ }
+ break;
+
+ case "description":
+ content = jtr.ReadAsString();
+ Description = (string.IsNullOrWhiteSpace(Description) ? content : Description);
+ break;
+
+ case "rootdir": // This is exclusive to TruRip XML
+ content = jtr.ReadAsString();
+ RootDir = (string.IsNullOrWhiteSpace(RootDir) ? content : RootDir);
+ break;
+
+ case "category":
+ content = jtr.ReadAsString();
+ Category = (string.IsNullOrWhiteSpace(Category) ? content : Category);
+ break;
+
+ case "version":
+ content = jtr.ReadAsString();
+ Version = (string.IsNullOrWhiteSpace(Version) ? content : Version);
+ break;
+
+ case "date":
+ content = jtr.ReadAsString();
+ Date = (string.IsNullOrWhiteSpace(Date) ? content.Replace(".", "/") : Date);
+ break;
+
+ case "author":
+ content = jtr.ReadAsString();
+ Author = (string.IsNullOrWhiteSpace(Author) ? content : Author);
+ break;
+
+ case "email":
+ content = jtr.ReadAsString();
+ Email = (string.IsNullOrWhiteSpace(Email) ? content : Email);
+ break;
+
+ case "homepage":
+ content = jtr.ReadAsString();
+ Homepage = (string.IsNullOrWhiteSpace(Homepage) ? content : Homepage);
+ break;
+
+ case "url":
+ content = jtr.ReadAsString();
+ Url = (string.IsNullOrWhiteSpace(Url) ? content : Url);
+ break;
+
+ case "comment":
+ content = jtr.ReadAsString();
+ Comment = (string.IsNullOrWhiteSpace(Comment) ? content : Comment);
+ break;
+
+ case "type": // This is exclusive to TruRip XML
+ content = jtr.ReadAsString();
+ Type = (string.IsNullOrWhiteSpace(Type) ? content : Type);
+ superdat = superdat || content.Contains("SuperDAT");
+ break;
+
+ case "forcemerging":
+ if (ForceMerging == ForceMerging.None)
+ ForceMerging = Utilities.GetForceMerging(jtr.ReadAsString());
+
+ break;
+
+ case "forcepacking":
+ if (ForcePacking == ForcePacking.None)
+ ForcePacking = Utilities.GetForcePacking(jtr.ReadAsString());
+
+ break;
+
+ case "forcenodump":
+ if (ForceNodump == ForceNodump.None)
+ ForceNodump = Utilities.GetForceNodump(jtr.ReadAsString());
+
+ break;
+
+ case "header":
+ content = jtr.ReadAsString();
+ Header = (string.IsNullOrWhiteSpace(Header) ? content : Header);
+ break;
+
+ default:
+ break;
+ }
+
+ jtr.Read();
+ }
+ }
+
+ ///
+ /// Read machine array information
+ ///
+ /// StreamReader to use to parse the header
+ /// JsonTextReader to use to parse the machine
+ /// True if game names are sanitized, false otherwise (default)
+ /// True if we should remove non-ASCII characters from output, false otherwise (default)
+ private void ReadMachines(
+ StreamReader sr,
+ JsonTextReader jtr,
+
+ // Miscellaneous
+ bool clean,
+ bool remUnicode)
+ {
+ // If the reader is invalid, skip
+ if (jtr == null)
+ return;
+
+ jtr.Read();
+ while (!sr.EndOfStream)
+ {
+ // If we hit the end of an array, we want to return
+ if (jtr.TokenType == JsonToken.EndArray)
+ return;
+
+ // We don't care about anything except start object
+ if (jtr.TokenType != JsonToken.StartObject)
+ {
+ jtr.Read();
+ continue;
+ }
+
+ ReadMachine(sr, jtr, clean, remUnicode);
+ jtr.Read();
+ }
+ }
+
+ ///
+ /// Read machine information
+ ///
+ /// StreamReader to use to parse the header
+ /// JsonTextReader to use to parse the machine
+ /// True if game names are sanitized, false otherwise (default)
+ /// True if we should remove non-ASCII characters from output, false otherwise (default)
+ private void ReadMachine(
+ StreamReader sr,
+ JsonTextReader jtr,
+
+ // Miscellaneous
+ bool clean,
+ bool remUnicode)
+ {
+ // If we have an empty machine, skip it
+ if (jtr == null)
+ return;
+
+ // Prepare internal variables
+ Machine machine = new Machine();
+
+ jtr.Read();
+ while (!sr.EndOfStream)
+ {
+ // If we hit the end of the machine, return
+ if (jtr.TokenType == JsonToken.EndObject)
+ return;
+
+ // We don't care about anything except properties
+ if (jtr.TokenType != JsonToken.PropertyName)
+ {
+ jtr.Read();
+ continue;
+ }
+
+ switch (jtr.Value)
+ {
+ case "name":
+ machine.Name = jtr.ReadAsString();
+ break;
+
+ case "comment":
+ machine.Comment = jtr.ReadAsString();
+ break;
+
+ case "description":
+ machine.Description = jtr.ReadAsString();
+ break;
+
+ case "year":
+ machine.Year = jtr.ReadAsString();
+ break;
+
+ case "manufacturer":
+ machine.Manufacturer = jtr.ReadAsString();
+ break;
+
+ case "publisher":
+ machine.Publisher = jtr.ReadAsString();
+ break;
+
+ case "romof":
+ machine.RomOf = jtr.ReadAsString();
+ break;
+
+ case "cloneof":
+ machine.CloneOf = jtr.ReadAsString();
+ break;
+
+ case "sampleof":
+ machine.SampleOf = jtr.ReadAsString();
+ break;
+
+ case "supported":
+ string supported = jtr.ReadAsString();
+ switch (supported)
+ {
+ case "yes":
+ machine.Supported = true;
+ break;
+ case "no":
+ machine.Supported = false;
+ break;
+ case "partial":
+ machine.Supported = null;
+ break;
+ }
+ break;
+
+ case "sourcefile":
+ machine.SourceFile = jtr.ReadAsString();
+ break;
+
+ case "runnable":
+ string runnable = jtr.ReadAsString();
+ switch (runnable)
+ {
+ case "yes":
+ machine.Runnable = true;
+ break;
+ case "no":
+ machine.Runnable = false;
+ break;
+ }
+ break;
+
+ case "board":
+ machine.Board = jtr.ReadAsString();
+ break;
+
+ case "rebuildto":
+ machine.RebuildTo = jtr.ReadAsString();
+ break;
+
+ case "devices":
+ machine.Devices = new List();
+ jtr.Read(); // Start Array
+ while (!sr.EndOfStream && jtr.TokenType != JsonToken.EndArray)
+ {
+ machine.Devices.Add(jtr.ReadAsString());
+ }
+
+ break;
+
+ case "slotoptions":
+ machine.SlotOptions = new List();
+ jtr.Read(); // Start Array
+ while (!sr.EndOfStream && jtr.TokenType != JsonToken.EndArray)
+ {
+ machine.SlotOptions.Add(jtr.ReadAsString());
+ }
+
+ break;
+
+ case "infos":
+ machine.Infos = new List>();
+ jtr.Read(); // Start Array
+ while (!sr.EndOfStream)
+ {
+ jtr.Read(); // Start object (or end array)
+ if (jtr.TokenType == JsonToken.EndArray)
+ break;
+
+ jtr.Read(); // Key
+ string key = jtr.Value as string;
+ string value = jtr.ReadAsString();
+ jtr.Read(); // End object
+
+ machine.Infos.Add(new KeyValuePair(key, value));
+ }
+
+ break;
+
+ case "isbios":
+ string isbios = jtr.ReadAsString();
+ if (string.Equals(isbios, "yes", StringComparison.OrdinalIgnoreCase))
+ machine.MachineType &= MachineType.Bios;
+
+ break;
+
+ case "isdevice":
+ string isdevice = jtr.ReadAsString();
+ if (string.Equals(isdevice, "yes", StringComparison.OrdinalIgnoreCase))
+ machine.MachineType &= MachineType.Device;
+
+ break;
+
+ case "ismechanical":
+ string ismechanical = jtr.ReadAsString();
+ if (string.Equals(ismechanical, "yes", StringComparison.OrdinalIgnoreCase))
+ machine.MachineType &= MachineType.Mechanical;
+
+ break;
+
+ case "items":
+ ReadItems(sr, jtr, clean, remUnicode, machine);
+ break;
+
+ default:
+ break;
+ }
+
+ jtr.Read();
+ }
+ }
+
+ ///
+ /// Read item array information
+ ///
+ /// StreamReader to use to parse the header
+ /// JsonTextReader to use to parse the machine
+ /// True if game names are sanitized, false otherwise (default)
+ /// True if we should remove non-ASCII characters from output, false otherwise (default)
+ private void ReadItems(
+ StreamReader sr,
+ JsonTextReader jtr,
+
+ // Miscellaneous
+ bool clean,
+ bool remUnicode,
+ Machine machine)
+ {
+ // If the reader is invalid, skip
+ if (jtr == null)
+ return;
+
+ jtr.Read();
+ while (!sr.EndOfStream)
+ {
+ // If we hit the end of an array, we want to return
+ if (jtr.TokenType == JsonToken.EndArray)
+ return;
+
+ // We don't care about anything except start object
+ if (jtr.TokenType != JsonToken.StartObject)
+ {
+ jtr.Read();
+ continue;
+ }
+
+ ReadItem(sr, jtr, clean, remUnicode, machine);
+ jtr.Read();
+ }
+ }
+
+ ///
+ /// Read item information
+ ///
+ /// StreamReader to use to parse the header
+ /// JsonTextReader to use to parse the machine
+ /// True if game names are sanitized, false otherwise (default)
+ /// True if we should remove non-ASCII characters from output, false otherwise (default)
+ private void ReadItem(
+ StreamReader sr,
+ JsonTextReader jtr,
+
+ // Miscellaneous
+ bool clean,
+ bool remUnicode,
+ Machine machine)
+ {
+ // If we have an empty machine, skip it
+ if (jtr == null)
+ return;
+
+ // Prepare internal variables
+ bool? def = null,
+ writable = null,
+ optional = null;
+ long size = -1;
+ long? areaSize = null;
+ string name = null,
+ partName = null,
+ partInterface = null,
+ areaName = null,
+ biosDescription = null,
+ region = null,
+ language = null,
+ date = null,
+ crc = null,
+ md5 = null,
+ ripemd160 = null,
+ sha1 = null,
+ sha256 = null,
+ sha384 = null,
+ sha512 = null,
+ merge = null,
+ index = null,
+ offset = null,
+ bios = null;
+ ItemStatus? itemStatus = null;
+ ItemType? itemType = null;
+ List> features = null;
+
+ jtr.Read();
+ while (!sr.EndOfStream)
+ {
+ // If we hit the end of the machine - create, add, and return
+ if (jtr.TokenType == JsonToken.EndObject)
+ {
+ // If we didn't read something valid, just return
+ if (itemType == null)
+ return;
+
+ DatItem datItem = Utilities.GetDatItem(itemType.Value);
+ datItem.CopyMachineInformation(machine);
+
+ datItem.Name = name;
+ datItem.PartName = partName;
+ datItem.PartInterface = partInterface;
+ datItem.Features = features;
+ datItem.AreaName = areaName;
+ datItem.AreaSize = areaSize;
+
+ if (itemType == ItemType.BiosSet)
+ {
+ (datItem as BiosSet).Description = biosDescription;
+ (datItem as BiosSet).Default = def;
+ }
+ else if (itemType == ItemType.Disk)
+ {
+ (datItem as Disk).MD5 = md5;
+ (datItem as Disk).RIPEMD160 = ripemd160;
+ (datItem as Disk).SHA1 = sha1;
+ (datItem as Disk).SHA256 = sha256;
+ (datItem as Disk).SHA384 = sha384;
+ (datItem as Disk).SHA512 = sha512;
+ (datItem as Disk).MergeTag = merge;
+ (datItem as Disk).Region = region;
+ (datItem as Disk).Index = index;
+ (datItem as Disk).Writable = writable;
+ (datItem as Disk).ItemStatus = itemStatus ?? ItemStatus.None;
+ (datItem as Disk).Optional = optional;
+ }
+ else if (itemType == ItemType.Release)
+ {
+ (datItem as Release).Region = region;
+ (datItem as Release).Language = language;
+ (datItem as Release).Date = date;
+ (datItem as Release).Default = def;
+ }
+ else if (itemType == ItemType.Rom)
+ {
+ (datItem as Rom).Bios = bios;
+ (datItem as Rom).Size = size;
+ (datItem as Rom).CRC = crc;
+ (datItem as Rom).MD5 = md5;
+ (datItem as Rom).RIPEMD160 = ripemd160;
+ (datItem as Rom).SHA1 = sha1;
+ (datItem as Rom).SHA256 = sha256;
+ (datItem as Rom).SHA384 = sha384;
+ (datItem as Rom).SHA512 = sha512;
+ (datItem as Rom).MergeTag = merge;
+ (datItem as Rom).Region = region;
+ (datItem as Rom).Offset = offset;
+ (datItem as Rom).Date = date;
+ (datItem as Rom).ItemStatus = itemStatus ?? ItemStatus.None;
+ (datItem as Rom).Optional = optional;
+ }
+
+ ParseAddHelper(datItem, clean, remUnicode);
+
+ return;
+ }
+
+ // We don't care about anything except properties
+ if (jtr.TokenType != JsonToken.PropertyName)
+ {
+ jtr.Read();
+ continue;
+ }
+
+ switch (jtr.Value)
+ {
+ case "type":
+ itemType = Utilities.GetItemType(jtr.ReadAsString());
+ break;
+
+ case "name":
+ name = jtr.ReadAsString();
+ break;
+
+ case "partname":
+ partName = jtr.ReadAsString();
+ break;
+
+ case "partinterface":
+ partInterface = jtr.ReadAsString();
+ break;
+
+ case "features":
+ features = new List>();
+ jtr.Read(); // Start Array
+ while (!sr.EndOfStream)
+ {
+ jtr.Read(); // Start object (or end array)
+ if (jtr.TokenType == JsonToken.EndArray)
+ break;
+
+ jtr.Read(); // Key
+ string key = jtr.Value as string;
+ string value = jtr.ReadAsString();
+ jtr.Read(); // End object
+
+ features.Add(new KeyValuePair(key, value));
+ }
+
+ break;
+
+ case "areaname":
+ areaName = jtr.ReadAsString();
+ break;
+
+ case "areasize":
+ if (Int64.TryParse(jtr.ReadAsString(), out long tempAreaSize))
+ areaSize = tempAreaSize;
+ else
+ areaSize = null;
+
+ break;
+
+ case "description":
+ biosDescription = jtr.ReadAsString();
+ break;
+
+ case "default":
+ def = jtr.ReadAsBoolean();
+ break;
+
+ case "region":
+ region = jtr.ReadAsString();
+ break;
+
+ case "language":
+ language = jtr.ReadAsString();
+ break;
+
+ case "date":
+ date = jtr.ReadAsString();
+ break;
+
+ case "size":
+ if (!Int64.TryParse(jtr.ReadAsString(), out size))
+ size = -1;
+
+ break;
+
+ case "crc":
+ crc = jtr.ReadAsString();
+ break;
+
+ case "md5":
+ md5 = jtr.ReadAsString();
+ break;
+
+ case "ripemd160":
+ ripemd160 = jtr.ReadAsString();
+ break;
+
+ case "sha1":
+ sha1 = jtr.ReadAsString();
+ break;
+
+ case "sha256":
+ sha256 = jtr.ReadAsString();
+ break;
+
+ case "sha384":
+ sha384 = jtr.ReadAsString();
+ break;
+
+ case "sha512":
+ sha512 = jtr.ReadAsString();
+ break;
+
+ case "merge":
+ merge = jtr.ReadAsString();
+ break;
+
+ case "index":
+ index = jtr.ReadAsString();
+ break;
+
+ case "writable":
+ writable = jtr.ReadAsBoolean();
+ break;
+
+ case "status":
+ itemStatus = Utilities.GetItemStatus(jtr.ReadAsString());
+ break;
+
+ case "optional":
+ optional = jtr.ReadAsBoolean();
+ break;
+
+ case "offset":
+ offset = jtr.ReadAsString();
+ break;
+
+ case "bios":
+ bios = jtr.ReadAsString();
+ break;
+
+ default:
+ break;
+ }
+
+ jtr.Read();
+ }
+ }
+
///
/// Create and open an output file for writing direct from a dictionary
///
diff --git a/SabreTools.Library/Tools/Utilities.cs b/SabreTools.Library/Tools/Utilities.cs
index a0c961a0..d38877eb 100644
--- a/SabreTools.Library/Tools/Utilities.cs
+++ b/SabreTools.Library/Tools/Utilities.cs
@@ -2806,6 +2806,7 @@ namespace SabreTools.Library.Tools
{
case "csv":
case "dat":
+ case "json":
case "md5":
case "ripemd160":
case "sfv":