Files
SabreTools/SabreTools.Library/DatFiles/Json.cs

532 lines
18 KiB
C#
Raw Normal View History

2020-06-15 22:31:46 -07:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Library.Data;
using SabreTools.Library.DatItems;
2020-08-01 23:04:11 -07:00
using SabreTools.Library.IO;
2020-06-15 22:31:46 -07:00
using SabreTools.Library.Tools;
using Newtonsoft.Json;
2020-08-24 11:56:49 -07:00
using Newtonsoft.Json.Linq;
2020-06-15 22:31:46 -07:00
namespace SabreTools.Library.DatFiles
{
/// <summary>
/// Represents parsing and writing of a JSON DAT
/// </summary>
internal class Json : DatFile
{
/// <summary>
/// Constructor designed for casting a base DatFile
/// </summary>
/// <param name="datFile">Parent DatFile to copy from</param>
public Json(DatFile datFile)
: base(datFile)
2020-06-15 22:31:46 -07:00
{
}
2020-06-16 11:27:36 -07:00
/// <summary>
/// Parse a Logiqx XML DAT and return all found games and roms within
/// </summary>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
2020-06-16 11:27:36 -07:00
/// <param name="keep">True if full pathnames are to be kept, false otherwise (default)</param>
protected override void ParseFile(
2020-06-16 11:27:36 -07:00
// Standard Dat parsing
string filename,
int indexId,
2020-06-16 11:27:36 -07:00
// Miscellaneous
bool keep)
2020-06-16 11:27:36 -07:00
{
// Prepare all internal variables
StreamReader sr = new StreamReader(FileExtensions.TryOpenRead(filename), new UTF8Encoding(false));
2020-06-16 11:27:36 -07:00
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":
2020-08-24 11:56:49 -07:00
ReadHeader(jtr);
2020-06-16 11:27:36 -07:00
jtr.Read();
break;
// Machine array
case "machines":
2020-08-24 14:29:00 -07:00
ReadMachines(jtr, filename, indexId);
2020-06-16 11:27:36 -07:00
jtr.Read();
break;
default:
jtr.Read();
break;
}
}
}
catch (Exception ex)
{
Globals.Logger.Warning($"Exception found while parsing '{filename}': {ex}");
}
jtr.Close();
}
/// <summary>
/// Read header information
/// </summary>
/// <param name="jtr">JsonTextReader to use to parse the header</param>
2020-08-24 11:56:49 -07:00
private void ReadHeader(JsonTextReader jtr)
2020-06-16 11:27:36 -07:00
{
// If the reader is invalid, skip
if (jtr == null)
return;
2020-08-24 11:56:49 -07:00
// Read in the header and apply any new fields
2020-06-16 11:27:36 -07:00
jtr.Read();
2020-08-24 11:56:49 -07:00
JsonSerializer js = new JsonSerializer();
DatHeader header = js.Deserialize<DatHeader>(jtr);
Header.ConditionalCopy(header);
2020-06-16 11:27:36 -07:00
}
/// <summary>
/// Read machine array information
/// </summary>
/// <param name="itr">JsonTextReader to use to parse the machine</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
2020-06-16 11:27:36 -07:00
private void ReadMachines(
JsonTextReader jtr,
// Standard Dat parsing
string filename,
int indexId)
2020-06-16 11:27:36 -07:00
{
// If the reader is invalid, skip
if (jtr == null)
return;
2020-08-24 14:29:00 -07:00
// Read in the machine array
2020-06-16 11:27:36 -07:00
jtr.Read();
2020-08-24 14:29:00 -07:00
JsonSerializer js = new JsonSerializer();
JArray machineArray = js.Deserialize<JArray>(jtr);
2020-06-16 11:27:36 -07:00
2020-08-24 14:29:00 -07:00
// Loop through each machine object and process
foreach (JObject machineObj in machineArray)
{
ReadMachine(machineObj, filename, indexId);
2020-06-16 11:27:36 -07:00
}
}
/// <summary>
2020-08-24 11:56:49 -07:00
/// Read machine object information
2020-06-16 11:27:36 -07:00
/// </summary>
2020-08-24 14:29:00 -07:00
/// <param name="machineObj">JObject representing a single machine</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
2020-06-16 11:27:36 -07:00
private void ReadMachine(
2020-08-24 14:29:00 -07:00
JObject machineObj,
2020-06-16 11:27:36 -07:00
// Standard Dat parsing
string filename,
int indexId)
2020-06-16 11:27:36 -07:00
{
2020-08-24 14:29:00 -07:00
// If object is invalid, skip it
if (machineObj == null)
2020-06-16 11:27:36 -07:00
return;
// Prepare internal variables
2020-08-24 11:56:49 -07:00
JsonSerializer js = new JsonSerializer();
Machine machine = null;
2020-06-16 11:27:36 -07:00
2020-08-24 14:29:00 -07:00
// Read the machine info, if possible
if (machineObj.ContainsKey("machine"))
machine = machineObj["machine"].ToObject<Machine>();
2020-06-16 11:27:36 -07:00
2020-08-24 14:29:00 -07:00
// Read items, if possible
if (machineObj.ContainsKey("items"))
ReadItems(machineObj["items"] as JArray, filename, indexId, machine);
2020-06-16 11:27:36 -07:00
}
/// <summary>
/// Read item array information
/// </summary>
2020-08-24 14:29:00 -07:00
/// <param name="itemsArr">JArray representing the items list</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="machine">Machine information to add to the parsed items</param>
2020-06-16 11:27:36 -07:00
private void ReadItems(
2020-08-24 14:29:00 -07:00
JArray itemsArr,
2020-06-16 11:27:36 -07:00
// Standard Dat parsing
string filename,
int indexId,
2020-06-16 11:27:36 -07:00
// Miscellaneous
Machine machine)
{
2020-08-24 14:29:00 -07:00
// If the array is invalid, skip
if (itemsArr == null)
2020-06-16 11:27:36 -07:00
return;
2020-08-24 14:29:00 -07:00
// Loop through each datitem object and process
foreach (JObject itemObj in itemsArr)
2020-06-16 11:27:36 -07:00
{
2020-08-24 14:29:00 -07:00
ReadItem(itemObj, filename, indexId, machine);
2020-06-16 11:27:36 -07:00
}
}
/// <summary>
/// Read item information
/// </summary>
2020-08-24 14:29:00 -07:00
/// <param name="machineObj">JObject representing a single datitem</param>
/// <param name="filename">Name of the file to be parsed</param>
/// <param name="indexId">Index ID for the DAT</param>
/// <param name="machine">Machine information to add to the parsed items</param>
2020-06-16 11:27:36 -07:00
private void ReadItem(
2020-08-24 14:29:00 -07:00
JObject itemObj,
2020-06-16 11:27:36 -07:00
// Standard Dat parsing
string filename,
int indexId,
2020-06-16 11:27:36 -07:00
// Miscellaneous
Machine machine)
{
2020-08-24 14:29:00 -07:00
// If we have an empty item, skip it
if (itemObj == null)
2020-06-16 11:27:36 -07:00
return;
// Prepare internal variables
2020-08-24 11:56:49 -07:00
DatItem datItem = null;
2020-06-16 11:27:36 -07:00
2020-08-24 14:29:00 -07:00
// Read the datitem info, if possible
if (itemObj.ContainsKey("datitem"))
2020-06-16 11:27:36 -07:00
{
2020-08-24 14:29:00 -07:00
JToken datItemObj = itemObj["datitem"];
switch (datItemObj.Value<string>("type").AsItemType())
2020-06-16 11:27:36 -07:00
{
2020-08-24 14:29:00 -07:00
case ItemType.Archive:
datItem = datItemObj.ToObject<Archive>();
2020-06-16 11:27:36 -07:00
break;
2020-08-24 14:29:00 -07:00
case ItemType.BiosSet:
datItem = datItemObj.ToObject<BiosSet>();
break;
case ItemType.Blank:
datItem = datItemObj.ToObject<Blank>();
break;
case ItemType.Disk:
datItem = datItemObj.ToObject<Disk>();
break;
case ItemType.Release:
datItem = datItemObj.ToObject<Release>();
break;
case ItemType.Rom:
datItem = datItemObj.ToObject<Rom>();
break;
case ItemType.Sample:
datItem = datItemObj.ToObject<Sample>();
2020-08-20 21:15:37 -07:00
break;
2020-08-24 11:56:49 -07:00
}
2020-08-24 14:29:00 -07:00
}
2020-08-20 21:15:37 -07:00
2020-08-24 14:29:00 -07:00
// If we got a valid datitem, copy machine info and add
if (datItem != null)
{
datItem.CopyMachineInformation(machine);
datItem.Source = new Source { Index = indexId, Name = filename };
ParseAddHelper(datItem);
2020-08-24 11:56:49 -07:00
}
}
2020-08-20 21:15:37 -07:00
2020-08-24 11:56:49 -07:00
/// <summary>
/// Create and open an output file for writing direct from a dictionary
/// </summary>
/// <param name="outfile">Name of the file to write to</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the DAT was written correctly, false otherwise</returns>
public override bool WriteToFile(string outfile, bool ignoreblanks = false)
{
try
{
Globals.Logger.User($"Opening file for writing: {outfile}");
FileStream fs = FileExtensions.TryCreate(outfile);
2020-08-24 11:56:49 -07:00
// If we get back null for some reason, just log and return
if (fs == null)
{
Globals.Logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
return false;
}
2020-06-15 22:31:46 -07:00
StreamWriter sw = new StreamWriter(fs, new UTF8Encoding(false));
JsonTextWriter jtw = new JsonTextWriter(sw)
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};
2020-06-15 22:31:46 -07:00
// Write out the header
WriteHeader(jtw);
// Write out each of the machines and roms
string lastgame = null;
2020-07-26 21:00:30 -07:00
// Use a sorted list of games to output
2020-07-26 22:34:45 -07:00
foreach (string key in Items.SortedKeys)
2020-06-15 22:31:46 -07:00
{
2020-07-26 22:34:45 -07:00
List<DatItem> roms = Items[key];
2020-06-15 22:31:46 -07:00
// Resolve the names in the block
roms = DatItem.ResolveNames(roms);
for (int index = 0; index < roms.Count; index++)
{
DatItem rom = roms[index];
// There are apparently times when a null rom can skip by, skip them
2020-08-20 13:17:14 -07:00
if (rom.Name == null || rom.Machine.Name == null)
2020-06-15 22:31:46 -07:00
{
Globals.Logger.Warning("Null rom found!");
continue;
}
// If we have a different game and we're not at the start of the list, output the end of last item
2020-08-20 13:17:14 -07:00
if (lastgame != null && lastgame.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
2020-06-15 22:31:46 -07:00
WriteEndGame(jtw);
// If we have a new game, output the beginning of the new item
2020-08-20 13:17:14 -07:00
if (lastgame == null || lastgame.ToLowerInvariant() != rom.Machine.Name.ToLowerInvariant())
2020-06-15 22:31:46 -07:00
WriteStartGame(jtw, rom);
// If we have a "null" game (created by DATFromDir or something similar), log it to file
if (rom.ItemType == ItemType.Rom
&& ((Rom)rom).Size == -1
&& ((Rom)rom).CRC == "null")
{
2020-08-20 13:17:14 -07:00
Globals.Logger.Verbose($"Empty folder found: {rom.Machine.Name}");
2020-06-15 22:31:46 -07:00
rom.Name = (rom.Name == "null" ? "-" : rom.Name);
((Rom)rom).Size = Constants.SizeZero;
((Rom)rom).CRC = ((Rom)rom).CRC == "null" ? Constants.CRCZero : null;
((Rom)rom).MD5 = ((Rom)rom).MD5 == "null" ? Constants.MD5Zero : null;
#if NET_FRAMEWORK
2020-06-15 22:31:46 -07:00
((Rom)rom).RIPEMD160 = ((Rom)rom).RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null;
#endif
2020-06-15 22:31:46 -07:00
((Rom)rom).SHA1 = ((Rom)rom).SHA1 == "null" ? Constants.SHA1Zero : null;
((Rom)rom).SHA256 = ((Rom)rom).SHA256 == "null" ? Constants.SHA256Zero : null;
((Rom)rom).SHA384 = ((Rom)rom).SHA384 == "null" ? Constants.SHA384Zero : null;
((Rom)rom).SHA512 = ((Rom)rom).SHA512 == "null" ? Constants.SHA512Zero : null;
}
// Now, output the rom data
WriteDatItem(jtw, rom, ignoreblanks);
// Set the new data to compare against
2020-08-20 13:17:14 -07:00
lastgame = rom.Machine.Name;
2020-06-15 22:31:46 -07:00
}
}
// Write the file footer out
WriteFooter(jtw);
Globals.Logger.Verbose("File written!" + Environment.NewLine);
jtw.Close();
fs.Dispose();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT header using the supplied StreamWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private bool WriteHeader(JsonTextWriter jtw)
{
try
{
jtw.WriteStartObject();
2020-08-24 11:56:49 -07:00
// Write the DatHeader
2020-06-15 22:31:46 -07:00
jtw.WritePropertyName("header");
2020-08-24 11:56:49 -07:00
JsonSerializer js = new JsonSerializer() { Formatting = Formatting.Indented };
js.Serialize(jtw, Header);
2020-06-15 22:31:46 -07:00
jtw.WritePropertyName("machines");
jtw.WriteStartArray();
jtw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game start using the supplied StreamWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
/// <returns>True if the data was written, false on error</returns>
private bool WriteStartGame(JsonTextWriter jtw, DatItem datItem)
{
try
{
// No game should start with a path separator
2020-08-20 13:17:14 -07:00
datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar);
2020-06-15 22:31:46 -07:00
2020-08-23 22:23:55 -07:00
// Build the state
2020-06-15 22:31:46 -07:00
jtw.WriteStartObject();
2020-08-24 11:56:49 -07:00
// Write the Machine
jtw.WritePropertyName("machine");
JsonSerializer js = new JsonSerializer() { Formatting = Formatting.Indented };
js.Serialize(jtw, datItem.Machine);
2020-08-20 22:42:04 -07:00
2020-06-15 22:31:46 -07:00
jtw.WritePropertyName("items");
jtw.WriteStartArray();
jtw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out Game end using the supplied StreamWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private bool WriteEndGame(JsonTextWriter jtw)
{
try
{
// End items
jtw.WriteEndArray();
// End machine
jtw.WriteEndObject();
jtw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DatItem using the supplied StreamWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <param name="datItem">DatItem object to be output</param>
/// <param name="ignoreblanks">True if blank roms should be skipped on output, false otherwise (default)</param>
/// <returns>True if the data was written, false on error</returns>
private bool WriteDatItem(JsonTextWriter jtw, DatItem datItem, bool ignoreblanks = false)
{
// If we are in ignore blanks mode AND we have a blank (0-size) rom, skip
if (ignoreblanks && (datItem.ItemType == ItemType.Rom && ((datItem as Rom).Size == 0 || (datItem as Rom).Size == -1)))
return true;
// If we have the blank item type somehow, skip
if (datItem.ItemType == ItemType.Blank)
return true;
try
{
// Pre-process the item name
ProcessItemName(datItem, true);
2020-08-23 22:23:55 -07:00
// Build the state
2020-06-15 22:31:46 -07:00
jtw.WriteStartObject();
2020-08-24 11:56:49 -07:00
// Write the DatItem
jtw.WritePropertyName("datitem");
JsonSerializer js = new JsonSerializer() { ContractResolver = new BaseFirstContractResolver(), Formatting = Formatting.Indented };
js.Serialize(jtw, datItem);
2020-06-15 22:31:46 -07:00
// End item
jtw.WriteEndObject();
jtw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// Write out DAT footer using the supplied StreamWriter
/// </summary>
/// <param name="jtw">JsonTextWriter to output to</param>
/// <returns>True if the data was written, false on error</returns>
private bool WriteFooter(JsonTextWriter jtw)
{
try
{
// End items
jtw.WriteEndArray();
// End machine
jtw.WriteEndObject();
// End machines
jtw.WriteEndArray();
// End file
jtw.WriteEndObject();
jtw.Flush();
}
catch (Exception ex)
{
Globals.Logger.Error(ex.ToString());
return false;
}
return true;
}
}
}