diff --git a/SabreTools.DatFiles/Formats/EverdriveSMDB.Reader.cs b/SabreTools.DatFiles/Formats/EverdriveSMDB.Reader.cs index 34b6c52e..630337ab 100644 --- a/SabreTools.DatFiles/Formats/EverdriveSMDB.Reader.cs +++ b/SabreTools.DatFiles/Formats/EverdriveSMDB.Reader.cs @@ -83,7 +83,7 @@ namespace SabreTools.DatFiles.Formats } /// - /// Convert rows information + /// Convert row information /// /// Deserialized model to convert /// Name of the file to be parsed diff --git a/SabreTools.DatFiles/Formats/EverdriveSMDB.Writer.cs b/SabreTools.DatFiles/Formats/EverdriveSMDB.Writer.cs index d3cdf667..ab3fddf8 100644 --- a/SabreTools.DatFiles/Formats/EverdriveSMDB.Writer.cs +++ b/SabreTools.DatFiles/Formats/EverdriveSMDB.Writer.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using SabreTools.Core; using SabreTools.DatItems; using SabreTools.DatItems.Formats; -using SabreTools.IO.Writers; namespace SabreTools.DatFiles.Formats { @@ -17,7 +15,10 @@ namespace SabreTools.DatFiles.Formats /// protected override ItemType[] GetSupportedTypes() { - return new ItemType[] { ItemType.Rom }; + return new ItemType[] + { + ItemType.Rom + }; } /// diff --git a/SabreTools.DatFiles/Formats/Listrom.Reader.cs b/SabreTools.DatFiles/Formats/Listrom.Reader.cs new file mode 100644 index 00000000..91219bc4 --- /dev/null +++ b/SabreTools.DatFiles/Formats/Listrom.Reader.cs @@ -0,0 +1,263 @@ +using System; +using System.Linq; +using SabreTools.Core; +using SabreTools.Core.Tools; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents parsing a MAME Listrom file + /// + internal partial class Listrom : DatFile + { + /// + public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) + { + try + { + // Deserialize the input file + var metadataFile = Serialization.Listrom.Deserialize(filename); + + // Convert the set data to the internal format + ConvertSets(metadataFile?.Set, filename, indexId, statsOnly); + } + catch (Exception ex) when (!throwOnError) + { + string message = $"'{filename}' - An error occurred during parsing"; + logger.Error(ex, message); + } + } + + #region Converters + + /// + /// Convert sets information + /// + /// Array of deserialized models to convert + /// Name of the file to be parsed + /// Index ID for the DAT + /// True to only add item statistics while parsing, false otherwise + private void ConvertSets(Models.Listrom.Set[]? sets, string filename, int indexId, bool statsOnly) + { + // If the rows array is missing, we can't do anything + if (sets == null || !sets.Any()) + return; + + // Loop through the sets and add + foreach (var set in sets) + { + ConvertSet(set, filename, indexId, statsOnly); + } + } + + /// + /// Convert set information + /// + /// Deserialized model to convert + /// Name of the file to be parsed + /// Index ID for the DAT + /// True to only add item statistics while parsing, false otherwise + private void ConvertSet(Models.Listrom.Set? set, string filename, int indexId, bool statsOnly) + { + // If the set is missing, we can't do anything + if (set == null) + return; + + // Create the machine + Machine machine; + if (!string.IsNullOrWhiteSpace(set.Device)) + { + machine = new Machine + { + Name = set.Device, + MachineType = MachineType.Device, + }; + } + else if (!string.IsNullOrWhiteSpace(set.Driver)) + { + machine = new Machine + { + Name = set.Driver, + MachineType = MachineType.None, + }; + } + else + { + return; + } + + foreach (var row in set.Row) + { + ConvertRow(row, machine, filename, indexId, statsOnly); + } + + + } + + /// + /// Convert row information + /// + /// Deserialized model to convert + /// Name of the file to be parsed + /// Index ID for the DAT + /// True to only add item statistics while parsing, false otherwise + private void ConvertRow(Models.Listrom.Row? row, Machine machine, string filename, int indexId, bool statsOnly) + { + // If the row is missing, we can't do anything + if (row == null) + return; + + DatItem item = null; + + // Normal CHD + if (row.Size == null + && !row.NoGoodDumpKnown + && !row.Bad + && (!string.IsNullOrWhiteSpace(row.MD5) + || !string.IsNullOrWhiteSpace(row.SHA1))) + { + item = new Disk + { + Name = row.Name, + ItemStatus = ItemStatus.None, + + Machine = machine, + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + if (!string.IsNullOrWhiteSpace(row.MD5)) + (item as Disk).MD5 = row.MD5; + else + (item as Disk).SHA1 = row.SHA1; + } + + // Normal ROM + else if (row.Size != null + && !row.NoGoodDumpKnown + && !row.Bad) + { + item = new Rom + { + Name = row.Name, + Size = Utilities.CleanLong(row.Size), + CRC = row.CRC, + SHA1 = row.SHA1, + ItemStatus = ItemStatus.None, + + Machine = machine, + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + } + + // Bad CHD + else if (row.Size == null + && !row.NoGoodDumpKnown + && row.Bad + && (!string.IsNullOrWhiteSpace(row.MD5) + || !string.IsNullOrWhiteSpace(row.SHA1))) + { + item = new Disk + { + Name = row.Name, + ItemStatus = ItemStatus.BadDump, + + Machine = machine, + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + if (!string.IsNullOrWhiteSpace(row.MD5)) + (item as Disk).MD5 = row.MD5; + else + (item as Disk).SHA1 = row.SHA1; + } + + // Nodump CHD + else if (row.Size == null + && row.NoGoodDumpKnown) + { + item = new Disk + { + Name = row.Name, + MD5 = null, + SHA1 = null, + ItemStatus = ItemStatus.Nodump, + + Machine = machine, + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + } + + // Bad ROM + else if (row.Size != null + && !row.NoGoodDumpKnown + && row.Bad) + { + item = new Rom + { + Name = row.Name, + Size = Utilities.CleanLong(row.Size), + CRC = row.CRC, + SHA1 = row.SHA1, + ItemStatus = ItemStatus.BadDump, + + Machine = machine, + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + } + + // Nodump ROM + else if (row.Size != null + && row.NoGoodDumpKnown) + { + item = new Rom + { + Name = row.Name, + Size = Utilities.CleanLong(row.Size), + CRC = null, + SHA1 = null, + ItemStatus = ItemStatus.Nodump, + + Machine = machine, + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + } + + // Now process and add the item + ParseAddHelper(item, statsOnly); + } + + #endregion + } +} diff --git a/SabreTools.DatFiles/Formats/Listrom.Writer.cs b/SabreTools.DatFiles/Formats/Listrom.Writer.cs new file mode 100644 index 00000000..d8ca951b --- /dev/null +++ b/SabreTools.DatFiles/Formats/Listrom.Writer.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SabreTools.Core; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents writing a MAME Listrom file + /// + internal partial class Listrom : DatFile + { + /// + protected override ItemType[] GetSupportedTypes() + { + return new ItemType[] + { + ItemType.Disk, + ItemType.Rom + }; + } + + /// + protected override List GetMissingRequiredFields(DatItem datItem) + { + List missingFields = new(); + + // Check item name + if (string.IsNullOrWhiteSpace(datItem.GetName())) + missingFields.Add(DatItemField.Name); + + switch (datItem) + { + case Disk disk: + if (string.IsNullOrWhiteSpace(disk.MD5) + && string.IsNullOrWhiteSpace(disk.SHA1)) + { + missingFields.Add(DatItemField.SHA1); + } + break; + + case Rom rom: + if (rom.Size == null || rom.Size < 0) + missingFields.Add(DatItemField.Size); + if (string.IsNullOrWhiteSpace(rom.CRC)) + missingFields.Add(DatItemField.CRC); + if (string.IsNullOrWhiteSpace(rom.SHA1)) + missingFields.Add(DatItemField.SHA1); + break; + } + + return null; + } + + /// + public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) + { + try + { + logger.User($"Writing to '{outfile}'..."); + + var metadataFile = CreateMetadataFile(ignoreblanks); + if (!Serialization.Listrom.SerializeToFile(metadataFile, outfile)) + { + logger.Warning($"File '{outfile}' could not be written! See the log for more details."); + return false; + } + } + catch (Exception ex) when (!throwOnError) + { + logger.Error(ex); + return false; + } + + return true; + } + + #region Converters + + /// + /// Create a MetadataFile from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.Listrom.MetadataFile CreateMetadataFile(bool ignoreblanks) + { + var metadataFile = new Models.Listrom.MetadataFile + { + Set = CreateSets(ignoreblanks) + }; + return metadataFile; + } + + /// + /// Create an array of Set from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.Listrom.Set[]? CreateSets(bool ignoreblanks) + { + // If we don't have items, we can't do anything + if (this.Items == null || !this.Items.Any()) + return null; + + // Create lists to hold the data + var sets = new List(); + var rows = new List(); + + // Loop through the sorted items and create games for them + foreach (string key in Items.SortedKeys) + { + var items = Items.FilteredItems(key); + if (items == null || !items.Any()) + continue; + + var set = new Models.Listrom.Set + { + Driver = !items[0].Machine.MachineType.HasFlag(MachineType.Device) ? items[0].Machine.Name : null, + Device = items[0].Machine.MachineType.HasFlag(MachineType.Device) ? items[0].Machine.Name : null, + }; + + // Loop through and convert the items to respective lists + foreach (var item in items) + { + // Skip if we're ignoring the item + if (ShouldIgnore(item, ignoreblanks)) + continue; + + switch (item) + { + case Disk disk: + rows.Add(CreateRow(disk)); + break; + case Rom rom: + rows.Add(CreateRow(rom)); + break; + } + } + + set.Row = rows.ToArray(); + sets.Add(set); + rows.Clear(); + } + + return sets.ToArray(); + } + + /// + /// Create a Row from the current Disk DatItem + /// + private static Models.Listrom.Row? CreateRow(Disk disk) + { + if (disk.ItemStatus == ItemStatus.Nodump) + { + return new Models.Listrom.Row + { + Name = disk.Name, + NoGoodDumpKnown = true, + }; + } + else if (disk.ItemStatus == ItemStatus.BadDump) + { + var row = new Models.Listrom.Row + { + Name = disk.Name, + Bad = true, + }; + + if (!string.IsNullOrWhiteSpace(disk.MD5)) + row.MD5 = disk.MD5; + else + row.SHA1 = disk.SHA1; + + return row; + } + else + { + var row = new Models.Listrom.Row + { + Name = disk.Name, + }; + + if (!string.IsNullOrWhiteSpace(disk.MD5)) + row.MD5 = disk.MD5; + else + row.SHA1 = disk.SHA1; + + return row; + } + } + + /// + /// Create a Row from the current Rom DatItem + /// + private static Models.Listrom.Row? CreateRow(Rom rom) + { + if (rom.ItemStatus == ItemStatus.Nodump) + { + return new Models.Listrom.Row + { + Name = rom.Name, + Size = rom.Size?.ToString(), + NoGoodDumpKnown = true, + }; + } + else if (rom.ItemStatus == ItemStatus.BadDump) + { + return new Models.Listrom.Row + { + Name = rom.Name, + Size = rom.Size?.ToString(), + Bad = true, + CRC = rom.CRC, + SHA1 = rom.SHA1, + }; + } + else + { + return new Models.Listrom.Row + { + Name = rom.Name, + Size = rom.Size?.ToString(), + CRC = rom.CRC, + SHA1 = rom.SHA1, + }; + } + } + + #endregion + } +} diff --git a/SabreTools.DatFiles/Formats/Listrom.cs b/SabreTools.DatFiles/Formats/Listrom.cs index 4d96f63d..cbe73aff 100644 --- a/SabreTools.DatFiles/Formats/Listrom.cs +++ b/SabreTools.DatFiles/Formats/Listrom.cs @@ -1,20 +1,9 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using SabreTools.Core; -using SabreTools.Core.Tools; -using SabreTools.DatItems; -using SabreTools.DatItems.Formats; -using SabreTools.IO; - -namespace SabreTools.DatFiles.Formats +namespace SabreTools.DatFiles.Formats { /// - /// Represents parsing and writing of a MAME Listrom DAT + /// Represents a MAME Listrom file /// - internal class Listrom : DatFile + internal partial class Listrom : DatFile { /// /// Constructor designed for casting a base DatFile @@ -24,436 +13,5 @@ namespace SabreTools.DatFiles.Formats : base(datFile) { } - - /// - /// - /// In a new style MAME listrom DAT, each game has the following format: - /// - /// ROMs required for driver "testdriver". - /// Name Size Checksum - /// abcd.bin 1024 CRC(00000000) SHA1(da39a3ee5e6b4b0d3255bfef95601890afd80709) - /// efgh.bin 1024 BAD CRC(00000000) SHA1(da39a3ee5e6b4b0d3255bfef95601890afd80709) BAD_DUMP - /// ijkl.bin 1024 NO GOOD DUMP KNOWN - /// abcd SHA1(da39a3ee5e6b4b0d3255bfef95601890afd80709) - /// efgh BAD (da39a3ee5e6b4b0d3255bfef95601890afd80709) BAD_DUMP - /// ijkl NO GOOD DUMP KNOWN - /// - public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) - { - // Open a file reader - Encoding enc = filename.GetEncoding(); - StreamReader sr = new(System.IO.File.OpenRead(filename), enc); - - string gamename = string.Empty; - while (!sr.EndOfStream) - { - try - { - string line = sr.ReadLine().Trim(); - - // If we have a blank line, we just skip it - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - // If we have the descriptor line, ignore it - else if (line == "Name Size Checksum") - { - continue; - } - - // If we have the beginning of a game, set the name of the game - else if (line.StartsWith("ROMs required for")) - { - gamename = Regex.Match(line, @"^ROMs required for \S*? ""(.*?)""\.").Groups[1].Value; - } - - // If we have a machine with no required roms (usually internal devices), skip it - else if (line.StartsWith("No ROMs required for")) - { - continue; - } - - // Otherwise, we assume we have a rom that we need to add - else - { - // First, we preprocess the line so that the rom name is consistently correct - string[] split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); - - // If the line doesn't have the 4 spaces of padding, check for 3 - if (split.Length == 1) - split = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); - - // If the split is still unsuccessful, log it and skip - if (split.Length == 1) - logger.Warning($"Possibly malformed line: '{line}'"); - - string romname = split[0]; - line = line[romname.Length..]; - - // Next we separate the ROM into pieces - split = line.Split(Array.Empty(), StringSplitOptions.RemoveEmptyEntries); - - // Standard Disks have 2 pieces (name, sha1) - if (split.Length == 1) - { - Disk disk = new() - { - Name = romname, - SHA1 = CleanListromHashData(split[0]), - - Machine = new Machine - { - Name = gamename, - }, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - ParseAddHelper(disk, statsOnly); - } - - // Baddump Disks have 4 pieces (name, BAD, sha1, BAD_DUMP) - else if (split.Length == 3 && line.EndsWith("BAD_DUMP")) - { - Disk disk = new() - { - Name = romname, - SHA1 = CleanListromHashData(split[1]), - ItemStatus = ItemStatus.BadDump, - - Machine = new Machine - { - Name = gamename, - }, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - ParseAddHelper(disk, statsOnly); - } - - // Standard ROMs have 4 pieces (name, size, crc, sha1) - else if (split.Length == 3) - { - Rom rom = new() - { - Name = romname, - Size = Utilities.CleanLong(split[0]), - CRC = CleanListromHashData(split[1]), - SHA1 = CleanListromHashData(split[2]), - - Machine = new Machine - { - Name = gamename, - }, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - ParseAddHelper(rom, statsOnly); - } - - // Nodump Disks have 5 pieces (name, NO, GOOD, DUMP, KNOWN) - else if (split.Length == 4 && line.EndsWith("NO GOOD DUMP KNOWN")) - { - Disk disk = new() - { - Name = romname, - ItemStatus = ItemStatus.Nodump, - - Machine = new Machine - { - Name = gamename, - }, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - ParseAddHelper(disk, statsOnly); - } - - // Baddump ROMs have 6 pieces (name, size, BAD, crc, sha1, BAD_DUMP) - else if (split.Length == 5 && line.EndsWith("BAD_DUMP")) - { - Rom rom = new() - { - Name = romname, - Size = Utilities.CleanLong(split[0]), - CRC = CleanListromHashData(split[2]), - SHA1 = CleanListromHashData(split[3]), - ItemStatus = ItemStatus.BadDump, - - Machine = new Machine - { - Name = gamename, - }, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - ParseAddHelper(rom, statsOnly); - } - - // Nodump ROMs have 6 pieces (name, size, NO, GOOD, DUMP, KNOWN) - else if (split.Length == 5 && line.EndsWith("NO GOOD DUMP KNOWN")) - { - Rom rom = new() - { - Name = romname, - Size = Utilities.CleanLong(split[0]), - ItemStatus = ItemStatus.Nodump, - - Machine = new Machine - { - Name = gamename, - }, - - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - ParseAddHelper(rom, statsOnly); - } - - // If we have something else, it's invalid - else - { - logger.Warning($"Invalid line detected: '{romname} {line}'"); - } - } - } - catch (Exception ex) when (!throwOnError) - { - string message = $"'{filename}' - There was an error parsing at position {sr.BaseStream.Position}"; - logger.Error(ex, message); - } - } - } - - /// - protected override ItemType[] GetSupportedTypes() - { - return new ItemType[] { ItemType.Disk, ItemType.Rom }; - } - - /// - protected override List GetMissingRequiredFields(DatItem datItem) - { - List missingFields = new(); - - // Check item name - if (string.IsNullOrWhiteSpace(datItem.GetName())) - missingFields.Add(DatItemField.Name); - - // TODO: Should CRC/SHA1 be included here? Unclear right now if fully required. Probably is, though - - return null; - } - - /// - public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) - { - try - { - logger.User($"Writing to '{outfile}'..."); - FileStream fs = System.IO.File.Create(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); - return false; - } - - StreamWriter sw = new(fs, new UTF8Encoding(false)); - - // Write out each of the machines and roms - string lastgame = null; - - // Use a sorted list of games to output - foreach (string key in Items.SortedKeys) - { - ConcurrentList datItems = Items.FilteredItems(key); - - // If this machine doesn't contain any writable items, skip - if (!ContainsWritable(datItems)) - continue; - - // Resolve the names in the block - datItems = DatItem.ResolveNames(datItems); - - for (int index = 0; index < datItems.Count; index++) - { - DatItem datItem = datItems[index]; - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteEndGame(sw); - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteStartGame(sw, datItem); - - // Check for a "null" item - datItem = ProcessNullifiedItem(datItem); - - // Write out the item if we're not ignoring - if (!ShouldIgnore(datItem, ignoreblanks)) - WriteDatItem(sw, datItem); - - // Set the new data to compare against - lastgame = datItem.Machine.Name; - } - } - - logger.User($"'{outfile}' written!{Environment.NewLine}"); - sw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) when (!throwOnError) - { - logger.Error(ex); - return false; - } - - return true; - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - private void WriteStartGame(StreamWriter sw, DatItem rom) - { - // No game should start with a path separator - rom.Machine.Name = rom.Machine.Name.TrimStart(Path.DirectorySeparatorChar); - - // Build the state - sw.Write($"ROMs required for driver \"{rom.Machine.Name}\".\n"); - sw.Write("Name Size Checksum\n"); - - sw.Flush(); - } - - /// - /// Write out Game end using the supplied StreamWriter - /// - /// StreamWriter to output to - private void WriteEndGame(StreamWriter sw) - { - // End driver - sw.Write("\n"); - - sw.Flush(); - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// StreamWriter to output to - /// DatItem object to be output - private void WriteDatItem(StreamWriter sw, DatItem datItem) - { - // Pre-process the item name - ProcessItemName(datItem, true); - - // Build the state - switch (datItem.ItemType) - { - case ItemType.Disk: - var disk = datItem as Disk; - - // The name is padded out to a particular length - if (disk.Name.Length < 43) - sw.Write(disk.Name.PadRight(43, ' ')); - else - sw.Write($"{disk.Name} "); - - // If we have a baddump, put the first indicator - if (disk.ItemStatus == ItemStatus.BadDump) - sw.Write(" BAD"); - - // If we have a nodump, write out the indicator - if (disk.ItemStatus == ItemStatus.Nodump) - sw.Write(" NO GOOD DUMP KNOWN"); - - // Otherwise, write out the SHA-1 hash - else if (!string.IsNullOrWhiteSpace(disk.SHA1)) - sw.Write($" SHA1({disk.SHA1 ?? string.Empty})"); - - // If we have a baddump, put the second indicator - if (disk.ItemStatus == ItemStatus.BadDump) - sw.Write(" BAD_DUMP"); - - sw.Write("\n"); - break; - - case ItemType.Rom: - var rom = datItem as Rom; - - // The name is padded out to a particular length - if (rom.Name.Length < 43) - sw.Write(rom.Name.PadRight(43 - rom.Size?.ToString().Length ?? 0, ' ')); - else - sw.Write($"{rom.Name} "); - - // If we don't have a nodump, write out the size - if (rom.ItemStatus != ItemStatus.Nodump) - sw.Write(rom.Size?.ToString() ?? string.Empty); - - // If we have a baddump, put the first indicator - if (rom.ItemStatus == ItemStatus.BadDump) - sw.Write(" BAD"); - - // If we have a nodump, write out the indicator - if (rom.ItemStatus == ItemStatus.Nodump) - { - sw.Write(" NO GOOD DUMP KNOWN"); - } - // Otherwise, write out the CRC and SHA-1 hashes - else - { - if (!string.IsNullOrWhiteSpace(rom.CRC)) - sw.Write($" CRC({rom.CRC ?? string.Empty})"); - if (!string.IsNullOrWhiteSpace(rom.SHA1)) - sw.Write($" SHA1({rom.SHA1 ?? string.Empty})"); - } - - // If we have a baddump, put the second indicator - if (rom.ItemStatus == ItemStatus.BadDump) - sw.Write(" BAD_DUMP"); - - sw.Write("\n"); - break; - } - - sw.Flush(); - } } } diff --git a/SabreTools.DatFiles/Formats/SeparatedValue.Writer.cs b/SabreTools.DatFiles/Formats/SeparatedValue.Writer.cs index 4970f446..82ce9912 100644 --- a/SabreTools.DatFiles/Formats/SeparatedValue.Writer.cs +++ b/SabreTools.DatFiles/Formats/SeparatedValue.Writer.cs @@ -36,8 +36,6 @@ namespace SabreTools.DatFiles.Formats switch (datItem) { case Disk disk: - if (string.IsNullOrWhiteSpace(disk.Name)) - missingFields.Add(DatItemField.Name); if (string.IsNullOrWhiteSpace(disk.MD5) && string.IsNullOrWhiteSpace(disk.SHA1)) { @@ -46,8 +44,6 @@ namespace SabreTools.DatFiles.Formats break; case Rom rom: - if (string.IsNullOrWhiteSpace(rom.Name)) - missingFields.Add(DatItemField.Name); if (rom.Size == null || rom.Size < 0) missingFields.Add(DatItemField.Size); if (string.IsNullOrWhiteSpace(rom.CRC) diff --git a/SabreTools.Models/Listrom/Row.cs b/SabreTools.Models/Listrom/Row.cs index 3cadd445..f6a62f1a 100644 --- a/SabreTools.Models/Listrom/Row.cs +++ b/SabreTools.Models/Listrom/Row.cs @@ -6,7 +6,9 @@ namespace SabreTools.Models.Listrom /// abcd.bin 1024 CRC(00000000) SHA1(da39a3ee5e6b4b0d3255bfef95601890afd80709) /// efgh.bin 1024 BAD CRC(00000000) SHA1(da39a3ee5e6b4b0d3255bfef95601890afd80709) BAD_DUMP /// ijkl.bin 1024 NO GOOD DUMP KNOWN + /// abcd MD5(d41d8cd98f00b204e9800998ecf8427e) /// abcd SHA1(da39a3ee5e6b4b0d3255bfef95601890afd80709) + /// efgh BAD MD5(d41d8cd98f00b204e9800998ecf8427e) BAD_DUMP /// efgh BAD SHA1(da39a3ee5e6b4b0d3255bfef95601890afd80709) BAD_DUMP /// ijkl NO GOOD DUMP KNOWN /// @@ -20,6 +22,8 @@ namespace SabreTools.Models.Listrom public string? CRC { get; set; } + public string? MD5 { get; set; } + public string? SHA1 { get; set; } public bool NoGoodDumpKnown { get; set; } diff --git a/SabreTools.Serialization/Listrom.cs b/SabreTools.Serialization/Listrom.Deserializer.cs similarity index 87% rename from SabreTools.Serialization/Listrom.cs rename to SabreTools.Serialization/Listrom.Deserializer.cs index c976c6b7..d4c8ccee 100644 --- a/SabreTools.Serialization/Listrom.cs +++ b/SabreTools.Serialization/Listrom.Deserializer.cs @@ -7,9 +7,9 @@ using SabreTools.Models.Listrom; namespace SabreTools.Serialization { /// - /// Serializer for MAME listrom files + /// Deserializer for MAME listrom files /// - public class Listrom + public partial class Listrom { /// /// Deserializes a MAME listrom file to the defined type @@ -110,10 +110,13 @@ namespace SabreTools.Serialization var row = new Row(); switch (lineParts.Length) { - // Normal CHD (Name, SHA1) + // Normal CHD (Name, MD5/SHA1) case 1: row.Name = name; - row.SHA1 = lineParts[0]["SHA1".Length..].Trim('(', ')'); + if (line.Contains("MD5(")) + row.MD5 = lineParts[0]["MD5".Length..].Trim('(', ')'); + else + row.SHA1 = lineParts[0]["SHA1".Length..].Trim('(', ')'); break; // Normal ROM (Name, Size, CRC, SHA1) @@ -128,7 +131,10 @@ namespace SabreTools.Serialization case 3 when line.Contains("BAD_DUMP"): row.Name = name; row.Bad = true; - row.SHA1 = lineParts[1]["SHA1".Length..].Trim('(', ')'); + if (line.Contains("MD5(")) + row.MD5 = lineParts[1]["MD5".Length..].Trim('(', ')'); + else + row.SHA1 = lineParts[1]["SHA1".Length..].Trim('(', ')'); break; // Nodump CHD (Name, NO GOOD DUMP KNOWN) @@ -137,13 +143,16 @@ namespace SabreTools.Serialization row.NoGoodDumpKnown = true; break; - // Bad ROM (Name, Size, BAD, CRC, SHA1, BAD_DUMP) + // Bad ROM (Name, Size, BAD, CRC, MD5/SHA1, BAD_DUMP) case 5 when line.Contains("BAD_DUMP"): row.Name = name; row.Size = lineParts[0]; row.Bad = true; row.CRC = lineParts[2]["CRC".Length..].Trim('(', ')'); - row.SHA1 = lineParts[3]["SHA1".Length..].Trim('(', ')'); + if (line.Contains("MD5(")) + row.SHA1 = lineParts[3]["MD5".Length..].Trim('(', ')'); + else + row.SHA1 = lineParts[3]["SHA1".Length..].Trim('(', ')'); break; // Nodump ROM (Name, Size, NO GOOD DUMP KNOWN) diff --git a/SabreTools.Serialization/Listrom.Serializer.cs b/SabreTools.Serialization/Listrom.Serializer.cs new file mode 100644 index 00000000..55b222ea --- /dev/null +++ b/SabreTools.Serialization/Listrom.Serializer.cs @@ -0,0 +1,179 @@ +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.Models.Listrom; + +namespace SabreTools.Serialization +{ + /// + /// Deserializer for MAME listrom files + /// + public partial class Listrom + { + /// + /// Serializes the defined type to a MAME listrom file + /// + /// Data to serialize + /// Path to the file to serialize to + /// True on successful serialization, false otherwise + public static bool SerializeToFile(MetadataFile? metadataFile, string path) + { + using var stream = SerializeToStream(metadataFile); + if (stream == null) + return false; + + using var fs = File.OpenWrite(path); + stream.Seek(0, SeekOrigin.Begin); + stream.CopyTo(fs); + return true; + } + + /// + /// Serializes the defined type to a stream + /// + /// Data to serialize + /// Stream containing serialized data on success, null otherwise + public static Stream? SerializeToStream(MetadataFile? metadataFile) + { + // If the metadata file is null + if (metadataFile == null) + return null; + + // Setup the writer and output + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + + // Write out the sets, if they exist + WriteSets(metadataFile.Set, writer); + + // Return the stream + return stream; + } + + /// + /// Write sets information to the current writer + /// + /// Array of Set objects representing the sets information + /// StreamWriter representing the output + private static void WriteSets(Set[]? sets, StreamWriter writer) + { + // If the games information is missing, we can't do anything + if (sets == null || !sets.Any()) + return; + + // Loop through and write out the games + foreach (var set in sets) + { + WriteSet(set, writer); + writer.Flush(); + } + } + + /// + /// Write set information to the current writer + /// + /// Set object representing the set information + /// StreamWriter representing the output + private static void WriteSet(Set set, StreamWriter writer) + { + // If the set information is missing, we can't do anything + if (set == null) + return; + + if (!string.IsNullOrWhiteSpace(set.Driver)) + { + if (set.Row != null && set.Row.Any()) + { + writer.WriteLine($"ROMs required for driver \"{set.Driver}\"."); + writer.WriteLine("Name Size Checksum"); + writer.Flush(); + + WriteRows(set.Row, writer); + + writer.WriteLine(); + writer.Flush(); + } + else + { + writer.WriteLine($"No ROMs required for driver \"{set.Driver}\"."); + writer.WriteLine(); + writer.Flush(); + } + } + else if (!string.IsNullOrWhiteSpace(set.Device)) + { + if (set.Row != null && set.Row.Any()) + { + writer.WriteLine($"ROMs required for device \"{set.Device}\"."); + writer.WriteLine("Name Size Checksum"); + writer.Flush(); + + WriteRows(set.Row, writer); + + writer.WriteLine(); + writer.Flush(); + } + else + { + writer.WriteLine($"No ROMs required for device \"{set.Device}\"."); + writer.WriteLine(); + writer.Flush(); + } + } + } + + /// + /// Write rows information to the current writer + /// + /// Array of Row objects to write + /// StreamWriter representing the output + private static void WriteRows(Row[]? rows, StreamWriter writer) + { + // If the array is missing, we can't do anything + if (rows == null) + return; + + foreach (var row in rows) + { + var rowBuilder = new StringBuilder(); + + int padding = 40 - (row.Size?.Length ?? 0); + if (padding < row.Name.Length) + padding = row.Name.Length + 2; + + rowBuilder.Append($"{row.Name.PadRight(padding, ' ')}"); + if (row.Size != null) + rowBuilder.Append($"{row.Size} "); + + if (row.NoGoodDumpKnown) + { + rowBuilder.Append("NO GOOD DUMP KNOWN"); + } + else + { + if (row.Bad) + rowBuilder.Append("BAD "); + + if (row.Size != null) + { + rowBuilder.Append($"CRC({row.CRC}) "); + rowBuilder.Append($"SHA1({row.SHA1}) "); + } + else + { + if (!string.IsNullOrWhiteSpace(row.MD5)) + rowBuilder.Append($"MD5({row.MD5}) "); + else + rowBuilder.Append($"SHA1({row.SHA1}) "); + } + + if (row.Bad) + rowBuilder.Append("BAD_DUMP"); + } + + writer.WriteLine(rowBuilder.ToString().TrimEnd()); + writer.Flush(); + } + } + } +} \ No newline at end of file