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
///