diff --git a/SabreTools.DatFiles/Formats/ClrMamePro.Reader.cs b/SabreTools.DatFiles/Formats/ClrMamePro.Reader.cs index d38557bd..4e54dae0 100644 --- a/SabreTools.DatFiles/Formats/ClrMamePro.Reader.cs +++ b/SabreTools.DatFiles/Formats/ClrMamePro.Reader.cs @@ -464,10 +464,10 @@ namespace SabreTools.DatFiles.Formats switch (video.Orientation) { - case "vertical": + case "horizontal": item.Rotate = 0; break; - case "horizontal": + case "vertical": item.Rotate = 90; break; } @@ -528,12 +528,12 @@ namespace SabreTools.DatFiles.Formats Players = Utilities.CleanLong(input.Players), //Control = input.Control, // TODO: Add to internal model or find mapping Controls = new List + { + new Control { - new Control - { - Buttons = Utilities.CleanLong(input.Buttons), - }, + Buttons = Utilities.CleanLong(input.Buttons), }, + }, Coins = Utilities.CleanLong(input.Coins), Tilt = input.Tilt?.AsYesNo(), Service = input.Service?.AsYesNo(), diff --git a/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs b/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs index 7e8946e4..fb8c701a 100644 --- a/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs +++ b/SabreTools.DatFiles/Formats/ClrMamePro.Writer.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Text; +using System.Linq; using SabreTools.Core; using SabreTools.Core.Tools; using SabreTools.DatItems; using SabreTools.DatItems.Formats; -using SabreTools.IO.Writers; namespace SabreTools.DatFiles.Formats { @@ -22,25 +20,118 @@ namespace SabreTools.DatFiles.Formats { ItemType.Archive, ItemType.BiosSet, - //ItemType.Chip, - //ItemType.DipSwitch, + ItemType.Chip, + ItemType.DipSwitch, ItemType.Disk, - //ItemType.Display, - //ItemType.Driver, - //ItemType.Input, + ItemType.Display, + ItemType.Driver, + ItemType.Input, ItemType.Media, ItemType.Release, ItemType.Rom, ItemType.Sample, - //ItemType.Sound, + ItemType.Sound, }; } /// protected override List GetMissingRequiredFields(DatItem datItem) { + var missingFields = new List(); + switch (datItem) + { + case Release release: + if (string.IsNullOrWhiteSpace(release.Name)) + missingFields.Add(DatItemField.Name); + if (string.IsNullOrWhiteSpace(release.Region)) + missingFields.Add(DatItemField.Region); + break; + + case BiosSet biosset: + if (string.IsNullOrWhiteSpace(biosset.Name)) + missingFields.Add(DatItemField.Name); + if (string.IsNullOrWhiteSpace(biosset.Description)) + missingFields.Add(DatItemField.Description); + 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) + && string.IsNullOrWhiteSpace(rom.MD5) + && string.IsNullOrWhiteSpace(rom.SHA1) + && string.IsNullOrWhiteSpace(rom.SHA256) + && string.IsNullOrWhiteSpace(rom.SHA384) + && string.IsNullOrWhiteSpace(rom.SHA512) + && string.IsNullOrWhiteSpace(rom.SpamSum)) + { + missingFields.Add(DatItemField.SHA1); + } + break; + + case Disk disk: + if (string.IsNullOrWhiteSpace(disk.Name)) + missingFields.Add(DatItemField.Name); + if (string.IsNullOrWhiteSpace(disk.MD5) + && string.IsNullOrWhiteSpace(disk.SHA1)) + { + missingFields.Add(DatItemField.SHA1); + } + break; + + case Sample sample: + if (string.IsNullOrWhiteSpace(sample.Name)) + missingFields.Add(DatItemField.Name); + break; + + case Archive archive: + if (string.IsNullOrWhiteSpace(archive.Name)) + missingFields.Add(DatItemField.Name); + break; + + case Chip chip: + if (!chip.ChipTypeSpecified) + missingFields.Add(DatItemField.ChipType); + if (string.IsNullOrWhiteSpace(chip.Name)) + missingFields.Add(DatItemField.Name); + break; + + case Display display: + if (!display.DisplayTypeSpecified) + missingFields.Add(DatItemField.DisplayType); + if (!display.RotateSpecified) + missingFields.Add(DatItemField.Rotate); + break; + + case Sound sound: + if (!sound.ChannelsSpecified) + missingFields.Add(DatItemField.Channels); + break; + + case Input input: + if (!input.PlayersSpecified) + missingFields.Add(DatItemField.Players); + if (!input.ControlsSpecified) + missingFields.Add(DatItemField.Control_Buttons); + break; + + case DipSwitch dipswitch: + if (string.IsNullOrWhiteSpace(dipswitch.Name)) + missingFields.Add(DatItemField.Name); + break; + + case Driver driver: + if (!driver.StatusSpecified) + missingFields.Add(DatItemField.SupportStatus); + if (!driver.EmulationSpecified) + missingFields.Add(DatItemField.EmulationStatus); + break; + } + // TODO: Check required fields - return null; + return missingFields; } /// @@ -49,68 +140,13 @@ namespace SabreTools.DatFiles.Formats 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) + var metadataFile = CreateMetadataFile(); + if (!Serialization.ClrMamePro.SerializeToFile(metadataFile, outfile, Quotes)) { - logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); + logger.Warning($"File '{outfile}' could not be written! See the log for more details."); return false; } - - ClrMameProWriter cmpw = new(fs, new UTF8Encoding(false)) - { - Quotes = Quotes - }; - - // Write out the header - WriteHeader(cmpw); - - // 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(cmpw, datItem); - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteStartGame(cmpw, datItem); - - // Check for a "null" item - datItem = ProcessNullifiedItem(datItem); - - // Write out the item if we're not ignoring - if (!ShouldIgnore(datItem, ignoreblanks)) - WriteDatItem(cmpw, datItem); - - // Set the new data to compare against - lastgame = datItem.Machine.Name; - } - } - - // Write the file footer out - WriteFooter(cmpw); - - logger.User($"'{outfile}' written!{Environment.NewLine}"); - cmpw.Dispose(); - fs.Dispose(); } catch (Exception ex) when (!throwOnError) { @@ -122,176 +158,413 @@ namespace SabreTools.DatFiles.Formats } /// - /// Write out DAT header using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - private void WriteHeader(ClrMameProWriter cmpw) - { - cmpw.WriteStartElement("clrmamepro"); - - cmpw.WriteRequiredStandalone("name", Header.Name); - cmpw.WriteRequiredStandalone("description", Header.Description); - cmpw.WriteOptionalStandalone("category", Header.Category); - cmpw.WriteRequiredStandalone("version", Header.Version); - cmpw.WriteOptionalStandalone("date", Header.Date); - cmpw.WriteRequiredStandalone("author", Header.Author); - cmpw.WriteOptionalStandalone("email", Header.Email); - cmpw.WriteOptionalStandalone("homepage", Header.Homepage); - cmpw.WriteOptionalStandalone("url", Header.Url); - cmpw.WriteOptionalStandalone("comment", Header.Comment); - if (Header.ForcePacking != PackingFlag.None) - cmpw.WriteOptionalStandalone("forcezipping", Header.ForcePacking.FromPackingFlag(true), false); - if (Header.ForceMerging != MergingFlag.None) - cmpw.WriteOptionalStandalone("forcemerging", Header.ForceMerging.FromMergingFlag(false), false); - - // End clrmamepro - cmpw.WriteEndElement(); - - cmpw.Flush(); - } - + /// Create a MetadataFile from the current internal information /// - /// Write out Game start using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - /// DatItem object to be output - private static void WriteStartGame(ClrMameProWriter cmpw, DatItem datItem) + private Models.ClrMamePro.MetadataFile CreateMetadataFile() { - // No game should start with a path separator - datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); - - // Build the state - cmpw.WriteStartElement(datItem.Machine.MachineType == MachineType.Bios ? "resource" : "game"); - - cmpw.WriteRequiredStandalone("name", datItem.Machine.Name); - cmpw.WriteOptionalStandalone("romof", datItem.Machine.RomOf); - cmpw.WriteOptionalStandalone("cloneof", datItem.Machine.CloneOf); - cmpw.WriteOptionalStandalone("description", datItem.Machine.Description ?? datItem.Machine.Name); - cmpw.WriteOptionalStandalone("year", datItem.Machine.Year); - cmpw.WriteOptionalStandalone("manufacturer", datItem.Machine.Manufacturer); - cmpw.WriteOptionalStandalone("category", datItem.Machine.Category); - - cmpw.Flush(); - } - - /// - /// Write out Game end using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - /// DatItem object to be output - private static void WriteEndGame(ClrMameProWriter cmpw, DatItem datItem) - { - // Build the state - cmpw.WriteOptionalStandalone("sampleof", datItem.Machine.SampleOf); - - // End game - cmpw.WriteEndElement(); - - cmpw.Flush(); - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// DatFile to write out from - /// ClrMameProWriter to output to - /// DatItem object to be output - private void WriteDatItem(ClrMameProWriter cmpw, DatItem datItem) - { - // Pre-process the item name - ProcessItemName(datItem, true); - - // Build the state - switch (datItem.ItemType) + var metadataFile = new Models.ClrMamePro.MetadataFile { - case ItemType.Archive: - var archive = datItem as Archive; - cmpw.WriteStartElement("archive"); - cmpw.WriteRequiredAttributeString("name", archive.Name); - cmpw.WriteEndElement(); - break; + ClrMamePro = CreateClrMamePro(), + Game = CreateGames() + }; + return metadataFile; + } - case ItemType.BiosSet: - var biosSet = datItem as BiosSet; - cmpw.WriteStartElement("biosset"); - cmpw.WriteRequiredAttributeString("name", biosSet.Name); - cmpw.WriteOptionalAttributeString("description", biosSet.Description); - cmpw.WriteOptionalAttributeString("default", biosSet.Default?.ToString().ToLowerInvariant()); - cmpw.WriteEndElement(); - break; + /// + /// Create a ClrMamePro from the current internal information + /// + private Models.ClrMamePro.ClrMamePro? CreateClrMamePro() + { + // If we don't have a header, we can't do anything + if (this.Header == null) + return null; - case ItemType.Disk: - var disk = datItem as Disk; - cmpw.WriteStartElement("disk"); - cmpw.WriteRequiredAttributeString("name", disk.Name); - cmpw.WriteOptionalAttributeString("md5", disk.MD5?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("sha1", disk.SHA1?.ToLowerInvariant(), quoteOverride: false); - if (disk.ItemStatus != ItemStatus.None) - cmpw.WriteOptionalAttributeString("flags", disk.ItemStatus.FromItemStatus(false)); - cmpw.WriteEndElement(); - break; + var clrMamePro = new Models.ClrMamePro.ClrMamePro + { + Name = Header.Name, + Description = Header.Description, + RootDir = Header.RootDir, + Category = Header.Category, + Version = Header.Version, + Date = Header.Date, + Author = Header.Author, + Homepage = Header.Homepage, + Url = Header.Url, + Comment = Header.Comment, + Header = Header.HeaderSkipper, + Type = Header.Type, + }; - case ItemType.Media: - var media = datItem as Media; - cmpw.WriteStartElement("media"); - cmpw.WriteRequiredAttributeString("name", media.Name); - cmpw.WriteOptionalAttributeString("md5", media.MD5?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("sha1", media.SHA1?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("sha256", media.SHA256?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("spamsum", media.SpamSum?.ToLowerInvariant()); - cmpw.WriteEndElement(); - break; + if (Header.ForceMergingSpecified) + clrMamePro.ForceMerging = Header.ForceMerging.FromMergingFlag(romCenter: false); + if (Header.ForcePackingSpecified) + clrMamePro.ForcePacking = Header.ForcePacking.FromPackingFlag(yesno: false); - case ItemType.Release: - var release = datItem as Release; - cmpw.WriteStartElement("release"); - cmpw.WriteRequiredAttributeString("name", release.Name); - cmpw.WriteOptionalAttributeString("region", release.Region); - cmpw.WriteOptionalAttributeString("language", release.Language); - cmpw.WriteOptionalAttributeString("date", release.Date); - cmpw.WriteOptionalAttributeString("default", release.Default?.ToString().ToLowerInvariant()); - cmpw.WriteEndElement(); - break; + return clrMamePro; + } - case ItemType.Rom: - var rom = datItem as Rom; - cmpw.WriteStartElement("rom"); - cmpw.WriteRequiredAttributeString("name", rom.Name); - cmpw.WriteOptionalAttributeString("size", rom.Size?.ToString(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("md5", rom.MD5?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("sha1", rom.SHA1?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("sha256", rom.SHA256?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("sha384", rom.SHA384?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("sha512", rom.SHA512?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("spamsum", rom.SpamSum?.ToLowerInvariant(), quoteOverride: false); - cmpw.WriteOptionalAttributeString("date", rom.Date); - if (rom.ItemStatus != ItemStatus.None) - cmpw.WriteOptionalAttributeString("flags", rom.ItemStatus.FromItemStatus(false)); - cmpw.WriteEndElement(); - break; + /// + /// Create an array of GameBase from the current internal information + /// + private Models.ClrMamePro.GameBase[]? CreateGames() + { + // If we don't have items, we can't do anything + if (this.Items == null || !this.Items.Any()) + return null; - case ItemType.Sample: - var sample = datItem as Sample; - cmpw.WriteStartElement("sample"); - cmpw.WriteRequiredAttributeString("name", sample.Name); - cmpw.WriteEndElement(); + // Create a list of hold the games + var games = 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; + + // Get the first item for game information + var machine = items[0].Machine; + + // We normalize to all "game" + var game = new Models.ClrMamePro.Game + { + Name = machine.Name, + Description = machine.Description, + Year = machine.Year, + Manufacturer = machine.Manufacturer, + Category = machine.Category, + CloneOf = machine.CloneOf, + RomOf = machine.RomOf, + SampleOf = machine.SampleOf, + }; + + // Create holders for all item types + var releases = new List(); + var biossets = new List(); + var roms = new List(); + var disks = new List(); + var medias = new List(); + var samples = new List(); + var archives = new List(); + var chips = new List(); + var dipswitches = new List(); + + // Loop through and convert the items to respective lists + foreach (var item in items) + { + switch (item) + { + case Release release: + releases.Add(CreateRelease(release)); + break; + case BiosSet biosset: + biossets.Add(CreateBiosSet(biosset)); + break; + case Rom rom: + roms.Add(CreateRom(rom)); + break; + case Disk disk: + disks.Add(CreateDisk(disk)); + break; + case Media media: + medias.Add(CreateMedia(media)); + break; + case Sample sample: + samples.Add(CreateSample(sample)); + break; + case Archive archive: + archives.Add(CreateArchive(archive)); + break; + case Chip chip: + chips.Add(CreateChip(chip)); + break; + case Display display: + game.Video = CreateVideo(display); + break; + case Sound sound: + game.Sound = CreateSound(sound); + break; + case Input input: + game.Input = CreateInput(input); + break; + case DipSwitch dipswitch: + dipswitches.Add(CreateDipSwitch(dipswitch)); + break; + case Driver driver: + game.Driver = CreateDriver(driver); + break; + } + } + + // Assign the values to the game + game.Release = releases.ToArray(); + game.BiosSet = biossets.ToArray(); + game.Rom = roms.ToArray(); + game.Disk = disks.ToArray(); + game.Media = medias.ToArray(); + game.Sample = samples.ToArray(); + game.Archive = archives.ToArray(); + + // Add the game to the list + games.Add(game); + } + + // TODO: Populate the games + + return games.ToArray(); + } + + /// + /// Create a Release from the current Release DatItem + /// + private static Models.ClrMamePro.Release CreateRelease(Release item) + { + var release = new Models.ClrMamePro.Release + { + Name = item.Name, + Region = item.Region, + Language = item.Language, + Date = item.Date, + }; + + if (item.DefaultSpecified) + release.Default = item.Default.FromYesNo(); + + return release; + } + + /// + /// Create a BiosSet from the current BiosSet DatItem + /// + private static Models.ClrMamePro.BiosSet CreateBiosSet(BiosSet item) + { + var biosset = new Models.ClrMamePro.BiosSet + { + Name = item.Name, + Description = item.Description, + }; + + if (item.DefaultSpecified) + biosset.Default = item.Default.FromYesNo(); + + return biosset; + } + + /// + /// Create a Rom from the current Rom DatItem + /// + private static Models.ClrMamePro.Rom CreateRom(Rom item) + { + var rom = new Models.ClrMamePro.Rom + { + Name = item.Name, + Size = item.Size?.ToString(), + CRC = item.CRC, + MD5 = item.MD5, + SHA1 = item.SHA1, + SHA256 = item.SHA256, + SHA384 = item.SHA384, + SHA512 = item.SHA512, + SpamSum = item.SpamSum, + //xxHash364 = item.xxHash364, // TODO: Add to internal model + //xxHash3128 = item.xxHash3128, // TODO: Add to internal model + Merge = item.MergeTag, + Region = item.Region, + //Flags = item.Flags, // TODO: Add to internal model + Offs = item.Offset, + //Serial = item.Serial, // TODO: Add to internal model + //Header = item.Header, // TODO: Add to internal model + Date = item.Date, + }; + + if (item.ItemStatusSpecified) + rom.Status = item.ItemStatus.FromItemStatus(yesno: false); + if (item.InvertedSpecified) + rom.Inverted = item.Inverted.FromYesNo(); + if (item.MIASpecified) + rom.MIA = item.MIA.FromYesNo(); + + return rom; + } + + /// + /// Create a Disk from the current Disk DatItem + /// + private static Models.ClrMamePro.Disk CreateDisk(Disk item) + { + var disk = new Models.ClrMamePro.Disk + { + Name = item.Name, + MD5 = item.MD5, + SHA1 = item.SHA1, + Merge = item.MergeTag, + //Flags = item.Flags, // TODO: Add to internal model + }; + + if (item.ItemStatusSpecified) + disk.Status = item.ItemStatus.FromItemStatus(yesno: false); + + return disk; + } + + /// + /// Create a Media from the current Media DatItem + /// + private static Models.ClrMamePro.Media CreateMedia(Media item) + { + var media = new Models.ClrMamePro.Media + { + Name = item.Name, + MD5 = item.MD5, + SHA1 = item.SHA1, + SHA256 = item.SHA256, + SpamSum = item.SpamSum, + }; + return media; + } + + /// + /// Create a Sample from the current Sample DatItem + /// + private static Models.ClrMamePro.Sample CreateSample(Sample item) + { + var sample = new Models.ClrMamePro.Sample + { + Name = item.Name, + }; + return sample; + } + + /// + /// Create a Archive from the current Archive DatItem + /// + private static Models.ClrMamePro.Archive CreateArchive(Archive item) + { + var archive = new Models.ClrMamePro.Archive + { + Name = item.Name, + }; + return archive; + } + + /// + /// Create a Chip from the current Chip DatItem + /// + private static Models.ClrMamePro.Chip CreateChip(Chip item) + { + var chip = new Models.ClrMamePro.Chip + { + Type = item.ChipType.FromChipType(), + Name = item.Name, + //Flags = item.Flags, // TODO: Add to internal model + Clock = item.Clock?.ToString(), + }; + return chip; + } + + /// + /// Create a Video from the current Display DatItem + /// + private static Models.ClrMamePro.Video CreateVideo(Display item) + { + var video = new Models.ClrMamePro.Video + { + Screen = item.DisplayType.FromDisplayType(), + X = item.Width?.ToString(), + Y = item.Height?.ToString(), + //AspectX = item.AspectX, // TODO: Add to internal model or find mapping + //AspectY = item.AspectY, // TODO: Add to internal model or find mapping + Freq = item.Refresh?.ToString(), + }; + + switch (item.Rotate) + { + case 0: + case 180: + video.Orientation = "horizontal"; + break; + case 90: + case 270: + video.Orientation = "vertical"; break; } - cmpw.Flush(); + return video; } /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - private static void WriteFooter(ClrMameProWriter cmpw) + /// Create a Sound from the current Sound DatItem + /// + private static Models.ClrMamePro.Sound CreateSound(Sound item) { - // End game - cmpw.WriteEndElement(); + var sound = new Models.ClrMamePro.Sound + { + Channels = item.Channels?.ToString(), + }; + return sound; + } - cmpw.Flush(); + /// + /// Create a Input from the current Input DatItem + /// + private static Models.ClrMamePro.Input CreateInput(Input item) + { + var input = new Models.ClrMamePro.Input + { + Players = item.Players?.ToString(), + //Control = item.Control, // TODO: Add to internal model or find mapping + Coins = item.Coins?.ToString(), + Tilt = item.Tilt.FromYesNo(), + Service = item.Service.FromYesNo(), + }; + + if (item.ControlsSpecified) + input.Buttons = item.Controls[0].Buttons?.ToString(); + + return input; + } + + /// + /// Create a DipSwitch from the current DipSwitch DatItem + /// + private static Models.ClrMamePro.DipSwitch CreateDipSwitch(DipSwitch item) + { + var dipswitch = new Models.ClrMamePro.DipSwitch + { + Name = item.Name, + }; + + if (item.ValuesSpecified) + { + var entries = new List(); + foreach (var setting in item.Values) + { + entries.Add(setting.Value); + if (setting.Default == true) + dipswitch.Default = setting.Value; + } + + dipswitch.Entry = entries.ToArray(); + } + + return dipswitch; + } + + /// + /// Create a Driver from the current Driver DatItem + /// + private static Models.ClrMamePro.Driver CreateDriver(Driver item) + { + var driver = new Models.ClrMamePro.Driver + { + Status = item.Status.FromSupportStatus(), + //Color = item.Color, // TODO: Add to internal model or find mapping + //Sound = item.Sound, // TODO: Add to internal model or find mapping + //PaletteSize = item.PaletteSize, // TODO: Add to internal model or find mapping + //Blit = item.Blit, // TODO: Add to internal model or find mapping + + }; + return driver; } } } diff --git a/SabreTools.Models/ClrMamePro/Driver.cs b/SabreTools.Models/ClrMamePro/Driver.cs index 3d1f2b26..f84b3a78 100644 --- a/SabreTools.Models/ClrMamePro/Driver.cs +++ b/SabreTools.Models/ClrMamePro/Driver.cs @@ -7,16 +7,16 @@ namespace SabreTools.Models.ClrMamePro public string Status { get; set; } /// color, (good|imperfect|preliminary) - public string Color { get; set; } + public string? Color { get; set; } /// sound, (good|imperfect|preliminary) - public string Sound { get; set; } + public string? Sound { get; set; } /// palettesize, Numeric? - public string PaletteSize { get; set; } + public string? PaletteSize { get; set; } /// blit, (plain|dirty) - public string Blit { get; set; } + public string? Blit { get; set; } #region DO NOT USE IN PRODUCTION diff --git a/SabreTools.Models/ClrMamePro/GameBase.cs b/SabreTools.Models/ClrMamePro/GameBase.cs index c1ef54dd..c39198e1 100644 --- a/SabreTools.Models/ClrMamePro/GameBase.cs +++ b/SabreTools.Models/ClrMamePro/GameBase.cs @@ -6,7 +6,7 @@ namespace SabreTools.Models.ClrMamePro public abstract class GameBase { /// name - public string? Name { get; set; } + public string Name { get; set; } /// description public string? Description { get; set; } diff --git a/SabreTools.Models/ClrMamePro/Rom.cs b/SabreTools.Models/ClrMamePro/Rom.cs index 250da8c2..f71ff638 100644 --- a/SabreTools.Models/ClrMamePro/Rom.cs +++ b/SabreTools.Models/ClrMamePro/Rom.cs @@ -62,7 +62,7 @@ namespace SabreTools.Models.ClrMamePro public string? Region { get; set; } /// offs; Appears after Flags - public string? Offs { get; set; } + public string? Offs { get; set; } // TODO: Is this "Offset" elsewhere? #endregion diff --git a/SabreTools.Serialization/ClrMamePro.cs b/SabreTools.Serialization/ClrMamePro.cs index 70a108c7..e649a8ee 100644 --- a/SabreTools.Serialization/ClrMamePro.cs +++ b/SabreTools.Serialization/ClrMamePro.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using SabreTools.IO.Readers; using SabreTools.IO.Writers; @@ -18,7 +19,7 @@ namespace SabreTools.Serialization /// Deserializes a ClrMamePro metadata file to the defined type /// /// Path to the file to deserialize - /// Enable quotes on read and write, false otherwise + /// Enable quotes on read, false otherwise /// Deserialized data on success, null on failure public static MetadataFile? Deserialize(string path, bool quotes) { @@ -38,7 +39,7 @@ namespace SabreTools.Serialization /// Deserializes a ClrMamePro metadata file in a stream to the defined type /// /// Stream to deserialize - /// Enable quotes on read and write, false otherwise + /// Enable quotes on read, false otherwise /// Deserialized data on success, null on failure public static MetadataFile? Deserialize(Stream? stream, bool quotes) { @@ -857,16 +858,18 @@ namespace SabreTools.Serialization /// /// Data to serialize /// Path to the file to serialize to + /// Enable quotes on write, false otherwise /// True on successful serialization, false otherwise - public static bool SerializeToFile(MetadataFile? metadataFile, string path) + public static bool SerializeToFile(MetadataFile? metadataFile, string path, bool quotes) { try { - using var stream = SerializeToStream(metadataFile); + using var stream = SerializeToStream(metadataFile, quotes); if (stream == null) return false; using var fs = File.OpenWrite(path); + stream.Seek(0, SeekOrigin.Begin); stream.CopyTo(fs); return true; } @@ -881,8 +884,9 @@ namespace SabreTools.Serialization /// Serializes the defined type to a stream /// /// Data to serialize + /// Enable quotes on write, false otherwise /// Stream containing serialized data on success, null otherwise - public static Stream? SerializeToStream(MetadataFile? metadataFile) + public static Stream? SerializeToStream(MetadataFile? metadataFile, bool quotes) { try { @@ -890,8 +894,18 @@ namespace SabreTools.Serialization if (metadataFile == null) return null; - // TODO: Implement writing - return null; + // Setup the writer and output + var stream = new MemoryStream(); + var writer = new ClrMameProWriter(stream, Encoding.UTF8) { Quotes = quotes }; + + // Write the header, if it exists + WriteHeader(metadataFile.ClrMamePro, writer); + + // Write out the games, if they exist + WriteGames(metadataFile.Game, writer); + + // Return the stream + return stream; } catch { @@ -900,6 +914,406 @@ namespace SabreTools.Serialization } } + /// + /// Write header information to the current writer + /// + /// ClrMamePro representing the header information + /// ClrMameProReader representing the metadata file + private static void WriteHeader(Models.ClrMamePro.ClrMamePro? header, ClrMameProWriter writer) + { + // If the header information is missing, we can't do anything + if (header == null) + return; + + writer.WriteStartElement("clrmamepro"); + + writer.WriteOptionalStandalone("name", header.Name); + writer.WriteOptionalStandalone("description", header.Description); + writer.WriteOptionalStandalone("rootdir", header.RootDir); + writer.WriteOptionalStandalone("category", header.Category); + writer.WriteOptionalStandalone("version", header.Version); + writer.WriteOptionalStandalone("date", header.Date); + writer.WriteOptionalStandalone("author", header.Author); + writer.WriteOptionalStandalone("homepage", header.Homepage); + writer.WriteOptionalStandalone("url", header.Url); + writer.WriteOptionalStandalone("comment", header.Comment); + writer.WriteOptionalStandalone("header", header.Header); + writer.WriteOptionalStandalone("type", header.Type); + writer.WriteOptionalStandalone("forcemerging", header.ForceMerging); + writer.WriteOptionalStandalone("forcezipping", header.ForceZipping); + writer.WriteOptionalStandalone("forcepacking", header.ForcePacking); + + writer.WriteEndElement(); // clrmamepro + writer.Flush(); + } + + /// + /// Write games information to the current writer + /// + /// Array of GameBase objects representing the games information + /// ClrMameProReader representing the metadata file + private static void WriteGames(GameBase[]? games, ClrMameProWriter writer) + { + // If the games information is missing, we can't do anything + if (games == null || !games.Any()) + return; + + // Loop through and write out the games + foreach (var game in games) + { + WriteGame(game, writer); + writer.Flush(); + } + } + + /// + /// Write game information to the current writer + /// + /// GameBase object representing the game information + /// ClrMameProReader representing the metadata file + private static void WriteGame(GameBase game, ClrMameProWriter writer) + { + // If the game information is missing, we can't do anything + if (game == null) + return; + + switch (game) + { + case Game: + writer.WriteStartElement("game"); + break; + case Machine: + writer.WriteStartElement("machine"); + break; + case Resource: + writer.WriteStartElement("resource"); + break; + case Set: + writer.WriteStartElement(name: "set"); + break; + } + + // Write the standalone values + writer.WriteRequiredStandalone("name", game.Name, throwOnError: true); + writer.WriteOptionalStandalone("description", game.Description); + writer.WriteOptionalStandalone("year", game.Year); + writer.WriteOptionalStandalone("manufacturer", game.Manufacturer); + writer.WriteOptionalStandalone("category", game.Category); + writer.WriteOptionalStandalone("cloneof", game.CloneOf); + writer.WriteOptionalStandalone("romof", game.RomOf); + writer.WriteOptionalStandalone("sampleof", game.SampleOf); + + // Write the item values + WriteReleases(game.Release, writer); + WriteBiosSets(game.BiosSet, writer); + WriteRoms(game.Rom, writer); + WriteDisks(game.Disk, writer); + WriteMedia(game.Media, writer); + WriteSamples(game.Sample, writer); + WriteArchives(game.Archive, writer); + WriteChips(game.Chip, writer); + WriteVideo(game.Video, writer); + WriteSound(game.Sound, writer); + WriteInput(game.Input, writer); + WriteDipSwitches(game.DipSwitch, writer); + WriteDriver(game.Driver, writer); + + writer.WriteEndElement(); // game, machine, resource, set + } + + /// + /// Write releases information to the current writer + /// + /// Array of Release objects to write + /// ClrMameProReader representing the metadata file + private static void WriteReleases(Release[]? releases, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (releases == null) + return; + + foreach (var release in releases) + { + writer.WriteStartElement("release"); + writer.WriteRequiredAttributeString("name", release.Name, throwOnError: true); + writer.WriteRequiredAttributeString("region", release.Region, throwOnError: true); + writer.WriteOptionalAttributeString("language", release.Language); + writer.WriteOptionalAttributeString("date", release.Date); + writer.WriteOptionalAttributeString("default", release.Default); + writer.WriteEndElement(); // release + } + } + + /// + /// Write biossets information to the current writer + /// + /// Array of BiosSet objects to write + /// ClrMameProReader representing the metadata file + private static void WriteBiosSets(BiosSet[]? biossets, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (biossets == null) + return; + + foreach (var biosset in biossets) + { + writer.WriteStartElement("biosset"); + writer.WriteRequiredAttributeString("name", biosset.Name, throwOnError: true); + writer.WriteRequiredAttributeString("description", biosset.Description, throwOnError: true); + writer.WriteOptionalAttributeString("default", biosset.Default); + writer.WriteEndElement(); // release + } + } + + /// + /// Write roms information to the current writer + /// + /// Array of Rom objects to write + /// ClrMameProReader representing the metadata file + private static void WriteRoms(Rom[]? roms, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (roms == null) + return; + + foreach (var rom in roms) + { + writer.WriteStartElement("rom"); + writer.WriteRequiredAttributeString("name", rom.Name, throwOnError: true); + writer.WriteRequiredAttributeString("size", rom.Size, throwOnError: true); + writer.WriteOptionalAttributeString("crc", rom.CRC); + writer.WriteOptionalAttributeString("md5", rom.MD5); + writer.WriteOptionalAttributeString("sha1", rom.SHA1); + writer.WriteOptionalAttributeString("sha256", rom.SHA256); + writer.WriteOptionalAttributeString("sha384", rom.SHA384); + writer.WriteOptionalAttributeString("sha512", rom.SHA512); + writer.WriteOptionalAttributeString("spamsum", rom.SpamSum); + writer.WriteOptionalAttributeString("xxh3_64", rom.xxHash364); + writer.WriteOptionalAttributeString("xxh3_128", rom.xxHash3128); + writer.WriteOptionalAttributeString("merge", rom.Merge); + writer.WriteOptionalAttributeString("status", rom.Status); + writer.WriteOptionalAttributeString("region", rom.Region); + writer.WriteOptionalAttributeString("flags", rom.Flags); + writer.WriteOptionalAttributeString("offs", rom.Offs); + writer.WriteOptionalAttributeString("serial", rom.Serial); + writer.WriteOptionalAttributeString("header", rom.Header); + writer.WriteOptionalAttributeString("date", rom.Date); + writer.WriteOptionalAttributeString("inverted", rom.Inverted); + writer.WriteOptionalAttributeString("mia", rom.MIA); + writer.WriteEndElement(); // rom + } + } + + /// + /// Write disks information to the current writer + /// + /// Array of Disk objects to write + /// ClrMameProReader representing the metadata file + private static void WriteDisks(Disk[]? disks, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (disks == null) + return; + + foreach (var disk in disks) + { + writer.WriteStartElement("disk"); + writer.WriteRequiredAttributeString("name", disk.Name, throwOnError: true); + writer.WriteOptionalAttributeString("md5", disk.MD5); + writer.WriteOptionalAttributeString("sha1", disk.SHA1); + writer.WriteOptionalAttributeString("merge", disk.Merge); + writer.WriteOptionalAttributeString("status", disk.Status); + writer.WriteOptionalAttributeString("flags", disk.Flags); + writer.WriteEndElement(); // disk + } + } + + /// + /// Write medias information to the current writer + /// + /// Array of Media objects to write + /// ClrMameProReader representing the metadata file + private static void WriteMedia(Media[]? medias, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (medias == null) + return; + + foreach (var media in medias) + { + writer.WriteStartElement("media"); + writer.WriteRequiredAttributeString("name", media.Name, throwOnError: true); + writer.WriteOptionalAttributeString("md5", media.MD5); + writer.WriteOptionalAttributeString("sha1", media.SHA1); + writer.WriteOptionalAttributeString("sha256", media.SHA256); + writer.WriteOptionalAttributeString("spamsum", media.SpamSum); + writer.WriteEndElement(); // media + } + } + + /// + /// Write samples information to the current writer + /// + /// Array of Sample objects to write + /// ClrMameProReader representing the metadata file + private static void WriteSamples(Sample[]? samples, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (samples == null) + return; + + foreach (var sample in samples) + { + writer.WriteStartElement("sample"); + writer.WriteRequiredAttributeString("name", sample.Name, throwOnError: true); + writer.WriteEndElement(); // sample + } + } + + /// + /// Write archives information to the current writer + /// + /// Array of Archive objects to write + /// ClrMameProReader representing the metadata file + private static void WriteArchives(Archive[]? archives, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (archives == null) + return; + + foreach (var archive in archives) + { + writer.WriteStartElement("archive"); + writer.WriteRequiredAttributeString("name", archive.Name, throwOnError: true); + writer.WriteEndElement(); // archive + } + } + + /// + /// Write chips information to the current writer + /// + /// Array of Chip objects to write + /// ClrMameProReader representing the metadata file + private static void WriteChips(Chip[]? chips, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (chips == null) + return; + + foreach (var chip in chips) + { + writer.WriteStartElement("chip"); + writer.WriteRequiredAttributeString("type", chip.Type, throwOnError: true); + writer.WriteRequiredAttributeString("name", chip.Name, throwOnError: true); + writer.WriteOptionalAttributeString("flags", chip.Flags); + writer.WriteOptionalAttributeString("clock", chip.Clock); + writer.WriteEndElement(); // chip + } + } + + /// + /// Write video information to the current writer + /// + /// Video object to write + /// ClrMameProReader representing the metadata file + private static void WriteVideo(Video? video, ClrMameProWriter writer) + { + // If the item is missing, we can't do anything + if (video == null) + return; + + writer.WriteStartElement("video"); + writer.WriteRequiredAttributeString("screen", video.Screen, throwOnError: true); + writer.WriteRequiredAttributeString("orientation", video.Orientation, throwOnError: true); + writer.WriteOptionalAttributeString("x", video.X); + writer.WriteOptionalAttributeString("y", video.Y); + writer.WriteOptionalAttributeString("aspectx", video.AspectX); + writer.WriteOptionalAttributeString("aspecty", video.AspectY); + writer.WriteOptionalAttributeString("freq", video.Freq); + writer.WriteEndElement(); // video + } + + /// + /// Write sound information to the current writer + /// + /// Sound object to write + /// ClrMameProReader representing the metadata file + private static void WriteSound(Sound? sound, ClrMameProWriter writer) + { + // If the item is missing, we can't do anything + if (sound == null) + return; + + writer.WriteStartElement("sound"); + writer.WriteRequiredAttributeString("channels", sound.Channels, throwOnError: true); + writer.WriteEndElement(); // sound + } + + /// + /// Write input information to the current writer + /// + /// Input object to write + /// ClrMameProReader representing the metadata file + private static void WriteInput(Input? input, ClrMameProWriter writer) + { + // If the item is missing, we can't do anything + if (input == null) + return; + + writer.WriteStartElement("input"); + writer.WriteRequiredAttributeString("players", input.Players, throwOnError: true); + writer.WriteOptionalAttributeString("control", input.Control); + writer.WriteRequiredAttributeString("buttons", input.Buttons, throwOnError: true); + writer.WriteOptionalAttributeString("coins", input.Coins); + writer.WriteOptionalAttributeString("tilt", input.Tilt); + writer.WriteOptionalAttributeString("service", input.Service); + writer.WriteEndElement(); // input + } + + /// + /// Write dipswitches information to the current writer + /// + /// Array of DipSwitch objects to write + /// ClrMameProReader representing the metadata file + private static void WriteDipSwitches(DipSwitch[]? dipswitches, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (dipswitches == null) + return; + + foreach (var dipswitch in dipswitches) + { + writer.WriteStartElement("dipswitch"); + writer.WriteRequiredAttributeString("name", dipswitch.Name, throwOnError: true); + foreach (var entry in dipswitch.Entry ?? Array.Empty()) + { + writer.WriteRequiredAttributeString("entry", entry); + } + writer.WriteOptionalAttributeString("default", dipswitch.Default); + writer.WriteEndElement(); // dipswitch + } + } + + /// + /// Write driver information to the current writer + /// + /// Driver object to write + /// ClrMameProReader representing the metadata file + private static void WriteDriver(Driver? driver, ClrMameProWriter writer) + { + // If the item is missing, we can't do anything + if (driver == null) + return; + + writer.WriteStartElement("driver"); + writer.WriteRequiredAttributeString("status", driver.Status, throwOnError: true); + writer.WriteOptionalAttributeString("color", driver.Color); // TODO: Probably actually required + writer.WriteOptionalAttributeString("sound", driver.Sound); // TODO: Probably actually required + writer.WriteOptionalAttributeString("palettesize", driver.PaletteSize); + writer.WriteOptionalAttributeString("blit", driver.Blit); + writer.WriteEndElement(); // driver + } + #endregion } } \ No newline at end of file