diff --git a/SabreTools.DatFiles/DatFile.ToMetadata.cs b/SabreTools.DatFiles/DatFile.ToMetadata.cs index 037e9d34..01804271 100644 --- a/SabreTools.DatFiles/DatFile.ToMetadata.cs +++ b/SabreTools.DatFiles/DatFile.ToMetadata.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using SabreTools.Core.Tools; @@ -35,6 +34,31 @@ namespace SabreTools.DatFiles return metadataFile; } + /// + /// Convert metadata information + /// + public Models.Metadata.MetadataFile? ConvertMetadataDB(bool ignoreblanks = false) + { + // If we don't have items, we can't do anything + if (ItemsDB == null) + return null; + + // Create an object to hold the data + var metadataFile = new Models.Metadata.MetadataFile(); + + // Convert and assign the header + var header = ConvertHeader(); + if (header != null) + metadataFile[Models.Metadata.MetadataFile.HeaderKey] = header; + + // Convert and assign the machines + var machines = ConvertMachinesDB(ignoreblanks); + if (machines != null) + metadataFile[Models.Metadata.MetadataFile.MachineKey] = machines; + + return metadataFile; + } + /// /// Convert header information /// @@ -618,6 +642,423 @@ namespace SabreTools.DatFiles return [.. machines]; } + /// + /// Convert machines information + /// + private Models.Metadata.Machine[]? ConvertMachinesDB(bool ignoreblanks = false) + { + // Create a machine list to hold all outputs + var machines = new List(); + + // Loop through the sorted items and create games for them + foreach (string key in ItemsDB.SortedKeys) + { + var items = ItemsDB.GetItemsForBucket(key, filter: true); + if (items == null || items.Length == 0) + continue; + + // Create a machine to hold everything + var machine = ItemsDB.GetMachineForItem(items[0].Item1).Item2!.GetInternalClone(); + + // Handle Trurip object, if it exists + if (machine.ContainsKey(Models.Metadata.Machine.TruripKey)) + { + var trurip = machine.Read(Models.Metadata.Machine.TruripKey); + if (trurip != null) + { + var truripItem = trurip.ConvertToLogiqx(); + truripItem.Publisher = machine.ReadString(Models.Metadata.Machine.PublisherKey); + truripItem.Year = machine.ReadString(Models.Metadata.Machine.YearKey); + truripItem.Players = machine.ReadString(Models.Metadata.Machine.PlayersKey); + truripItem.Source = machine.ReadString(Models.Metadata.Machine.SourceFileKey); + truripItem.CloneOf = machine.ReadString(Models.Metadata.Machine.CloneOfKey); + machine[Models.Metadata.Machine.TruripKey] = truripItem; + } + } + + // Create mapping dictionaries for the Parts, DataAreas, and DiskAreas associated with this machine + Dictionary partMappings = []; + Dictionary dataAreaMappings = []; + Dictionary diskAreaMappings = []; + + // Loop through and convert the items to respective lists + for (int index = 0; index < items.Length; index++) + { + // Get the item + var item = items[index]; + + // Check for a "null" item + item = ProcessNullifiedItem(item); + + // Skip if we're ignoring the item + if (ShouldIgnore(item, ignoreblanks)) + continue; + + switch (item.Item2) + { + case DatItems.Formats.Adjuster adjuster: + var adjusterItem = ProcessItem(adjuster); + EnsureMachineKey(machine, Models.Metadata.Machine.AdjusterKey); + AppendToMachineKey(machine, Models.Metadata.Machine.AdjusterKey, adjusterItem); + break; + case DatItems.Formats.Archive archive: + var archiveItem = archive.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.ArchiveKey); + AppendToMachineKey(machine, Models.Metadata.Machine.ArchiveKey, archiveItem); + break; + case DatItems.Formats.BiosSet biosSet: + var biosSetItem = biosSet.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.BiosSetKey); + AppendToMachineKey(machine, Models.Metadata.Machine.BiosSetKey, biosSetItem); + break; + case DatItems.Formats.Chip chip: + var chipItem = chip.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.ChipKey); + AppendToMachineKey(machine, Models.Metadata.Machine.ChipKey, chipItem); + break; + case DatItems.Formats.Configuration configuration: + var configurationItem = ProcessItem(configuration); + EnsureMachineKey(machine, Models.Metadata.Machine.ConfigurationKey); + AppendToMachineKey(machine, Models.Metadata.Machine.ConfigurationKey, configurationItem); + break; + case DatItems.Formats.Device device: + var deviceItem = ProcessItem(device); + EnsureMachineKey(machine, Models.Metadata.Machine.DeviceKey); + AppendToMachineKey(machine, Models.Metadata.Machine.DeviceKey, deviceItem); + break; + case DatItems.Formats.DeviceRef deviceRef: + var deviceRefItem = deviceRef.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.DeviceRefKey); + AppendToMachineKey(machine, Models.Metadata.Machine.DeviceRefKey, deviceRefItem); + break; + case DatItems.Formats.DipSwitch dipSwitch: + var dipSwitchItem = ProcessItem(dipSwitch); + EnsureMachineKey(machine, Models.Metadata.Machine.DipSwitchKey); + AppendToMachineKey(machine, Models.Metadata.Machine.DipSwitchKey, dipSwitchItem); + + // Add Part mapping + bool dipSwitchContainsPart = dipSwitchItem.ContainsKey(DatItems.Formats.DipSwitch.PartKey); + if (dipSwitchContainsPart) + { + var partItem = dipSwitchItem.Read(DatItems.Formats.DipSwitch.PartKey); + if (partItem != null) + partMappings[partItem.GetInternalClone()] = dipSwitchItem; + } + + break; + case DatItems.Formats.Disk disk: + var diskItem = disk.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.DiskKey); + AppendToMachineKey(machine, Models.Metadata.Machine.DiskKey, diskItem); + + // Add Part and DiskArea mappings + bool diskContainsPart = diskItem.ContainsKey(DatItems.Formats.Disk.PartKey); + bool diskContainsDiskArea = diskItem.ContainsKey(DatItems.Formats.Disk.DiskAreaKey); + if (diskContainsPart && diskContainsDiskArea) + { + var partItem = diskItem.Read(DatItems.Formats.Disk.PartKey); + if (partItem != null) + { + var partItemInternal = partItem.GetInternalClone(); + partMappings[partItemInternal] = diskItem; + + var diskAreaItem = diskItem.Read(DatItems.Formats.Disk.DiskAreaKey); + if (diskAreaItem != null) + diskAreaMappings[partItemInternal] = (diskAreaItem.GetInternalClone(), diskItem); + } + } + break; + case DatItems.Formats.Display display: + var displayItem = ProcessItem(display, machine); + EnsureMachineKey(machine, Models.Metadata.Machine.DisplayKey); + AppendToMachineKey(machine, Models.Metadata.Machine.DisplayKey, displayItem); + break; + case DatItems.Formats.Driver driver: + var driverItem = driver.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.DriverKey); + AppendToMachineKey(machine, Models.Metadata.Machine.DriverKey, driverItem); + break; + case DatItems.Formats.Feature feature: + var featureItem = feature.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.FeatureKey); + AppendToMachineKey(machine, Models.Metadata.Machine.FeatureKey, featureItem); + break; + case DatItems.Formats.Info info: + var infoItem = info.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.InfoKey); + AppendToMachineKey(machine, Models.Metadata.Machine.InfoKey, infoItem); + break; + case DatItems.Formats.Input input: + var inputItem = ProcessItem(input); + EnsureMachineKey(machine, Models.Metadata.Machine.InputKey); + AppendToMachineKey(machine, Models.Metadata.Machine.InputKey, inputItem); + break; + case DatItems.Formats.Media media: + var mediaItem = media.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.MediaKey); + AppendToMachineKey(machine, Models.Metadata.Machine.MediaKey, mediaItem); + break; + case DatItems.Formats.PartFeature partFeature: + var partFeatureItem = partFeature.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.FeatureKey); + AppendToMachineKey(machine, Models.Metadata.Machine.FeatureKey, partFeatureItem); + + // Add Part mapping + bool partFeatureContainsPart = partFeatureItem.ContainsKey(DatItems.Formats.PartFeature.PartKey); + if (partFeatureContainsPart) + { + var partItem = partFeatureItem.Read(DatItems.Formats.PartFeature.PartKey); + if (partItem != null) + partMappings[partItem.GetInternalClone()] = partFeatureItem; + } + break; + case DatItems.Formats.Port port: + var portItem = ProcessItem(port); + EnsureMachineKey(machine, Models.Metadata.Machine.PortKey); + AppendToMachineKey(machine, Models.Metadata.Machine.PortKey, portItem); + break; + case DatItems.Formats.RamOption ramOption: + var ramOptionItem = ramOption.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.RamOptionKey); + AppendToMachineKey(machine, Models.Metadata.Machine.RamOptionKey, ramOptionItem); + break; + case DatItems.Formats.Release release: + var releaseItem = release.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.ReleaseKey); + AppendToMachineKey(machine, Models.Metadata.Machine.ReleaseKey, releaseItem); + break; + case DatItems.Formats.Rom rom: + var romItem = ProcessItem(rom, machine); + EnsureMachineKey(machine, Models.Metadata.Machine.RomKey); + AppendToMachineKey(machine, Models.Metadata.Machine.RomKey, romItem); + + // Add Part and DataArea mappings + bool romContainsPart = romItem.ContainsKey(DatItems.Formats.Rom.PartKey); + bool romContainsDataArea = romItem.ContainsKey(DatItems.Formats.Rom.DataAreaKey); + if (romContainsPart && romContainsDataArea) + { + var partItem = romItem.Read(DatItems.Formats.Rom.PartKey); + if (partItem != null) + { + var partItemInternal = partItem.GetInternalClone(); + partMappings[partItemInternal] = romItem; + + var dataAreaItem = romItem.Read(DatItems.Formats.Rom.DataAreaKey); + if (dataAreaItem != null) + dataAreaMappings[partItemInternal] = (dataAreaItem.GetInternalClone(), romItem); + } + } + break; + case DatItems.Formats.Sample sample: + var sampleItem = sample.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.SampleKey); + AppendToMachineKey(machine, Models.Metadata.Machine.SampleKey, sampleItem); + break; + case DatItems.Formats.SharedFeat sharedFeat: + var sharedFeatItem = sharedFeat.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.SharedFeatKey); + AppendToMachineKey(machine, Models.Metadata.Machine.SharedFeatKey, sharedFeatItem); + break; + case DatItems.Formats.Slot slot: + var slotItem = ProcessItem(slot); + EnsureMachineKey(machine, Models.Metadata.Machine.SlotKey); + AppendToMachineKey(machine, Models.Metadata.Machine.SlotKey, slotItem); + break; + case DatItems.Formats.SoftwareList softwareList: + var softwareListItem = softwareList.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.SoftwareListKey); + AppendToMachineKey(machine, Models.Metadata.Machine.SoftwareListKey, softwareListItem); + break; + case DatItems.Formats.Sound sound: + var soundItem = sound.GetInternalClone(); + EnsureMachineKey(machine, Models.Metadata.Machine.SoundKey); + AppendToMachineKey(machine, Models.Metadata.Machine.SoundKey, soundItem); + break; + } + } + + // Handle Part, DiskItem, and DatItem mappings, if they exist + if (partMappings.Count != 0) + { + // Create a collection to hold the inverted Parts + Dictionary partItems = []; + + // Loop through the Part mappings + foreach (var partMapping in partMappings) + { + // Get the mapping pieces + var partItem = partMapping.Key; + var datItem = partMapping.Value; + + // Get the part name and skip if there's none + string? partName = partItem.ReadString(Models.Metadata.Part.NameKey); + if (partName == null) + continue; + + // Create the part in the dictionary, if needed + if (!partItems.ContainsKey(partName)) + partItems[partName] = []; + + // Copy over string values + partItems[partName][Models.Metadata.Part.NameKey] = partName; + if (!partItems[partName].ContainsKey(Models.Metadata.Part.InterfaceKey)) + partItems[partName][Models.Metadata.Part.InterfaceKey] = partItem.ReadString(Models.Metadata.Part.InterfaceKey); + + // Clear any empty fields + ClearEmptyKeys(partItems[partName]); + + // If the item has a DataArea mapping + if (dataAreaMappings.ContainsKey(partItem)) + { + // Get the mapped items + var (dataArea, romItem) = dataAreaMappings[partItem]; + + // Clear any empty fields + ClearEmptyKeys(romItem); + + // Get the data area name and skip if there's none + string? dataAreaName = dataArea.ReadString(Models.Metadata.DataArea.NameKey); + if (dataAreaName != null) + { + // Get existing data areas as a list + var dataAreas = partItems[partName].Read(Models.Metadata.Part.DataAreaKey)?.ToList() ?? []; + + // Find the existing disk area to append to, otherwise create a new disk area + int dataAreaIndex = dataAreas.FindIndex(da => da.ReadString(Models.Metadata.DataArea.NameKey) == dataAreaName); + Models.Metadata.DataArea aggregateDataArea; + if (dataAreaIndex > -1) + { + aggregateDataArea = dataAreas[dataAreaIndex]; + } + else + { + aggregateDataArea = []; + aggregateDataArea[Models.Metadata.DataArea.EndiannessKey] = dataArea.ReadString(Models.Metadata.DataArea.EndiannessKey); + aggregateDataArea[Models.Metadata.DataArea.NameKey] = dataArea.ReadString(Models.Metadata.DataArea.NameKey); + aggregateDataArea[Models.Metadata.DataArea.SizeKey] = dataArea.ReadString(Models.Metadata.DataArea.SizeKey); + aggregateDataArea[Models.Metadata.DataArea.WidthKey] = dataArea.ReadString(Models.Metadata.DataArea.WidthKey); + } + + // Clear any empty fields + ClearEmptyKeys(aggregateDataArea); + + // Get existing roms as a list + var roms = aggregateDataArea.Read(Models.Metadata.DataArea.RomKey)?.ToList() ?? []; + + // Add the rom to the data area + roms.Add(romItem); + + // Assign back the roms + aggregateDataArea[Models.Metadata.DataArea.RomKey] = roms.ToArray(); + + // Assign back the data area + if (dataAreaIndex > -1) + dataAreas[dataAreaIndex] = aggregateDataArea; + else + dataAreas.Add(aggregateDataArea); + + // Assign back the data areas array + partItems[partName][Models.Metadata.Part.DataAreaKey] = dataAreas.ToArray(); + } + } + + // If the item has a DiskArea mapping + if (diskAreaMappings.ContainsKey(partItem)) + { + // Get the mapped items + var (diskArea, diskItem) = diskAreaMappings[partItem]; + + // Clear any empty fields + ClearEmptyKeys(diskItem); + + // Get the disk area name and skip if there's none + string? diskAreaName = diskArea.ReadString(Models.Metadata.DiskArea.NameKey); + if (diskAreaName != null) + { + // Get existing data areas as a list + var diskAreas = partItems[partName].Read(Models.Metadata.Part.DiskAreaKey)?.ToList() ?? []; + + // Find the existing disk area to append to, otherwise create a new disk area + int diskAreaIndex = diskAreas.FindIndex(da => da.ReadString(Models.Metadata.DiskArea.NameKey) == diskAreaName); + Models.Metadata.DiskArea aggregateDiskArea; + if (diskAreaIndex > -1) + { + aggregateDiskArea = diskAreas[diskAreaIndex]; + } + else + { + aggregateDiskArea = []; + aggregateDiskArea[Models.Metadata.DiskArea.NameKey] = diskArea.ReadString(Models.Metadata.DiskArea.NameKey); + } + + // Clear any empty fields + ClearEmptyKeys(aggregateDiskArea); + + // Get existing disks as a list + var disks = aggregateDiskArea.Read(Models.Metadata.DiskArea.DiskKey)?.ToList() ?? []; + + // Add the disk to the data area + disks.Add(diskItem); + + // Assign back the disks + aggregateDiskArea[Models.Metadata.DiskArea.DiskKey] = disks.ToArray(); + + // Assign back the disk area + if (diskAreaIndex > -1) + diskAreas[diskAreaIndex] = aggregateDiskArea; + else + diskAreas.Add(aggregateDiskArea); + + // Assign back the disk areas array + partItems[partName][Models.Metadata.Part.DiskAreaKey] = diskAreas.ToArray(); + } + } + + // If the item is a DipSwitch + if (datItem is Models.Metadata.DipSwitch dipSwitchItem) + { + // Get existing dipswitches as a list + var dipSwitches = partItems[partName].Read(Models.Metadata.Part.DipSwitchKey)?.ToList() ?? []; + + // Clear any empty fields + ClearEmptyKeys(dipSwitchItem); + + // Add the dipswitch + dipSwitches.Add(dipSwitchItem); + + // Assign back the dipswitches + partItems[partName][Models.Metadata.Part.DipSwitchKey] = dipSwitches.ToArray(); + } + + // If the item is a Feature + else if (datItem is Models.Metadata.Feature featureItem) + { + // Get existing features as a list + var features = partItems[partName].Read(Models.Metadata.Part.FeatureKey)?.ToList() ?? []; + + // Clear any empty fields + ClearEmptyKeys(featureItem); + + // Add the feature + features.Add(featureItem); + + // Assign back the features + partItems[partName][Models.Metadata.Part.FeatureKey] = features.ToArray(); + } + } + + // Assign the part array to the machine + machine[Models.Metadata.Machine.PartKey] = partItems.Values.ToArray(); + } + + // Add the machine to the list + machines.Add(machine); + } + + // Return the list of machines + return [.. machines]; + } + /// /// Convert Adjuster information /// diff --git a/SabreTools.DatFiles/SerializableDatFile.cs b/SabreTools.DatFiles/SerializableDatFile.cs index aa4fa3f9..510688e8 100644 --- a/SabreTools.DatFiles/SerializableDatFile.cs +++ b/SabreTools.DatFiles/SerializableDatFile.cs @@ -70,7 +70,7 @@ namespace SabreTools.DatFiles logger.User($"Writing to '{outfile}'..."); // Serialize the input file in two steps - var internalFormat = ConvertMetadata(ignoreblanks); + var internalFormat = ConvertMetadataDB(ignoreblanks); var specificFormat = Activator.CreateInstance().Deserialize(internalFormat); if (!Activator.CreateInstance().Serialize(specificFormat, outfile)) {