mirror of
https://github.com/claunia/SabreTools.git
synced 2025-12-16 19:14:27 +00:00
Split SoftwareList with no other updates
This commit is contained in:
496
SabreTools.DatFiles/Formats/SoftwareList.Reader.cs
Normal file
496
SabreTools.DatFiles/Formats/SoftwareList.Reader.cs
Normal file
@@ -0,0 +1,496 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Schema;
|
||||
using SabreTools.Core;
|
||||
using SabreTools.Core.Tools;
|
||||
using SabreTools.DatItems;
|
||||
using SabreTools.DatItems.Formats;
|
||||
|
||||
namespace SabreTools.DatFiles.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parsing a SoftwareList
|
||||
/// </summary>
|
||||
internal partial class SoftwareList : DatFile
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false)
|
||||
{
|
||||
// Prepare all internal variables
|
||||
XmlReader xtr = XmlReader.Create(filename, new XmlReaderSettings
|
||||
{
|
||||
CheckCharacters = false,
|
||||
DtdProcessing = DtdProcessing.Ignore,
|
||||
IgnoreComments = true,
|
||||
IgnoreWhitespace = true,
|
||||
ValidationFlags = XmlSchemaValidationFlags.None,
|
||||
ValidationType = ValidationType.None,
|
||||
});
|
||||
|
||||
// If we got a null reader, just return
|
||||
if (xtr == null)
|
||||
return;
|
||||
|
||||
// Otherwise, read the file to the end
|
||||
try
|
||||
{
|
||||
xtr.MoveToContent();
|
||||
while (!xtr.EOF)
|
||||
{
|
||||
// We only want elements
|
||||
if (xtr.NodeType != XmlNodeType.Element)
|
||||
{
|
||||
xtr.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (xtr.Name)
|
||||
{
|
||||
case "softwarelist":
|
||||
Header.Name ??= xtr.GetAttribute("name") ?? string.Empty;
|
||||
Header.Description ??= xtr.GetAttribute("description") ?? string.Empty;
|
||||
|
||||
xtr.Read();
|
||||
break;
|
||||
|
||||
// We want to process the entire subtree of the machine
|
||||
case "software":
|
||||
ReadSoftware(xtr.ReadSubtree(), statsOnly, filename, indexId);
|
||||
|
||||
// Skip the software now that we've processed it
|
||||
xtr.Skip();
|
||||
break;
|
||||
|
||||
default:
|
||||
xtr.Read();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
logger.Warning(ex, $"Exception found while parsing '{filename}'");
|
||||
|
||||
// For XML errors, just skip the affected node
|
||||
xtr?.Read();
|
||||
}
|
||||
|
||||
xtr.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read software information
|
||||
/// </summary>
|
||||
/// <param name="reader">XmlReader representing a software block</param>
|
||||
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
|
||||
/// <param name="filename">Name of the file to be parsed</param>
|
||||
/// <param name="indexId">Index ID for the DAT</param>
|
||||
private void ReadSoftware(XmlReader reader, bool statsOnly, string filename, int indexId)
|
||||
{
|
||||
// If we have an empty software, skip it
|
||||
if (reader == null)
|
||||
return;
|
||||
|
||||
// Otherwise, add what is possible
|
||||
reader.MoveToContent();
|
||||
|
||||
bool containsItems = false;
|
||||
|
||||
// Create a new machine
|
||||
Machine machine = new()
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
CloneOf = reader.GetAttribute("cloneof"),
|
||||
Supported = reader.GetAttribute("supported").AsSupported(),
|
||||
};
|
||||
|
||||
while (!reader.EOF)
|
||||
{
|
||||
// We only want elements
|
||||
if (reader.NodeType != XmlNodeType.Element)
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the elements from the software
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "description":
|
||||
machine.Description = reader.ReadElementContentAsString();
|
||||
break;
|
||||
|
||||
case "year":
|
||||
machine.Year = reader.ReadElementContentAsString();
|
||||
break;
|
||||
|
||||
case "publisher":
|
||||
machine.Publisher = reader.ReadElementContentAsString();
|
||||
break;
|
||||
|
||||
case "info":
|
||||
ParseAddHelper(new Info
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Value = reader.GetAttribute("value"),
|
||||
|
||||
Source = new Source
|
||||
{
|
||||
Index = indexId,
|
||||
Name = filename,
|
||||
},
|
||||
}, statsOnly);
|
||||
|
||||
reader.Read();
|
||||
break;
|
||||
|
||||
case "sharedfeat":
|
||||
ParseAddHelper(new SharedFeature
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Value = reader.GetAttribute("value"),
|
||||
|
||||
Source = new Source
|
||||
{
|
||||
Index = indexId,
|
||||
Name = filename,
|
||||
},
|
||||
}, statsOnly);
|
||||
|
||||
reader.Read();
|
||||
break;
|
||||
|
||||
case "part": // Contains all rom and disk information
|
||||
var part = new Part()
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Interface = reader.GetAttribute("interface"),
|
||||
};
|
||||
|
||||
// Now read the internal tags
|
||||
containsItems = ReadPart(reader.ReadSubtree(), machine, part, statsOnly, filename, indexId);
|
||||
|
||||
// Skip the part now that we've processed it
|
||||
reader.Skip();
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Read();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no items were found for this machine, add a Blank placeholder
|
||||
if (!containsItems)
|
||||
{
|
||||
Blank blank = new()
|
||||
{
|
||||
Source = new Source
|
||||
{
|
||||
Index = indexId,
|
||||
Name = filename,
|
||||
},
|
||||
};
|
||||
|
||||
blank.CopyMachineInformation(machine);
|
||||
|
||||
// Now process and add the rom
|
||||
ParseAddHelper(blank, statsOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read part information
|
||||
/// </summary>
|
||||
/// <param name="reader">XmlReader representing a part block</param>
|
||||
/// <param name="machine">Machine information to pass to contained items</param>
|
||||
/// <param name="part">Part information to pass to contained items</param>
|
||||
/// <param name="statsOnly">True to only add item statistics while parsing, false otherwise</param>
|
||||
/// <param name="filename">Name of the file to be parsed</param>
|
||||
/// <param name="indexId">Index ID for the DAT</param>
|
||||
private bool ReadPart(XmlReader reader, Machine machine, Part part, bool statsOnly, string filename, int indexId)
|
||||
{
|
||||
// If we have an empty port, skip it
|
||||
if (reader == null)
|
||||
return false;
|
||||
|
||||
// Get lists ready
|
||||
part.Features = new List<PartFeature>();
|
||||
List<DatItem> items = new();
|
||||
|
||||
while (!reader.EOF)
|
||||
{
|
||||
// We only want elements
|
||||
if (reader.NodeType != XmlNodeType.Element)
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the elements from the software
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "feature":
|
||||
var feature = new PartFeature()
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Value = reader.GetAttribute("value"),
|
||||
};
|
||||
|
||||
part.Features.Add(feature);
|
||||
|
||||
reader.Read();
|
||||
break;
|
||||
|
||||
case "dataarea":
|
||||
var dataArea = new DataArea
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Size = Utilities.CleanLong(reader.GetAttribute("size")),
|
||||
Width = Utilities.CleanLong(reader.GetAttribute("width")),
|
||||
Endianness = reader.GetAttribute("endianness").AsEndianness(),
|
||||
};
|
||||
|
||||
List<DatItem> roms = ReadDataArea(reader.ReadSubtree(), dataArea);
|
||||
|
||||
// If we got valid roms, add them to the list
|
||||
if (roms != null)
|
||||
items.AddRange(roms);
|
||||
|
||||
// Skip the dataarea now that we've processed it
|
||||
reader.Skip();
|
||||
break;
|
||||
|
||||
case "diskarea":
|
||||
var diskArea = new DiskArea
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
};
|
||||
|
||||
List<DatItem> disks = ReadDiskArea(reader.ReadSubtree(), diskArea);
|
||||
|
||||
// If we got valid disks, add them to the list
|
||||
if (disks != null)
|
||||
items.AddRange(disks);
|
||||
|
||||
// Skip the diskarea now that we've processed it
|
||||
reader.Skip();
|
||||
break;
|
||||
|
||||
case "dipswitch":
|
||||
var dipSwitch = new DipSwitch
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Tag = reader.GetAttribute("tag"),
|
||||
Mask = reader.GetAttribute("mask"),
|
||||
};
|
||||
|
||||
// Now read the internal tags
|
||||
ReadDipSwitch(reader.ReadSubtree(), dipSwitch);
|
||||
|
||||
items.Add(dipSwitch);
|
||||
|
||||
// Skip the dipswitch now that we've processed it
|
||||
reader.Skip();
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Read();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all of the items, if they exist
|
||||
string key = string.Empty;
|
||||
foreach (DatItem item in items)
|
||||
{
|
||||
// Add all missing information
|
||||
switch (item.ItemType)
|
||||
{
|
||||
case ItemType.DipSwitch:
|
||||
(item as DipSwitch).Part = part;
|
||||
break;
|
||||
case ItemType.Disk:
|
||||
(item as Disk).Part = part;
|
||||
break;
|
||||
case ItemType.Rom:
|
||||
(item as Rom).Part = part;
|
||||
|
||||
// If the rom is continue or ignore, add the size to the previous rom
|
||||
// TODO: Can this be done on write? We technically lose information this way.
|
||||
// Order is not guaranteed, and since these don't tend to have any way
|
||||
// of determining what the "previous" item was after this, that info would
|
||||
// have to be stored *with* the item somehow
|
||||
if ((item as Rom).LoadFlag == LoadFlag.Continue || (item as Rom).LoadFlag == LoadFlag.Ignore)
|
||||
{
|
||||
int index = Items[key].Count - 1;
|
||||
DatItem lastrom = Items[key][index];
|
||||
if (lastrom.ItemType == ItemType.Rom)
|
||||
{
|
||||
(lastrom as Rom).Size += (item as Rom).Size;
|
||||
Items[key].RemoveAt(index);
|
||||
Items[key].Add(lastrom);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
item.Source = new Source(indexId, filename);
|
||||
item.CopyMachineInformation(machine);
|
||||
|
||||
// Finally add each item
|
||||
key = ParseAddHelper(item, statsOnly);
|
||||
}
|
||||
|
||||
return items.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read dataarea information
|
||||
/// </summary>
|
||||
/// <param name="reader">XmlReader representing a dataarea block</param>
|
||||
/// <param name="dataArea">DataArea representing the enclosing area</param>
|
||||
private List<DatItem> ReadDataArea(XmlReader reader, DataArea dataArea)
|
||||
{
|
||||
List<DatItem> items = new();
|
||||
|
||||
while (!reader.EOF)
|
||||
{
|
||||
// We only want elements
|
||||
if (reader.NodeType != XmlNodeType.Element)
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the elements from the software
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "rom":
|
||||
var rom = new Rom
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Size = Utilities.CleanLong(reader.GetAttribute("size")),
|
||||
CRC = reader.GetAttribute("crc"),
|
||||
SHA1 = reader.GetAttribute("sha1"),
|
||||
Offset = reader.GetAttribute("offset"),
|
||||
Value = reader.GetAttribute("value"),
|
||||
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
|
||||
LoadFlag = reader.GetAttribute("loadflag").AsLoadFlag(),
|
||||
|
||||
DataArea = dataArea,
|
||||
};
|
||||
|
||||
items.Add(rom);
|
||||
reader.Read();
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Read();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read diskarea information
|
||||
/// </summary>
|
||||
/// <param name="reader">XmlReader representing a diskarea block</param>
|
||||
/// <param name="diskArea">DiskArea representing the enclosing area</param>
|
||||
private List<DatItem> ReadDiskArea(XmlReader reader, DiskArea diskArea)
|
||||
{
|
||||
List<DatItem> items = new();
|
||||
|
||||
while (!reader.EOF)
|
||||
{
|
||||
// We only want elements
|
||||
if (reader.NodeType != XmlNodeType.Element)
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the elements from the software
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "disk":
|
||||
DatItem disk = new Disk
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
SHA1 = reader.GetAttribute("sha1"),
|
||||
ItemStatus = reader.GetAttribute("status").AsItemStatus(),
|
||||
Writable = reader.GetAttribute("writable").AsYesNo(),
|
||||
|
||||
DiskArea = diskArea,
|
||||
};
|
||||
|
||||
items.Add(disk);
|
||||
reader.Read();
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Read();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read DipSwitch DipValues information
|
||||
/// </summary>
|
||||
/// <param name="reader">XmlReader representing a diskarea block</param>
|
||||
/// <param name="dipSwitch">DipSwitch to populate</param>
|
||||
private void ReadDipSwitch(XmlReader reader, DipSwitch dipSwitch)
|
||||
{
|
||||
// If we have an empty dipswitch, skip it
|
||||
if (reader == null)
|
||||
return;
|
||||
|
||||
// Get list ready
|
||||
dipSwitch.Values = new List<Setting>();
|
||||
|
||||
// Otherwise, add what is possible
|
||||
reader.MoveToContent();
|
||||
|
||||
while (!reader.EOF)
|
||||
{
|
||||
// We only want elements
|
||||
if (reader.NodeType != XmlNodeType.Element)
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the information from the dipswitch
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "dipvalue":
|
||||
var dipValue = new Setting
|
||||
{
|
||||
Name = reader.GetAttribute("name"),
|
||||
Value = reader.GetAttribute("value"),
|
||||
Default = reader.GetAttribute("default").AsYesNo(),
|
||||
};
|
||||
|
||||
dipSwitch.Values.Add(dipValue);
|
||||
|
||||
reader.Read();
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Read();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user