diff --git a/SabreTools.Library/Data/Flags.cs b/SabreTools.Library/Data/Flags.cs index aa39f495..a62d58bd 100644 --- a/SabreTools.Library/Data/Flags.cs +++ b/SabreTools.Library/Data/Flags.cs @@ -208,9 +208,10 @@ namespace SabreTools.Library.Data MissFile = AttractMode << 1, CSV = MissFile << 1, TSV = CSV << 1, + Listroms = TSV << 1, // SFV-similar Formats - RedumpSFV = TSV << 1, + RedumpSFV = Listroms << 1, RedumpMD5 = RedumpSFV << 1, RedumpSHA1 = RedumpMD5 << 1, RedumpSHA256 = RedumpSHA1 << 1, @@ -218,7 +219,7 @@ namespace SabreTools.Library.Data RedumpSHA512 = RedumpSHA384 << 1, // Specialty combinations - ALL = 0xFFFFF, + ALL = 0xFFFFFFF, } /// diff --git a/SabreTools.Library/Dats/Partials/DatFile.Parsers.cs b/SabreTools.Library/Dats/Partials/DatFile.Parsers.cs index c11e7b8b..5b5224d3 100644 --- a/SabreTools.Library/Dats/Partials/DatFile.Parsers.cs +++ b/SabreTools.Library/Dats/Partials/DatFile.Parsers.cs @@ -106,6 +106,9 @@ namespace SabreTools.Library.Dats case DatFormat.CSV: ParseCSVTSV(filename, sysid, srcid, ',', keep, clean, remUnicode); break; + case DatFormat.Listroms: + ParseListroms(filename, sysid, srcid, keep, clean, remUnicode); + break; case DatFormat.Logiqx: case DatFormat.OfflineList: case DatFormat.SabreDat: @@ -2342,6 +2345,197 @@ namespace SabreTools.Library.Dats xtr.Dispose(); } + /// + /// Parse a MAME Listroms 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) + /// + /// In a new style MAME listroms DAT, each game has the following format: + /// + /// ROMs required for driver "005". + /// Name Size Checksum + /// 1346b.cpu-u25 2048 CRC(8e68533e) SHA1(a257c556d31691068ed5c991f1fb2b51da4826db) + /// 6331.sound-u8 32 BAD CRC(1d298cb0) SHA1(bb0bb62365402543e3154b9a77be9c75010e6abc) BAD_DUMP + /// + /// + private void ParseListroms( + // Standard Dat parsing + string filename, + int sysid, + int srcid, + + // Miscellaneous + bool keep, + bool clean, + bool remUnicode) + { + // Open a file reader + Encoding enc = Style.GetEncoding(filename); + StreamReader sr = new StreamReader(FileTools.TryOpenRead(filename), enc); + + string gamename = ""; + while (!sr.EndOfStream) + { + string line = sr.ReadLine().Trim(); + + // If we have a blank line, we just skip it + if (String.IsNullOrEmpty(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 driver")) + { + gamename = Regex.Match(line, @"^ROMs required for driver ""(.*?)""\.").Groups[1].Value; + } + + // Otherwise, we assume we have a rom that we need to add + else + { + // First we separate the ROM into pieces + string[] split = line.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); + + // Standard Disks have 2 pieces (name, sha1) + if (split.Length == 2) + { + Disk disk = new Disk() + { + Name = split[0], + SHA1 = Style.CleanListromHashData(split[1]), + + Machine = new Machine() + { + Name = gamename, + }, + }; + + ParseAddHelper(disk, clean, remUnicode); + } + + // Baddump Disks have 4 pieces (name, BAD, sha1, BAD_DUMP) + else if (split.Length == 4 && line.EndsWith("BAD_DUMP")) + { + Disk disk = new Disk() + { + Name = split[0], + SHA1 = Style.CleanListromHashData(split[2]), + ItemStatus = ItemStatus.BadDump, + + Machine = new Machine() + { + Name = gamename, + }, + }; + + ParseAddHelper(disk, clean, remUnicode); + } + + // Standard ROMs have 4 pieces (name, size, crc, sha1) + else if (split.Length == 4) + { + if (!Int64.TryParse(split[1], out long size)) + { + size = 0; + } + + Rom rom = new Rom() + { + Name = split[0], + Size = size, + CRC = Style.CleanListromHashData(split[2]), + SHA1 = Style.CleanListromHashData(split[3]), + + Machine = new Machine() + { + Name = gamename, + }, + }; + + ParseAddHelper(rom, clean, remUnicode); + } + + // Nodump Disks have 5 pieces (name, NO, GOOD, DUMP, KNOWN) + else if (split.Length == 5 && line.EndsWith("NO GOOD DUMP KNOWN")) + { + Disk disk = new Disk() + { + Name = split[0], + ItemStatus = ItemStatus.Nodump, + + Machine = new Machine() + { + Name = gamename, + }, + }; + + ParseAddHelper(disk, clean, remUnicode); + } + + // Baddump ROMs have 6 pieces (name, size, BAD, crc, sha1, BAD_DUMP) + else if (split.Length == 6 && line.EndsWith("BAD_DUMP")) + { + if (!Int64.TryParse(split[1], out long size)) + { + size = 0; + } + + Rom rom = new Rom() + { + Name = split[0], + Size = size, + CRC = Style.CleanListromHashData(split[3]), + SHA1 = Style.CleanListromHashData(split[4]), + ItemStatus = ItemStatus.BadDump, + + Machine = new Machine() + { + Name = gamename, + }, + }; + } + + // Nodump ROMs have 6 pieces (name, size, NO, GOOD, DUMP, KNOWN) + else if (split.Length == 6 && line.EndsWith("NO GOOD DUMP KNOWN")) + { + if (!Int64.TryParse(split[1], out long size)) + { + size = 0; + } + + Rom rom = new Rom() + { + Name = split[0], + Size = size, + ItemStatus = ItemStatus.Nodump, + + Machine = new Machine() + { + Name = gamename, + }, + }; + } + + // If we have something else, it's invalid + else + { + Globals.Logger.Warning("Invalid line detected: '" + line + "'"); + } + } + } + } + /// /// Parse a Redump MD5 and return all found games and roms within /// diff --git a/SabreTools.Library/Dats/Partials/DatFile.Writers.cs b/SabreTools.Library/Dats/Partials/DatFile.Writers.cs index a4f799cf..b6ab3fc5 100644 --- a/SabreTools.Library/Dats/Partials/DatFile.Writers.cs +++ b/SabreTools.Library/Dats/Partials/DatFile.Writers.cs @@ -513,6 +513,10 @@ namespace SabreTools.Library.Dats case DatFormat.DOSCenter: state += "game (\n\tname \"" + rom.Machine.Name + ".zip\"\n"; break; + case DatFormat.Listroms: + state += "ROMs required for driver \"" + rom.Machine.Name + "\".\n" + + "Name Size Checksum\n"; + break; case DatFormat.Logiqx: state += "\t + /// Clean a hash string from a Listrom DAT + /// + /// Hash string to sanitize + /// Cleaned string + public static string CleanListromHashData(string hash) + { + if (hash.StartsWith("CRC")) + { + return hash.Substring(4, 8).ToLowerInvariant(); + } + else if (hash.StartsWith("SHA1")) + { + return hash.Substring(5, 40).ToLowerInvariant(); + } + + return hash; + } + /// /// Generate a proper outfile name based on a DAT and output directory /// @@ -156,25 +175,37 @@ namespace SabreTools.Library.Tools || (datdata.DatFormat & DatFormat.RomCenter) != 0)) { outfileNames.Add(DatFormat.DOSCenter, CreateOutfileNamesHelper(outDir, ".dc.dat", datdata, overwrite)); - }; + } + + //MAME Listroms + if ((datdata.DatFormat & DatFormat.Listroms) != 0 + && (datdata.DatFormat & DatFormat.AttractMode) == 0) + { + outfileNames.Add(DatFormat.Listroms, CreateOutfileNamesHelper(outDir, ".txt", datdata, overwrite)); + } + if ((datdata.DatFormat & DatFormat.Listroms) != 0 + && (datdata.DatFormat & DatFormat.AttractMode) != 0) + { + outfileNames.Add(DatFormat.Listroms, CreateOutfileNamesHelper(outDir, ".lr.txt", datdata, overwrite)); + } // Logiqx XML if ((datdata.DatFormat & DatFormat.Logiqx) != 0) { outfileNames.Add(DatFormat.Logiqx, CreateOutfileNamesHelper(outDir, ".xml", datdata, overwrite)); - }; + } // Missfile if ((datdata.DatFormat & DatFormat.MissFile) != 0 && (datdata.DatFormat & DatFormat.AttractMode) == 0) { outfileNames.Add(DatFormat.MissFile, CreateOutfileNamesHelper(outDir, ".txt", datdata, overwrite)); - }; + } if ((datdata.DatFormat & DatFormat.MissFile) != 0 && (datdata.DatFormat & DatFormat.AttractMode) != 0) { outfileNames.Add(DatFormat.MissFile, CreateOutfileNamesHelper(outDir, ".miss.txt", datdata, overwrite)); - }; + } // OfflineList if (((datdata.DatFormat & DatFormat.OfflineList) != 0) diff --git a/SabreTools/Partials/SabreTools.Help.cs b/SabreTools/Partials/SabreTools.Help.cs index 317f71f6..a1ff6d57 100644 --- a/SabreTools/Partials/SabreTools.Help.cs +++ b/SabreTools/Partials/SabreTools.Help.cs @@ -110,6 +110,11 @@ namespace SabreTools "Output in DOSCenter format", FeatureType.Flag, null)); + datFromDir.AddFeature("output-lr", new Feature( + new List() { "-olr", "--output-lr" }, + "Output in MAME Listrom format", + FeatureType.Flag, + null)); datFromDir.AddFeature("output-miss", new Feature( new List() { "-om", "--output-miss" }, "Output in Missfile format", @@ -761,6 +766,11 @@ namespace SabreTools "Output in DOSCenter format", FeatureType.Flag, null)); + update.AddFeature("output-lr", new Feature( + new List() { "-olr", "--output-lr" }, + "Output in MAME Listrom format", + FeatureType.Flag, + null)); update.AddFeature("output-miss", new Feature( new List() { "-om", "--output-miss" }, "Output in Missfile format", diff --git a/SabreTools/SabreTools.cs b/SabreTools/SabreTools.cs index ecf5cd0d..61539135 100644 --- a/SabreTools/SabreTools.cs +++ b/SabreTools/SabreTools.cs @@ -448,6 +448,10 @@ namespace SabreTools case "--of-as-game": filter.IncludeOfInGame = true; break; + case "-olr": + case "--output-lr": + datFormat |= DatFormat.Listroms; + break; case "-om": case "--output-miss": datFormat |= DatFormat.MissFile;