diff --git a/SabreTools.DatFiles/DatFile.Splitting.cs b/SabreTools.DatFiles/DatFile.Splitting.cs new file mode 100644 index 00000000..a715a3ce --- /dev/null +++ b/SabreTools.DatFiles/DatFile.Splitting.cs @@ -0,0 +1,1180 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; + +namespace SabreTools.DatFiles +{ + public partial class DatFile + { + // TODO: Make all private methods internal + // TODO: Create tests for all of these individually + #region Item Dictionary Passthrough - Splitting + + /// + /// Use romof tags to add roms to the children + /// + public void AddRomsFromBios() + { + AddRomsFromBiosImpl(); + AddRomsFromBiosImplDB(); + } + + /// + /// Use cloneof tags to add roms to the parents, removing the child sets in the process + /// + /// True to add DatItems to subfolder of parent (not including Disk), false otherwise + /// True to skip checking for duplicate ROMs in parent, false otherwise + public void AddRomsFromChildren(bool subfolder, bool skipDedup) + { + AddRomsFromChildrenImpl(subfolder, skipDedup); + AddRomsFromChildrenImplDB(subfolder, skipDedup); + } + + /// + /// Use device_ref and optionally slotoption tags to add roms to the children + /// + /// True if only child device sets are touched, false for non-device sets + /// True if slotoptions tags are used as well, false otherwise + public bool AddRomsFromDevices(bool dev, bool useSlotOptions) + { + bool foundnew = AddRomsFromDevicesImpl(dev, useSlotOptions); + foundnew |= AddRomsFromDevicesImplDB(dev, useSlotOptions); + return foundnew; + } + + /// + /// Use cloneof tags to add roms to the children, setting the new romof tag in the process + /// + public void AddRomsFromParent() + { + AddRomsFromParentImpl(); + AddRomsFromParentImplDB(); + } + + /// + /// Remove all BIOS and device sets + /// + public void RemoveBiosAndDeviceSets() + { + RemoveBiosAndDeviceSetsImpl(); + RemoveBiosAndDeviceSetsImplDB(); + } + + /// + /// Use romof tags to remove bios roms from children + /// + /// True if only child Bios sets are touched, false for non-bios sets + public void RemoveBiosRomsFromChild(bool bios) + { + RemoveBiosRomsFromChildImpl(bios); + RemoveBiosRomsFromChildImplDB(bios); + } + + /// + /// Use cloneof tags to remove roms from the children + /// + public void RemoveRomsFromChild() + { + RemoveRomsFromChildImpl(); + RemoveRomsFromChildImplDB(); + } + + /// + /// Remove all romof and cloneof tags from all games + /// + public void RemoveTagsFromChild() + { + RemoveTagsFromChildImpl(); + RemoveTagsFromChildImplDB(); + } + + #endregion + + #region Splitting Implementations + + /// + /// Use romof tags to add roms to the children + /// + private void AddRomsFromBiosImpl() + { + List games = [.. Items.Keys]; + games.Sort(); + + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = Items[game]; + if (items == null || items.Count == 0) + continue; + + // Get the machine + var machine = items[0].GetFieldValue(DatItem.MachineKey); + if (machine == null) + continue; + + // Get the bios parent + string? romOf = machine.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + if (string.IsNullOrEmpty(romOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = Items[romOf!]; + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we copy the items from the parent to the current game + DatItem copyFrom = items[0]; + foreach (DatItem item in parentItems) + { + DatItem datItem = (DatItem)item.Clone(); + datItem.CopyMachineInformation(copyFrom); + if (items.FindIndex(i => i.GetName() == datItem.GetName()) == -1 && !items.Contains(datItem)) + Add(game, datItem); + } + } + } + + /// + /// Use romof tags to add roms to the children + /// + private void AddRomsFromBiosImplDB() + { + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // Get the items for this game + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + // Get the source for the first item + var source = ItemsDB.GetSourceForItem(items.First().Key); + + // Get the machine for the first item + var machine = ItemsDB.GetMachineForItem(items.First().Key); + if (machine.Value == null) + continue; + + // Get the bios parent + string? romOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + if (string.IsNullOrEmpty(romOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = GetItemsForBucketDB(romOf!); + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we copy the items from the parent to the current game + foreach (var item in parentItems) + { + DatItem datItem = (item.Value.Clone() as DatItem)!; + if (items.Any(i => i.Value.GetName() == datItem.GetName()) + && items.Any(i => i.Value == datItem)) + { + ItemsDB.AddItem(datItem, machine.Key, source.Key); + } + } + } + } + + /// + /// Use cloneof tags to add roms to the parents, removing the child sets in the process + /// + /// True to add DatItems to subfolder of parent (not including Disk), false otherwise + /// True to skip checking for duplicate ROMs in parent, false otherwise + private void AddRomsFromChildrenImpl(bool subfolder, bool skipDedup) + { + List games = [.. Items.Keys]; + games.Sort(); + + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = Items[game]; + if (items == null || items.Count == 0) + continue; + + // Get the machine + var machine = items[0].GetFieldValue(DatItem.MachineKey); + if (machine == null) + continue; + + // Get the clone parent + string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); + if (string.IsNullOrEmpty(cloneOf)) + continue; + + // Get the parent items + var parentItems = Items[cloneOf!]; + + // Otherwise, move the items from the current game to a subfolder of the parent game + DatItem copyFrom; + if (parentItems == null || parentItems.Count == 0) + { + copyFrom = new Rom(); + copyFrom.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.NameKey, cloneOf); + copyFrom.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.DescriptionKey, cloneOf); + } + else + { + copyFrom = parentItems[0]; + } + + items = Items[game]; + foreach (DatItem item in items!) + { + // Special disk handling + if (item is Disk disk) + { + string? mergeTag = disk.GetStringFieldValue(Models.Metadata.Disk.MergeKey); + + // If the merge tag exists and the parent already contains it, skip + if (mergeTag != null && Items[cloneOf!]! + .FindAll(i => i is Disk) + .ConvertAll(i => (i as Disk)!.GetName()).Contains(mergeTag)) + { + continue; + } + + // If the merge tag exists but the parent doesn't contain it, add to parent + else if (mergeTag != null && !Items[cloneOf!]! + .FindAll(i => i is Disk) + .ConvertAll(i => (i as Disk)!.GetName()).Contains(mergeTag)) + { + disk.CopyMachineInformation(copyFrom); + Add(cloneOf!, disk); + } + + // If there is no merge tag, add to parent + else if (mergeTag == null) + { + disk.CopyMachineInformation(copyFrom); + Add(cloneOf!, disk); + } + } + + // Special rom handling + else if (item is Rom rom) + { + // If the merge tag exists and the parent already contains it, skip + if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && Items[cloneOf!]! + .FindAll(i => i is Rom) + .ConvertAll(i => (i as Rom)!.GetName()) + .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) + { + continue; + } + + // If the merge tag exists but the parent doesn't contain it, add to subfolder of parent + else if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && !Items[cloneOf!]! + .FindAll(i => i is Rom) + .ConvertAll(i => (i as Rom)!.GetName()) + .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) + { + if (subfolder) + rom.SetName($"{rom.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)}\\{rom.GetName()}"); + + rom.CopyMachineInformation(copyFrom); + Add(cloneOf!, rom); + } + + // If the parent doesn't already contain this item, add to subfolder of parent + else if (!Items[cloneOf!]!.Contains(item) || skipDedup) + { + if (subfolder) + rom.SetName($"{item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)}\\{rom.GetName()}"); + + rom.CopyMachineInformation(copyFrom); + Add(cloneOf!, rom); + } + } + + // All other that would be missing to subfolder of parent + else if (!Items[cloneOf!]!.Contains(item)) + { + if (subfolder) + item.SetName($"{item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)}\\{item.GetName()}"); + + item.CopyMachineInformation(copyFrom); + Add(cloneOf!, item); + } + } + + // Then, remove the old game so it's not picked up by the writer + Remove(game); + } + } + + /// + /// Use cloneof tags to add roms to the parents, removing the child sets in the process + /// + /// True to add DatItems to subfolder of parent (not including Disk), false otherwise + /// True to skip checking for duplicate ROMs in parent, false otherwise + private void AddRomsFromChildrenImplDB(bool subfolder, bool skipDedup) + { + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + // Get the machine for the first item + var machine = ItemsDB.GetMachineForItem(items.First().Key); + if (machine.Value == null) + continue; + + // Get the clone parent + string? cloneOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); + if (string.IsNullOrEmpty(cloneOf)) + continue; + + // Get the clone parent machine + var cloneOfMachine = ItemsDB.GetMachine(cloneOf); + if (cloneOfMachine.Value == null) + continue; + + items = GetItemsForBucketDB(game); + foreach (var item in items) + { + // Get the parent items and current machine name + var parentItems = GetItemsForBucketDB(cloneOf!); + if (parentItems == null || parentItems.Count == 0) + continue; + + string? machineName = ItemsDB.GetMachineForItem(item.Key).Value? + .GetStringFieldValue(Models.Metadata.Machine.NameKey); + + // Special disk handling + if (item.Value is Disk disk) + { + string? mergeTag = disk.GetStringFieldValue(Models.Metadata.Disk.MergeKey); + + // If the merge tag exists and the parent already contains it, skip + if (mergeTag != null && parentItems.Values + .Where(i => i is Disk) + .Select(i => (i as Disk)!.GetName()) + .Contains(mergeTag)) + { + continue; + } + + // If the merge tag exists but the parent doesn't contain it, add to parent + else if (mergeTag != null && !parentItems.Values + .Where(i => i is Disk) + .Select(i => (i as Disk)!.GetName()) + .Contains(mergeTag)) + { + ItemsDB._itemToMachineMapping[item.Key] = cloneOfMachine.Key; + ItemsDB._buckets[cloneOf!].Add(item.Key); + } + + // If there is no merge tag, add to parent + else if (mergeTag == null) + { + ItemsDB._itemToMachineMapping[item.Key] = cloneOfMachine.Key; + ItemsDB._buckets[cloneOf!].Add(item.Key); + } + } + + // Special rom handling + else if (item.Value is Rom rom) + { + // If the merge tag exists and the parent already contains it, skip + if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && parentItems.Values + .Where(i => i is Rom) + .Select(i => (i as Rom)!.GetName()) + .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) + { + continue; + } + + // If the merge tag exists but the parent doesn't contain it, add to subfolder of parent + else if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && !parentItems.Values + .Where(i => i is Rom) + .Select(i => (i as Rom)!.GetName()) + .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) + { + if (subfolder) + rom.SetName($"{machineName}\\{rom.GetName()}"); + + ItemsDB._itemToMachineMapping[item.Key] = cloneOfMachine.Key; + ItemsDB._buckets[cloneOf!].Add(item.Key); + } + + // If the parent doesn't already contain this item, add to subfolder of parent + else if (!parentItems.Contains(item) || skipDedup) + { + if (subfolder) + rom.SetName($"{machineName}\\{rom.GetName()}"); + + ItemsDB._itemToMachineMapping[item.Key] = cloneOfMachine.Key; + ItemsDB._buckets[cloneOf!].Add(item.Key); + } + } + + // All other that would be missing to subfolder of parent + else if (!parentItems.Contains(item)) + { + if (subfolder) + item.Value.SetName($"{machineName}\\{item.Value.GetName()}"); + + ItemsDB._itemToMachineMapping[item.Key] = cloneOfMachine.Key; + ItemsDB._buckets[cloneOf!].Add(item.Key); + } + } + + // Then, remove the old game so it's not picked up by the writer +#if NET40_OR_GREATER || NETCOREAPP + ItemsDB._buckets.TryRemove(game, out _); +#else + ItemsDB._buckets.Remove(game); +#endif + } + } + + /// + /// Use device_ref and optionally slotoption tags to add roms to the children + /// + /// True if only child device sets are touched, false for non-device sets (default) + /// True if slotoptions tags are used as well, false otherwise + private bool AddRomsFromDevicesImpl(bool dev, bool useSlotOptions) + { + bool foundnew = false; + List machines = [.. Items.Keys]; + machines.Sort(); + + foreach (string machine in machines) + { + // If the machine doesn't have items, we continue + var datItems = Items[machine]; + if (datItems == null || datItems.Count == 0) + continue; + + // If the machine (is/is not) a device, we want to continue + if (dev ^ (datItems[0].GetFieldValue(DatItem.MachineKey)!.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) + continue; + + // Get all device reference names from the current machine + List deviceReferences = datItems + .FindAll(i => i is DeviceRef) + .ConvertAll(i => i as DeviceRef) + .ConvertAll(dr => dr!.GetName()) + .Distinct() + .ToList(); + + // Get all slot option names from the current machine + List slotOptions = datItems + .FindAll(i => i is Slot) + .ConvertAll(i => i as Slot) + .FindAll(s => s!.SlotOptionsSpecified) + .SelectMany(s => s!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) + .Select(so => so.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)) + .Distinct() + .ToList(); + + // If we're checking device references + if (deviceReferences.Count > 0) + { + // Loop through all names and check the corresponding machines + var newDeviceReferences = new HashSet(); + foreach (string? deviceReference in deviceReferences) + { + // If the device reference is missing + if (string.IsNullOrEmpty(deviceReference)) + continue; + + // Add to the list of new device reference names + var devItems = Items[deviceReference!]; + if (devItems == null || devItems.Count == 0) + continue; + + newDeviceReferences.UnionWith(devItems + .FindAll(i => i is DeviceRef) + .ConvertAll(i => (i as DeviceRef)!.GetName()!)); + + // Set new machine information and add to the current machine + DatItem copyFrom = datItems[0]; + foreach (DatItem item in devItems) + { + // If the parent machine doesn't already contain this item, add it + if (!datItems.Exists(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) && i.GetName() == item.GetName())) + { + // Set that we found new items + foundnew = true; + + // Clone the item and then add it + DatItem datItem = (DatItem)item.Clone(); + datItem.CopyMachineInformation(copyFrom); + Add(machine, datItem); + } + } + } + + // Now that every device reference is accounted for, add the new list of device references, if they don't already exist + foreach (string deviceReference in newDeviceReferences) + { + if (!deviceReferences.Contains(deviceReference)) + { + var deviceRef = new DeviceRef(); + deviceRef.SetName(deviceReference); + datItems.Add(deviceRef); + } + } + } + + // If we're checking slotoptions + if (useSlotOptions && slotOptions.Count > 0) + { + // Loop through all names and check the corresponding machines + var newSlotOptions = new HashSet(); + foreach (string? slotOption in slotOptions) + { + // If the slot option is missing + if (string.IsNullOrEmpty(slotOption)) + // If the machine doesn't exist then we continue + continue; + + // Add to the list of new slot option names + var slotItems = Items[slotOption!]; + if (slotItems == null || slotItems.Count == 0) + continue; + + newSlotOptions.UnionWith(slotItems + .FindAll(i => i is Slot) + .FindAll(s => (s as Slot)!.SlotOptionsSpecified) + .SelectMany(s => (s as Slot)!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) + .Select(o => o.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)!)); + + // Set new machine information and add to the current machine + DatItem copyFrom = datItems[0]; + foreach (DatItem item in slotItems) + { + // If the parent machine doesn't already contain this item, add it + if (!datItems.Exists(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) && i.GetName() == item.GetName())) + { + // Set that we found new items + foundnew = true; + + // Clone the item and then add it + DatItem datItem = (DatItem)item.Clone(); + datItem.CopyMachineInformation(copyFrom); + Add(machine, datItem); + } + } + } + + // Now that every device is accounted for, add the new list of slot options, if they don't already exist + foreach (string slotOption in newSlotOptions) + { + if (!slotOptions.Contains(slotOption)) + { + var slotOptionItem = new SlotOption(); + slotOptionItem.SetFieldValue(Models.Metadata.SlotOption.DevNameKey, slotOption); + + var slotItem = new Slot(); + slotItem.SetFieldValue(Models.Metadata.Slot.SlotOptionKey, [slotOptionItem]); + + datItems.Add(slotItem); + } + } + } + } + + return foundnew; + } + + /// + /// Use device_ref and optionally slotoption tags to add roms to the children + /// + /// True if only child device sets are touched, false for non-device sets + /// True if slotoptions tags are used as well, false otherwise + private bool AddRomsFromDevicesImplDB(bool dev, bool useSlotOptions) + { + bool foundnew = false; + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + // Get the source for the first item + var source = ItemsDB.GetSourceForItem(items.First().Key); + + // Get the machine for the first item + var machine = ItemsDB.GetMachineForItem(items.First().Key); + if (machine.Value == null) + continue; + + // If the machine (is/is not) a device, we want to continue + if (dev ^ (machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) + continue; + + // Get all device reference names from the current machine + List deviceReferences = items.Values + .Where(i => i is DeviceRef) + .Select(i => i as DeviceRef) + .Select(dr => dr!.GetName()) + .Distinct() + .ToList(); + + // Get all slot option names from the current machine + List slotOptions = items.Values + .Where(i => i is Slot) + .Select(i => i as Slot) + .Where(s => s!.SlotOptionsSpecified) + .SelectMany(s => s!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) + .Select(so => so.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)) + .Distinct() + .ToList(); + + // If we're checking device references + if (deviceReferences.Count > 0) + { + // Loop through all names and check the corresponding machines + var newDeviceReferences = new HashSet(); + foreach (string? deviceReference in deviceReferences) + { + // If the device reference is invalid + if (deviceReference == null) + continue; + + // If the machine doesn't exist then we continue + var devItems = GetItemsForBucketDB(deviceReference); + if (devItems == null || devItems.Count == 0) + continue; + + // Add to the list of new device reference names + newDeviceReferences.UnionWith(devItems.Values + .Where(i => i is DeviceRef) + .Select(i => (i as DeviceRef)!.GetName()!)); + + // Set new machine information and add to the current machine + var copyFrom = ItemsDB.GetMachineForItem(items.First().Key); + if (copyFrom.Value == null) + continue; + + foreach (var item in devItems.Values) + { + // If the parent machine doesn't already contain this item, add it + if (!items.Values.Any(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) + && i.GetName() == item.GetName())) + { + // Set that we found new items + foundnew = true; + + // Clone the item and then add it + DatItem datItem = (item.Clone() as DatItem)!; + ItemsDB.AddItem(datItem, machine.Key, source.Key); + } + } + } + + // Now that every device reference is accounted for, add the new list of device references, if they don't already exist + foreach (string deviceReference in newDeviceReferences) + { + if (!deviceReferences.Contains(deviceReference)) + { + var deviceRef = new DeviceRef(); + deviceRef.SetName(deviceReference); + ItemsDB.AddItem(deviceRef, machine.Key, source.Key); + } + } + } + + // If we're checking slotoptions + if (useSlotOptions && slotOptions.Count > 0) + { + // Loop through all names and check the corresponding machines + var newSlotOptions = new HashSet(); + foreach (string? slotOption in slotOptions) + { + // If the slot option is invalid + if (slotOption == null) + continue; + + // If the machine doesn't exist then we continue + var slotItems = GetItemsForBucketDB(slotOption); + if (slotItems == null || slotItems.Count == 0) + continue; + + // Add to the list of new slot option names + newSlotOptions.UnionWith(slotItems.Values + .Where(i => i is Slot) + .Where(s => (s as Slot)!.SlotOptionsSpecified) + .SelectMany(s => (s as Slot)!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) + .Select(o => o.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)!)); + + // Set new machine information and add to the current machine + var copyFrom = ItemsDB.GetMachineForItem(GetItemsForBucketDB(game)!.First().Key); + if (copyFrom.Value == null) + continue; + + foreach (var item in slotItems.Values) + { + // If the parent machine doesn't already contain this item, add it + if (!items.Values.Any(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) + && i.GetName() == item.GetName())) + { + // Set that we found new items + foundnew = true; + + // Clone the item and then add it + DatItem datItem = (item.Clone() as DatItem)!; + ItemsDB.AddItem(datItem, machine.Key, source.Key); + } + } + } + + // Now that every device is accounted for, add the new list of slot options, if they don't already exist + foreach (string slotOption in newSlotOptions) + { + if (!slotOptions.Contains(slotOption)) + { + var slotOptionItem = new SlotOption(); + slotOptionItem.SetFieldValue(Models.Metadata.SlotOption.DevNameKey, slotOption); + + var slotItem = new Slot(); + slotItem.SetFieldValue(Models.Metadata.Slot.SlotOptionKey, [slotOptionItem]); + + ItemsDB.AddItem(slotItem, machine.Key, source.Key); + } + } + } + } + + return foundnew; + } + + /// + /// Use cloneof tags to add roms to the children, setting the new romof tag in the process + /// + private void AddRomsFromParentImpl() + { + List games = [.. Items.Keys]; + games.Sort(); + + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = Items[game]; + if (items == null || items.Count == 0) + continue; + + // Get the machine + var machine = items[0].GetFieldValue(DatItem.MachineKey); + if (machine == null) + continue; + + // Get the clone parent + string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); + if (string.IsNullOrEmpty(cloneOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = Items[cloneOf!]; + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we copy the items from the parent to the current game + DatItem copyFrom = items[0]; + foreach (DatItem item in parentItems) + { + DatItem datItem = (DatItem)item.Clone(); + datItem.CopyMachineInformation(copyFrom); + if (items.FindIndex(i => string.Equals(i.GetName(), datItem.GetName(), StringComparison.OrdinalIgnoreCase)) == -1 + && !items.Contains(datItem)) + { + Add(game, datItem); + } + } + + // Now we want to get the parent romof tag and put it in each of the items + items = Items[game]; + string? romof = Items[cloneOf!]![0].GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + foreach (DatItem item in items!) + { + item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); + } + } + } + + /// + /// Use cloneof tags to add roms to the children, setting the new romof tag in the process + /// + private void AddRomsFromParentImplDB() + { + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + // Get the source for the first item + var source = ItemsDB.GetSourceForItem(items.First().Key); + + // Get the machine for the first item in the list + var machine = ItemsDB.GetMachineForItem(items.First().Key); + if (machine.Value == null) + continue; + + // Get the clone parent + string? cloneOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); + if (string.IsNullOrEmpty(cloneOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = GetItemsForBucketDB(cloneOf!); + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we copy the items from the parent to the current game + foreach (var item in parentItems) + { + DatItem datItem = (DatItem)item.Value.Clone(); + if (items.Values.Any(i => i.GetName()?.ToLowerInvariant() == datItem.GetName()?.ToLowerInvariant()) + && items.Values.Any(i => i == datItem)) + { + ItemsDB.AddItem(datItem, machine.Key, source.Key); + } + } + + // Get the parent machine + var parentMachine = ItemsDB.GetMachineForItem(GetItemsForBucketDB(cloneOf!)!.First().Key); + if (parentMachine.Value == null) + continue; + + // Now we want to get the parent romof tag and put it in each of the items + items = GetItemsForBucketDB(game); + string? romof = parentMachine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + foreach (var key in items.Keys) + { + var itemMachine = ItemsDB.GetMachineForItem(key); + if (itemMachine.Value == null) + continue; + + itemMachine.Value.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); + } + } + } + + /// + /// Remove all BIOS and device sets + /// + private void RemoveBiosAndDeviceSetsImpl() + { + List games = [.. Items.Keys]; + games.Sort(); + + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = Items[game]; + if (items == null || items.Count == 0) + continue; + + // Get the machine + var machine = items[0].GetFieldValue(DatItem.MachineKey); + if (machine == null) + continue; + + // Remove flagged items + if ((machine.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true) + || (machine.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) + { + Remove(game); + } + } + } + + /// + /// Remove all BIOS and device sets + /// + private void RemoveBiosAndDeviceSetsImplDB() + { + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + // Get the machine + var machine = ItemsDB.GetMachineForItem(items.First().Key); + if (machine.Value == null) + continue; + + // Remove flagged items + if ((machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true) + || (machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) + { + foreach (var key in items.Keys) + { + ItemsDB.RemoveItem(key); + } + } + } + } + + /// + /// Use romof tags to remove bios roms from children + /// + /// True if only child Bios sets are touched, false for non-bios sets + private void RemoveBiosRomsFromChildImpl(bool bios) + { + // Loop through the romof tags + List games = [.. Items.Keys]; + games.Sort(); + + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = Items[game]; + if (items == null || items.Count == 0) + continue; + + // Get the machine + var machine = items[0].GetFieldValue(DatItem.MachineKey); + if (machine == null) + continue; + + // If the game (is/is not) a bios, we want to continue + if (bios ^ (machine.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true)) + continue; + + // Get the bios parent + string? romOf = machine.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + if (string.IsNullOrEmpty(romOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = Items[romOf!]; + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we remove the items that are in the parent from the current game + foreach (DatItem item in parentItems) + { + DatItem datItem = (DatItem)item.Clone(); + while (items.Contains(datItem)) + { + Items.Remove(game, datItem); + } + } + } + } + + /// + /// Use romof tags to remove bios roms from children + /// + /// True if only child Bios sets are touched, false for non-bios sets + internal void RemoveBiosRomsFromChildImplDB(bool bios) + { + // Loop through the romof tags + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + // Get the machine for the item + var machine = ItemsDB.GetMachineForItem(items.First().Key); + if (machine.Value == null) + continue; + + // If the game (is/is not) a bios, we want to continue + if (bios ^ (machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true)) + continue; + + // Get the bios parent + string? romOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + if (string.IsNullOrEmpty(romOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = GetItemsForBucketDB(romOf!); + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we remove the items that are in the parent from the current game + foreach (var item in parentItems) + { + var matchedItems = items.Where(i => i.Value == item.Value); + foreach (var match in matchedItems) + { + ItemsDB.RemoveItem(match.Key); + } + } + } + } + + /// + /// Use cloneof tags to remove roms from the children + /// + private void RemoveRomsFromChildImpl() + { + List games = [.. Items.Keys]; + games.Sort(); + + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = Items[game]; + if (items == null || items.Count == 0) + continue; + + // Get the machine + var machine = items[0].GetFieldValue(DatItem.MachineKey); + if (machine == null) + continue; + + // Get the clone parent + string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); + if (string.IsNullOrEmpty(cloneOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = Items[cloneOf!]; + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we remove the parent items from the current game + foreach (DatItem item in parentItems!) + { + DatItem datItem = (DatItem)item.Clone(); + while (items.Contains(datItem)) + { + Items.Remove(game, datItem); + } + } + + // Now we want to get the parent romof tag and put it in each of the remaining items + items = Items[game]; + string? romof = Items[cloneOf!]![0].GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + foreach (DatItem item in items!) + { + item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); + } + } + } + + /// + /// Use cloneof tags to remove roms from the children + /// + internal void RemoveRomsFromChildImplDB() + { + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + // Get the machine for the first item + var machine = ItemsDB.GetMachineForItem(items.First().Key); + if (machine.Value == null) + continue; + + // Get the clone parent + string? cloneOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); + if (string.IsNullOrEmpty(cloneOf)) + continue; + + // If the parent doesn't have any items, we want to continue + var parentItems = GetItemsForBucketDB(cloneOf!); + if (parentItems == null || parentItems.Count == 0) + continue; + + // If the parent exists and has items, we remove the parent items from the current game + foreach (var item in parentItems) + { + var matchedItems = items.Where(i => i.Value == item.Value); + foreach (var match in matchedItems) + { + ItemsDB.RemoveItem(match.Key); + } + } + + // Now we want to get the parent romof tag and put it in each of the remaining items + items = GetItemsForBucketDB(game); + machine = ItemsDB.GetMachineForItem(GetItemsForBucketDB(cloneOf!)!.First().Key); + if (machine.Value == null) + continue; + + string? romof = machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); + foreach (var item in items!) + { + machine = ItemsDB.GetMachineForItem(item.Key); + if (machine.Value == null) + continue; + + machine.Value.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); + } + } + } + + /// + /// Remove all romof and cloneof tags from all games + /// + private void RemoveTagsFromChildImpl() + { + List games = [.. Items.Keys]; + games.Sort(); + + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = Items[game]; + if (items == null || items.Count == 0) + continue; + + foreach (DatItem item in items) + { + item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.CloneOfKey, null); + item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.RomOfKey, null); + item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.SampleOfKey, null); + } + } + } + + /// + /// Remove all romof and cloneof tags from all games + /// + private void RemoveTagsFromChildImplDB() + { + List games = [.. ItemsDB.SortedKeys]; + foreach (string game in games) + { + // If the game has no items in it, we want to continue + var items = GetItemsForBucketDB(game); + if (items == null || items.Count == 0) + continue; + + foreach (long id in items.Keys) + { + var machine = ItemsDB.GetMachineForItem(id); + if (machine.Value == null) + continue; + + machine.Value.SetFieldValue(Models.Metadata.Machine.CloneOfKey, null); + machine.Value.SetFieldValue(Models.Metadata.Machine.RomOfKey, null); + machine.Value.SetFieldValue(Models.Metadata.Machine.SampleOfKey, null); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/SabreTools.DatFiles/DatFile.cs b/SabreTools.DatFiles/DatFile.cs index dd09be9e..891c9873 100644 --- a/SabreTools.DatFiles/DatFile.cs +++ b/SabreTools.DatFiles/DatFile.cs @@ -277,6 +277,15 @@ namespace SabreTools.DatFiles public List GetDuplicates(DatItem datItem, bool sorted = false) => Items.GetDuplicates(datItem, sorted); + /// + /// List all duplicates found in a DAT based on a DatItem + /// + /// Item to try to match + /// True if the DAT is already sorted accordingly, false otherwise (default) + /// List of matched DatItem objects + public Dictionary GetDuplicatesDB(DatItem datItem, bool sorted = false) + => ItemsDB.GetDuplicates(datItem, sorted); + /// /// List all duplicates found in a DAT based on a DatItem /// @@ -368,88 +377,6 @@ namespace SabreTools.DatFiles #endregion - #region Item Dictionary Passthrough - Splitting - - /// - /// Use romof tags to add roms to the children - /// - public void AddRomsFromBios() - { - Items.AddRomsFromBios(); - ItemsDB.AddRomsFromBios(); - } - - /// - /// Use device_ref and optionally slotoption tags to add roms to the children - /// - /// True if only child device sets are touched, false for non-device sets - /// True if slotoptions tags are used as well, false otherwise - public bool AddRomsFromDevices(bool dev, bool useSlotOptions) - { - bool foundnew = Items.AddRomsFromDevices(dev, useSlotOptions); - foundnew |= ItemsDB.AddRomsFromDevices(dev, useSlotOptions); - return foundnew; - } - - /// - /// Use cloneof tags to add roms to the parents, removing the child sets in the process - /// - /// True to add DatItems to subfolder of parent (not including Disk), false otherwise - /// True to skip checking for duplicate ROMs in parent, false otherwise - public void AddRomsFromChildren(bool subfolder, bool skipDedup) - { - Items.AddRomsFromChildren(subfolder, skipDedup); - ItemsDB.AddRomsFromChildren(subfolder, skipDedup); - } - - /// - /// Use cloneof tags to add roms to the children, setting the new romof tag in the process - /// - public void AddRomsFromParent() - { - Items.AddRomsFromParent(); - ItemsDB.AddRomsFromParent(); - } - - /// - /// Remove all BIOS and device sets - /// - public void RemoveBiosAndDeviceSets() - { - Items.RemoveBiosAndDeviceSets(); - ItemsDB.RemoveBiosAndDeviceSets(); - } - - /// - /// Use romof tags to remove bios roms from children - /// - /// True if only child Bios sets are touched, false for non-bios sets - public void RemoveBiosRomsFromChild(bool bios) - { - Items.RemoveBiosRomsFromChild(bios); - ItemsDB.RemoveBiosRomsFromChild(bios); - } - - /// - /// Use cloneof tags to remove roms from the children - /// - public void RemoveRomsFromChild() - { - Items.RemoveRomsFromChild(); - ItemsDB.RemoveRomsFromChild(); - } - - /// - /// Remove all romof and cloneof tags from all games - /// - public void RemoveTagsFromChild() - { - Items.RemoveTagsFromChild(); - ItemsDB.RemoveTagsFromChild(); - } - - #endregion - #region Parsing /// diff --git a/SabreTools.DatFiles/ItemDictionary.cs b/SabreTools.DatFiles/ItemDictionary.cs index aeaf37f6..cdfe0c08 100644 --- a/SabreTools.DatFiles/ItemDictionary.cs +++ b/SabreTools.DatFiles/ItemDictionary.cs @@ -515,6 +515,7 @@ namespace SabreTools.DatFiles #endregion + // TODO: All internal, can this be put into a better location? #region Bucketing /// @@ -557,6 +558,7 @@ namespace SabreTools.DatFiles /// Item to try to match /// True if the DAT is already sorted accordingly, false otherwise (default) /// List of matched DatItem objects + /// This also sets the remove flag on any duplicates found internal List GetDuplicates(DatItem datItem, bool sorted = false) { List output = []; @@ -582,7 +584,10 @@ namespace SabreTools.DatFiles { DatItem other = roms[i]; if (other.GetBoolFieldValue(DatItem.RemoveKey) == true) + { + left.Add(other); continue; + } if (datItem.Equals(other)) { @@ -1195,507 +1200,6 @@ namespace SabreTools.DatFiles // TODO: All internal, can this be put into a better location? #region Splitting - /// - /// Use romof tags to add roms to the children - /// - internal void AddRomsFromBios() - { - List games = [.. Keys]; - games.Sort(); - - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = this[game]; - if (items == null || items.Count == 0) - continue; - - // Get the machine - var machine = items[0].GetFieldValue(DatItem.MachineKey); - if (machine == null) - continue; - - // Get the bios parent - string? romOf = machine.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - if (string.IsNullOrEmpty(romOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = this[romOf!]; - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we copy the items from the parent to the current game - DatItem copyFrom = items[0]; - foreach (DatItem item in parentItems) - { - DatItem datItem = (DatItem)item.Clone(); - datItem.CopyMachineInformation(copyFrom); - if (items.FindIndex(i => i.GetName() == datItem.GetName()) == -1 && !items.Contains(datItem)) - Add(game, datItem); - } - } - } - - /// - /// Use device_ref and optionally slotoption tags to add roms to the children - /// - /// True if only child device sets are touched, false for non-device sets (default) - /// True if slotoptions tags are used as well, false otherwise - internal bool AddRomsFromDevices(bool dev, bool useSlotOptions) - { - bool foundnew = false; - List machines = [.. Keys]; - machines.Sort(); - - foreach (string machine in machines) - { - // If the machine doesn't have items, we continue - var datItems = this[machine]; - if (datItems == null || datItems.Count == 0) - continue; - - // If the machine (is/is not) a device, we want to continue - if (dev ^ (datItems[0].GetFieldValue(DatItem.MachineKey)!.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) - continue; - - // Get all device reference names from the current machine - List deviceReferences = datItems - .FindAll(i => i is DeviceRef) - .ConvertAll(i => i as DeviceRef) - .ConvertAll(dr => dr!.GetName()) - .Distinct() - .ToList(); - - // Get all slot option names from the current machine - List slotOptions = datItems - .FindAll(i => i is Slot) - .ConvertAll(i => i as Slot) - .FindAll(s => s!.SlotOptionsSpecified) - .SelectMany(s => s!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) - .Select(so => so.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)) - .Distinct() - .ToList(); - - // If we're checking device references - if (deviceReferences.Count > 0) - { - // Loop through all names and check the corresponding machines - var newDeviceReferences = new HashSet(); - foreach (string? deviceReference in deviceReferences) - { - // If the device reference is missing - if (string.IsNullOrEmpty(deviceReference)) - continue; - - // Add to the list of new device reference names - var devItems = this[deviceReference!]; - if (devItems == null || devItems.Count == 0) - continue; - - newDeviceReferences.UnionWith(devItems - .FindAll(i => i is DeviceRef) - .ConvertAll(i => (i as DeviceRef)!.GetName()!)); - - // Set new machine information and add to the current machine - DatItem copyFrom = datItems[0]; - foreach (DatItem item in devItems) - { - // If the parent machine doesn't already contain this item, add it - if (!datItems.Exists(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) && i.GetName() == item.GetName())) - { - // Set that we found new items - foundnew = true; - - // Clone the item and then add it - DatItem datItem = (DatItem)item.Clone(); - datItem.CopyMachineInformation(copyFrom); - Add(machine, datItem); - } - } - } - - // Now that every device reference is accounted for, add the new list of device references, if they don't already exist - foreach (string deviceReference in newDeviceReferences) - { - if (!deviceReferences.Contains(deviceReference)) - { - var deviceRef = new DeviceRef(); - deviceRef.SetName(deviceReference); - datItems.Add(deviceRef); - } - } - } - - // If we're checking slotoptions - if (useSlotOptions && slotOptions.Count > 0) - { - // Loop through all names and check the corresponding machines - var newSlotOptions = new HashSet(); - foreach (string? slotOption in slotOptions) - { - // If the slot option is missing - if (string.IsNullOrEmpty(slotOption)) - // If the machine doesn't exist then we continue - continue; - - // Add to the list of new slot option names - var slotItems = this[slotOption!]; - if (slotItems == null || slotItems.Count == 0) - continue; - - newSlotOptions.UnionWith(slotItems - .FindAll(i => i is Slot) - .FindAll(s => (s as Slot)!.SlotOptionsSpecified) - .SelectMany(s => (s as Slot)!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) - .Select(o => o.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)!)); - - // Set new machine information and add to the current machine - DatItem copyFrom = datItems[0]; - foreach (DatItem item in slotItems) - { - // If the parent machine doesn't already contain this item, add it - if (!datItems.Exists(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) && i.GetName() == item.GetName())) - { - // Set that we found new items - foundnew = true; - - // Clone the item and then add it - DatItem datItem = (DatItem)item.Clone(); - datItem.CopyMachineInformation(copyFrom); - Add(machine, datItem); - } - } - } - - // Now that every device is accounted for, add the new list of slot options, if they don't already exist - foreach (string slotOption in newSlotOptions) - { - if (!slotOptions.Contains(slotOption)) - { - var slotOptionItem = new SlotOption(); - slotOptionItem.SetFieldValue(Models.Metadata.SlotOption.DevNameKey, slotOption); - - var slotItem = new Slot(); - slotItem.SetFieldValue(Models.Metadata.Slot.SlotOptionKey, [slotOptionItem]); - - datItems.Add(slotItem); - } - } - } - } - - return foundnew; - } - - /// - /// Use cloneof tags to add roms to the children, setting the new romof tag in the process - /// - internal void AddRomsFromParent() - { - List games = [.. Keys]; - games.Sort(); - - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = this[game]; - if (items == null || items.Count == 0) - continue; - - // Get the machine - var machine = items[0].GetFieldValue(DatItem.MachineKey); - if (machine == null) - continue; - - // Get the clone parent - string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); - if (string.IsNullOrEmpty(cloneOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = this[cloneOf!]; - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we copy the items from the parent to the current game - DatItem copyFrom = items[0]; - foreach (DatItem item in parentItems) - { - DatItem datItem = (DatItem)item.Clone(); - datItem.CopyMachineInformation(copyFrom); - if (items.FindIndex(i => string.Equals(i.GetName(), datItem.GetName(), StringComparison.OrdinalIgnoreCase)) == -1 - && !items.Contains(datItem)) - { - Add(game, datItem); - } - } - - // Now we want to get the parent romof tag and put it in each of the items - items = this[game]; - string? romof = this[cloneOf!]![0].GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - foreach (DatItem item in items!) - { - item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); - } - } - } - - /// - /// Use cloneof tags to add roms to the parents, removing the child sets in the process - /// - /// True to add DatItems to subfolder of parent (not including Disk), false otherwise - /// True to skip checking for duplicate ROMs in parent, false otherwise - internal void AddRomsFromChildren(bool subfolder, bool skipDedup) - { - List games = [.. Keys]; - games.Sort(); - - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = this[game]; - if (items == null || items.Count == 0) - continue; - - // Get the machine - var machine = items[0].GetFieldValue(DatItem.MachineKey); - if (machine == null) - continue; - - // Get the clone parent - string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); - if (string.IsNullOrEmpty(cloneOf)) - continue; - - // Get the parent items - var parentItems = this[cloneOf!]; - - // Otherwise, move the items from the current game to a subfolder of the parent game - DatItem copyFrom; - if (parentItems == null || parentItems.Count == 0) - { - copyFrom = new Rom(); - copyFrom.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.NameKey, cloneOf); - copyFrom.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.DescriptionKey, cloneOf); - } - else - { - copyFrom = parentItems[0]; - } - - items = this[game]; - foreach (DatItem item in items!) - { - // Special disk handling - if (item is Disk disk) - { - string? mergeTag = disk.GetStringFieldValue(Models.Metadata.Disk.MergeKey); - - // If the merge tag exists and the parent already contains it, skip - if (mergeTag != null && this[cloneOf!]! - .FindAll(i => i is Disk) - .ConvertAll(i => (i as Disk)!.GetName()).Contains(mergeTag)) - { - continue; - } - - // If the merge tag exists but the parent doesn't contain it, add to parent - else if (mergeTag != null && !this[cloneOf!]! - .FindAll(i => i is Disk) - .ConvertAll(i => (i as Disk)!.GetName()).Contains(mergeTag)) - { - disk.CopyMachineInformation(copyFrom); - Add(cloneOf!, disk); - } - - // If there is no merge tag, add to parent - else if (mergeTag == null) - { - disk.CopyMachineInformation(copyFrom); - Add(cloneOf!, disk); - } - } - - // Special rom handling - else if (item is Rom rom) - { - // If the merge tag exists and the parent already contains it, skip - if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && this[cloneOf!]! - .FindAll(i => i is Rom) - .ConvertAll(i => (i as Rom)!.GetName()) - .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) - { - continue; - } - - // If the merge tag exists but the parent doesn't contain it, add to subfolder of parent - else if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && !this[cloneOf!]! - .FindAll(i => i is Rom) - .ConvertAll(i => (i as Rom)!.GetName()) - .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) - { - if (subfolder) - rom.SetName($"{rom.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)}\\{rom.GetName()}"); - - rom.CopyMachineInformation(copyFrom); - Add(cloneOf!, rom); - } - - // If the parent doesn't already contain this item, add to subfolder of parent - else if (!this[cloneOf!]!.Contains(item) || skipDedup) - { - if (subfolder) - rom.SetName($"{item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)}\\{rom.GetName()}"); - - rom.CopyMachineInformation(copyFrom); - Add(cloneOf!, rom); - } - } - - // All other that would be missing to subfolder of parent - else if (!this[cloneOf!]!.Contains(item)) - { - if (subfolder) - item.SetName($"{item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)}\\{item.GetName()}"); - - item.CopyMachineInformation(copyFrom); - Add(cloneOf!, item); - } - } - - // Then, remove the old game so it's not picked up by the writer - Remove(game); - } - } - - /// - /// Remove all BIOS and device sets - /// - internal void RemoveBiosAndDeviceSets() - { - List games = [.. Keys]; - games.Sort(); - - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = this[game]; - if (items == null || items.Count == 0) - continue; - - // Get the machine - var machine = items[0].GetFieldValue(DatItem.MachineKey); - if (machine == null) - continue; - - // Remove flagged items - if ((machine.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true) - || (machine.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) - { - Remove(game); - } - } - } - - /// - /// Use romof tags to remove bios roms from children - /// - /// True if only child Bios sets are touched, false for non-bios sets - internal void RemoveBiosRomsFromChild(bool bios) - { - // Loop through the romof tags - List games = [.. Keys]; - games.Sort(); - - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = this[game]; - if (items == null || items.Count == 0) - continue; - - // Get the machine - var machine = items[0].GetFieldValue(DatItem.MachineKey); - if (machine == null) - continue; - - // If the game (is/is not) a bios, we want to continue - if (bios ^ (machine.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true)) - continue; - - // Get the bios parent - string? romOf = machine.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - if (string.IsNullOrEmpty(romOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = this[romOf!]; - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we remove the items that are in the parent from the current game - foreach (DatItem item in parentItems) - { - DatItem datItem = (DatItem)item.Clone(); - while (items.Contains(datItem)) - { - Remove(game, datItem); - } - } - } - } - - /// - /// Use cloneof tags to remove roms from the children - /// - internal void RemoveRomsFromChild() - { - List games = [.. Keys]; - games.Sort(); - - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = this[game]; - if (items == null || items.Count == 0) - continue; - - // Get the machine - var machine = items[0].GetFieldValue(DatItem.MachineKey); - if (machine == null) - continue; - - // Get the clone parent - string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); - if (string.IsNullOrEmpty(cloneOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = this[cloneOf!]; - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we remove the parent items from the current game - foreach (DatItem item in parentItems!) - { - DatItem datItem = (DatItem)item.Clone(); - while (items.Contains(datItem)) - { - Remove(game, datItem); - } - } - - // Now we want to get the parent romof tag and put it in each of the remaining items - items = this[game]; - string? romof = this[cloneOf!]![0].GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - foreach (DatItem item in items!) - { - item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); - } - } - } - /// /// Remove all romof and cloneof tags from all games /// diff --git a/SabreTools.DatFiles/ItemDictionaryDB.cs b/SabreTools.DatFiles/ItemDictionaryDB.cs index b59dae89..de69c4ba 100644 --- a/SabreTools.DatFiles/ItemDictionaryDB.cs +++ b/SabreTools.DatFiles/ItemDictionaryDB.cs @@ -96,11 +96,12 @@ namespace SabreTools.DatFiles /// /// Internal dictionary for item to machine mappings /// + /// TODO: Make private when access issues are figured out [JsonIgnore, XmlIgnore] #if NET40_OR_GREATER || NETCOREAPP - private readonly ConcurrentDictionary _itemToMachineMapping = []; + internal readonly ConcurrentDictionary _itemToMachineMapping = []; #else - private readonly Dictionary _itemToMachineMapping = []; + internal readonly Dictionary _itemToMachineMapping = []; #endif /// @@ -116,11 +117,12 @@ namespace SabreTools.DatFiles /// /// Internal dictionary representing the current buckets /// + /// TODO: Make private when access issues are figured out [JsonIgnore, XmlIgnore] #if NET40_OR_GREATER || NETCOREAPP - private readonly ConcurrentDictionary> _buckets = []; + internal readonly ConcurrentDictionary> _buckets = []; #else - private readonly Dictionary> _buckets = []; + internal readonly Dictionary> _buckets = []; #endif /// @@ -561,7 +563,7 @@ namespace SabreTools.DatFiles /// /// Add an item, returning the insert index /// - private long AddItem(DatItem item, long machineIndex, long sourceIndex) + internal long AddItem(DatItem item, long machineIndex, long sourceIndex) { // Add the item with a new index _items[_itemIndex++] = item; @@ -584,6 +586,7 @@ namespace SabreTools.DatFiles #endregion + // TODO: All internal, can this be put into a better location? #region Bucketing /// @@ -663,6 +666,7 @@ namespace SabreTools.DatFiles /// Item to try to match /// True if the DAT is already sorted accordingly, false otherwise (default) /// List of matched DatItem objects + /// This also sets the remove flag on any duplicates found internal Dictionary GetDuplicates(KeyValuePair datItem, bool sorted = false) { Dictionary output = []; @@ -684,7 +688,10 @@ namespace SabreTools.DatFiles foreach (var rom in roms) { if (rom.Value.GetBoolFieldValue(DatItem.RemoveKey) == true) + { + left[rom.Key] = rom.Value; continue; + } if (datItem.Value.Equals(rom.Value)) { @@ -1603,536 +1610,6 @@ namespace SabreTools.DatFiles // TODO: All internal, can this be put into a better location? #region Splitting - /// - /// Use romof tags to add roms to the children - /// - internal void AddRomsFromBios() - { - List games = [.. SortedKeys]; - foreach (string game in games) - { - // Get the items for this game - var items = GetItemsForBucket(game); - if (items == null || items.Count == 0) - continue; - - // Get the source for the first item - var source = GetSourceForItem(items.First().Key); - - // Get the machine for the first item - var machine = GetMachineForItem(items.First().Key); - if (machine.Value == null) - continue; - - // Get the bios parent - string? romOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - if (string.IsNullOrEmpty(romOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = GetItemsForBucket(romOf!); - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we copy the items from the parent to the current game - foreach (var item in parentItems) - { - DatItem datItem = (item.Value.Clone() as DatItem)!; - if (items.Any(i => i.Value.GetName() == datItem.GetName()) - && items.Any(i => i.Value == datItem)) - { - AddItem(datItem, machine.Key, source.Key); - } - } - } - } - - /// - /// Use device_ref and optionally slotoption tags to add roms to the children - /// - /// True if only child device sets are touched, false for non-device sets - /// True if slotoptions tags are used as well, false otherwise - internal bool AddRomsFromDevices(bool dev, bool useSlotOptions) - { - bool foundnew = false; - List games = [.. SortedKeys]; - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = GetItemsForBucket(game); - if (items == null || items.Count == 0) - continue; - - // Get the source for the first item - var source = GetSourceForItem(items.First().Key); - - // Get the machine for the first item - var machine = GetMachineForItem(items.First().Key); - if (machine.Value == null) - continue; - - // If the machine (is/is not) a device, we want to continue - if (dev ^ (machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) - continue; - - // Get all device reference names from the current machine - List deviceReferences = items.Values - .Where(i => i is DeviceRef) - .Select(i => i as DeviceRef) - .Select(dr => dr!.GetName()) - .Distinct() - .ToList(); - - // Get all slot option names from the current machine - List slotOptions = items.Values - .Where(i => i is Slot) - .Select(i => i as Slot) - .Where(s => s!.SlotOptionsSpecified) - .SelectMany(s => s!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) - .Select(so => so.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)) - .Distinct() - .ToList(); - - // If we're checking device references - if (deviceReferences.Count > 0) - { - // Loop through all names and check the corresponding machines - var newDeviceReferences = new HashSet(); - foreach (string? deviceReference in deviceReferences) - { - // If the device reference is invalid - if (deviceReference == null) - continue; - - // If the machine doesn't exist then we continue - var devItems = GetItemsForBucket(deviceReference); - if (devItems == null || devItems.Count == 0) - continue; - - // Add to the list of new device reference names - newDeviceReferences.UnionWith(devItems.Values - .Where(i => i is DeviceRef) - .Select(i => (i as DeviceRef)!.GetName()!)); - - // Set new machine information and add to the current machine - var copyFrom = GetMachineForItem(items.First().Key); - if (copyFrom.Value == null) - continue; - - foreach (var item in devItems.Values) - { - // If the parent machine doesn't already contain this item, add it - if (!items.Values.Any(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) - && i.GetName() == item.GetName())) - { - // Set that we found new items - foundnew = true; - - // Clone the item and then add it - DatItem datItem = (item.Clone() as DatItem)!; - AddItem(datItem, machine.Key, source.Key); - } - } - } - - // Now that every device reference is accounted for, add the new list of device references, if they don't already exist - foreach (string deviceReference in newDeviceReferences) - { - if (!deviceReferences.Contains(deviceReference)) - { - var deviceRef = new DeviceRef(); - deviceRef.SetName(deviceReference); - AddItem(deviceRef, machine.Key, source.Key); - } - } - } - - // If we're checking slotoptions - if (useSlotOptions && slotOptions.Count > 0) - { - // Loop through all names and check the corresponding machines - var newSlotOptions = new HashSet(); - foreach (string? slotOption in slotOptions) - { - // If the slot option is invalid - if (slotOption == null) - continue; - - // If the machine doesn't exist then we continue - var slotItems = GetItemsForBucket(slotOption); - if (slotItems == null || slotItems.Count == 0) - continue; - - // Add to the list of new slot option names - newSlotOptions.UnionWith(slotItems.Values - .Where(i => i is Slot) - .Where(s => (s as Slot)!.SlotOptionsSpecified) - .SelectMany(s => (s as Slot)!.GetFieldValue(Models.Metadata.Slot.SlotOptionKey)!) - .Select(o => o.GetStringFieldValue(Models.Metadata.SlotOption.DevNameKey)!)); - - // Set new machine information and add to the current machine - var copyFrom = GetMachineForItem(GetItemsForBucket(game)!.First().Key); - if (copyFrom.Value == null) - continue; - - foreach (var item in slotItems.Values) - { - // If the parent machine doesn't already contain this item, add it - if (!items.Values.Any(i => i.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) == item.GetStringFieldValue(Models.Metadata.DatItem.TypeKey) - && i.GetName() == item.GetName())) - { - // Set that we found new items - foundnew = true; - - // Clone the item and then add it - DatItem datItem = (item.Clone() as DatItem)!; - AddItem(datItem, machine.Key, source.Key); - } - } - } - - // Now that every device is accounted for, add the new list of slot options, if they don't already exist - foreach (string slotOption in newSlotOptions) - { - if (!slotOptions.Contains(slotOption)) - { - var slotOptionItem = new SlotOption(); - slotOptionItem.SetFieldValue(Models.Metadata.SlotOption.DevNameKey, slotOption); - - var slotItem = new Slot(); - slotItem.SetFieldValue(Models.Metadata.Slot.SlotOptionKey, [slotOptionItem]); - - AddItem(slotItem, machine.Key, source.Key); - } - } - } - } - - return foundnew; - } - - /// - /// Use cloneof tags to add roms to the children, setting the new romof tag in the process - /// - internal void AddRomsFromParent() - { - List games = [.. SortedKeys]; - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = GetItemsForBucket(game); - if (items == null || items.Count == 0) - continue; - - // Get the source for the first item - var source = GetSourceForItem(items.First().Key); - - // Get the machine for the first item in the list - var machine = GetMachineForItem(items.First().Key); - if (machine.Value == null) - continue; - - // Get the clone parent - string? cloneOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); - if (string.IsNullOrEmpty(cloneOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = GetItemsForBucket(cloneOf!); - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we copy the items from the parent to the current game - foreach (var item in parentItems) - { - DatItem datItem = (DatItem)item.Value.Clone(); - if (items.Values.Any(i => i.GetName()?.ToLowerInvariant() == datItem.GetName()?.ToLowerInvariant()) - && items.Values.Any(i => i == datItem)) - { - AddItem(datItem, machine.Key, source.Key); - } - } - - // Get the parent machine - var parentMachine = GetMachineForItem(GetItemsForBucket(cloneOf!)!.First().Key); - if (parentMachine.Value == null) - continue; - - // Now we want to get the parent romof tag and put it in each of the items - items = GetItemsForBucket(game); - string? romof = parentMachine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - foreach (var key in items.Keys) - { - var itemMachine = GetMachineForItem(key); - if (itemMachine.Value == null) - continue; - - itemMachine.Value.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); - } - } - } - - /// - /// Use cloneof tags to add roms to the parents, removing the child sets in the process - /// - /// True to add DatItems to subfolder of parent (not including Disk), false otherwise - /// True to skip checking for duplicate ROMs in parent, false otherwise - internal void AddRomsFromChildren(bool subfolder, bool skipDedup) - { - List games = [.. SortedKeys]; - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = GetItemsForBucket(game); - if (items == null || items.Count == 0) - continue; - - // Get the machine for the first item - var machine = GetMachineForItem(items.First().Key); - if (machine.Value == null) - continue; - - // Get the clone parent - string? cloneOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); - if (string.IsNullOrEmpty(cloneOf)) - continue; - - // Get the clone parent machine - var cloneOfMachine = GetMachine(cloneOf); - if (cloneOfMachine.Value == null) - continue; - - items = GetItemsForBucket(game); - foreach (var item in items) - { - // Get the parent items and current machine name - var parentItems = GetItemsForBucket(cloneOf!); - if (parentItems == null || parentItems.Count == 0) - continue; - - string? machineName = GetMachineForItem(item.Key).Value? - .GetStringFieldValue(Models.Metadata.Machine.NameKey); - - // Special disk handling - if (item.Value is Disk disk) - { - string? mergeTag = disk.GetStringFieldValue(Models.Metadata.Disk.MergeKey); - - // If the merge tag exists and the parent already contains it, skip - if (mergeTag != null && parentItems.Values - .Where(i => i is Disk) - .Select(i => (i as Disk)!.GetName()) - .Contains(mergeTag)) - { - continue; - } - - // If the merge tag exists but the parent doesn't contain it, add to parent - else if (mergeTag != null && !parentItems.Values - .Where(i => i is Disk) - .Select(i => (i as Disk)!.GetName()) - .Contains(mergeTag)) - { - _itemToMachineMapping[item.Key] = cloneOfMachine.Key; - _buckets[cloneOf!].Add(item.Key); - } - - // If there is no merge tag, add to parent - else if (mergeTag == null) - { - _itemToMachineMapping[item.Key] = cloneOfMachine.Key; - _buckets[cloneOf!].Add(item.Key); - } - } - - // Special rom handling - else if (item.Value is Rom rom) - { - // If the merge tag exists and the parent already contains it, skip - if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && parentItems.Values - .Where(i => i is Rom) - .Select(i => (i as Rom)!.GetName()) - .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) - { - continue; - } - - // If the merge tag exists but the parent doesn't contain it, add to subfolder of parent - else if (rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey) != null && !parentItems.Values - .Where(i => i is Rom) - .Select(i => (i as Rom)!.GetName()) - .Contains(rom.GetStringFieldValue(Models.Metadata.Rom.MergeKey))) - { - if (subfolder) - rom.SetName($"{machineName}\\{rom.GetName()}"); - - _itemToMachineMapping[item.Key] = cloneOfMachine.Key; - _buckets[cloneOf!].Add(item.Key); - } - - // If the parent doesn't already contain this item, add to subfolder of parent - else if (!parentItems.Contains(item) || skipDedup) - { - if (subfolder) - rom.SetName($"{machineName}\\{rom.GetName()}"); - - _itemToMachineMapping[item.Key] = cloneOfMachine.Key; - _buckets[cloneOf!].Add(item.Key); - } - } - - // All other that would be missing to subfolder of parent - else if (!parentItems.Contains(item)) - { - if (subfolder) - item.Value.SetName($"{machineName}\\{item.Value.GetName()}"); - - _itemToMachineMapping[item.Key] = cloneOfMachine.Key; - _buckets[cloneOf!].Add(item.Key); - } - } - - // Then, remove the old game so it's not picked up by the writer -#if NET40_OR_GREATER || NETCOREAPP - _buckets.TryRemove(game, out _); -#else - _buckets.Remove(game); -#endif - } - } - - /// - /// Remove all BIOS and device sets - /// - internal void RemoveBiosAndDeviceSets() - { - List games = [.. SortedKeys]; - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = GetItemsForBucket(game); - if (items == null || items.Count == 0) - continue; - - // Get the machine - var machine = GetMachineForItem(items.First().Key); - if (machine.Value == null) - continue; - - // Remove flagged items - if ((machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true) - || (machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsDeviceKey) == true)) - { - foreach (var key in items.Keys) - { - RemoveItem(key); - } - } - } - } - - /// - /// Use romof tags to remove bios roms from children - /// - /// True if only child Bios sets are touched, false for non-bios sets - internal void RemoveBiosRomsFromChild(bool bios) - { - // Loop through the romof tags - List games = [.. SortedKeys]; - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = GetItemsForBucket(game); - if (items == null || items.Count == 0) - continue; - - // Get the machine for the item - var machine = GetMachineForItem(items.First().Key); - if (machine.Value == null) - continue; - - // If the game (is/is not) a bios, we want to continue - if (bios ^ (machine.Value.GetBoolFieldValue(Models.Metadata.Machine.IsBiosKey) == true)) - continue; - - // Get the bios parent - string? romOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - if (string.IsNullOrEmpty(romOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = GetItemsForBucket(romOf!); - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we remove the items that are in the parent from the current game - foreach (var item in parentItems) - { - var matchedItems = items.Where(i => i.Value == item.Value); - foreach (var match in matchedItems) - { - RemoveItem(match.Key); - } - } - } - } - - /// - /// Use cloneof tags to remove roms from the children - /// - internal void RemoveRomsFromChild() - { - List games = [.. SortedKeys]; - foreach (string game in games) - { - // If the game has no items in it, we want to continue - var items = GetItemsForBucket(game); - if (items == null || items.Count == 0) - continue; - - // Get the machine for the first item - var machine = GetMachineForItem(items.First().Key); - if (machine.Value == null) - continue; - - // Get the clone parent - string? cloneOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey); - if (string.IsNullOrEmpty(cloneOf)) - continue; - - // If the parent doesn't have any items, we want to continue - var parentItems = GetItemsForBucket(cloneOf!); - if (parentItems == null || parentItems.Count == 0) - continue; - - // If the parent exists and has items, we remove the parent items from the current game - foreach (var item in parentItems) - { - var matchedItems = items.Where(i => i.Value == item.Value); - foreach (var match in matchedItems) - { - RemoveItem(match.Key); - } - } - - // Now we want to get the parent romof tag and put it in each of the remaining items - items = GetItemsForBucket(game); - machine = GetMachineForItem(GetItemsForBucket(cloneOf!)!.First().Key); - if (machine.Value == null) - continue; - - string? romof = machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey); - foreach (var item in items!) - { - machine = GetMachineForItem(item.Key); - if (machine.Value == null) - continue; - - machine.Value.SetFieldValue(Models.Metadata.Machine.RomOfKey, romof); - } - } - } - /// /// Remove all romof and cloneof tags from all games ///