diff --git a/SabreTools.DatFiles/DatFile.Filtering.cs b/SabreTools.DatFiles/DatFile.Filtering.cs
new file mode 100644
index 00000000..f8131670
--- /dev/null
+++ b/SabreTools.DatFiles/DatFile.Filtering.cs
@@ -0,0 +1,698 @@
+using System;
+using System.Collections.Generic;
+#if NET40_OR_GREATER || NETCOREAPP
+using System.Collections.Concurrent;
+#endif
+#if NET40_OR_GREATER || NETCOREAPP
+using System.Threading.Tasks;
+#endif
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.DatItems.Formats;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Linq;
+
+namespace SabreTools.DatFiles
+{
+ // TODO: Write tests for all of these implementations
+ public partial class DatFile
+ {
+ #region Constants
+
+ ///
+ /// Scene name Regex pattern
+ ///
+ private const string SceneNamePattern = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";
+
+ #endregion
+
+ #region Filtering
+
+ ///
+ /// Use game descriptions as names, updating cloneof/romof/sampleof
+ ///
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ public void MachineDescriptionToName(bool throwOnError = false)
+ {
+ MachineDescriptionToNameImpl(throwOnError);
+ MachineDescriptionToNameImplDB(throwOnError);
+ }
+
+ ///
+ /// Ensure that all roms are in their own game (or at least try to ensure)
+ ///
+ public void SetOneRomPerGame()
+ {
+ SetOneRomPerGameImpl();
+ SetOneRomPerGameImplDB();
+ }
+
+ ///
+ /// Filter a DAT using 1G1R logic given an ordered set of regions
+ ///
+ /// List of regions in order of priority
+ ///
+ /// In the most technical sense, the way that the region list is being used does not
+ /// confine its values to be just regions. Since it's essentially acting like a
+ /// specialized version of the machine name filter, anything that is usually encapsulated
+ /// in parenthesis would be matched on, including disc numbers, languages, editions,
+ /// and anything else commonly used. Please note that, unlike other existing 1G1R
+ /// solutions, this does not have the ability to contain custom mappings of parent
+ /// to clone sets based on name, nor does it have the ability to match on the
+ /// Release DatItem type.
+ ///
+ public void SetOneGamePerRegion(List regionList)
+ {
+ SetOneGamePerRegionImpl(regionList);
+ SetOneGamePerRegionImplDB(regionList);
+ }
+
+ ///
+ /// Strip the dates from the beginning of scene-style set names
+ ///
+ public void StripSceneDatesFromItems()
+ {
+ StripSceneDatesFromItemsImpl();
+ StripSceneDatesFromItemsImplDB();
+ }
+
+ #endregion
+
+ #region Filtering Implementations
+
+ ///
+ /// Create machine to description mapping dictionary
+ ///
+ private IDictionary CreateMachineToDescriptionMapping()
+ {
+#if NET40_OR_GREATER || NETCOREAPP
+ ConcurrentDictionary mapping = new();
+#else
+ Dictionary mapping = [];
+#endif
+#if NET452_OR_GREATER || NETCOREAPP
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+#elif NET40_OR_GREATER
+ Parallel.ForEach(Items.Keys, key =>
+#else
+ foreach (var key in Items.Keys)
+#endif
+ {
+ var items = GetItemsForBucket(key);
+ if (items == null)
+#if NET40_OR_GREATER || NETCOREAPP
+ return;
+#else
+ continue;
+#endif
+
+ foreach (DatItem item in items)
+ {
+ // Get the current machine
+ var machine = item.GetFieldValue(DatItem.MachineKey);
+ if (machine == null)
+ continue;
+
+ // Get the values to check against
+ string? machineName = machine.GetStringFieldValue(Models.Metadata.Machine.NameKey);
+ string? machineDesc = machine.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey);
+ if (machineName == null || machineDesc == null)
+ continue;
+
+ // If the key mapping doesn't exist, add it
+#if NET40_OR_GREATER || NETCOREAPP
+ mapping.TryAdd(machineName, machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -"));
+#else
+ mapping[machineName] = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
+#endif
+ }
+#if NET40_OR_GREATER || NETCOREAPP
+ });
+#else
+ }
+#endif
+
+ return mapping;
+ }
+
+ ///
+ /// Create machine to description mapping dictionary
+ ///
+ private Dictionary CreateMachineToDescriptionMappingDB()
+ {
+ Dictionary mapping = [];
+ foreach (var machine in GetMachinesDB())
+ {
+ // Get the current machine
+ if (machine.Value == null)
+ continue;
+
+ // Get the values to check against
+ string? machineName = machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey);
+ string? machineDesc = machine.Value.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey);
+ if (machineName == null || machineDesc == null)
+ continue;
+
+ // If the key mapping doesn't exist, add it
+ mapping[machineName] = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
+ }
+
+ return mapping;
+ }
+
+ ///
+ /// Use game descriptions as names, updating cloneof/romof/sampleof
+ ///
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ private void MachineDescriptionToNameImpl(bool throwOnError = false)
+ {
+ try
+ {
+ // First we want to get a mapping for all games to description
+ var mapping = CreateMachineToDescriptionMapping();
+
+ // Now we loop through every item and update accordingly
+ UpdateMachineNamesFromDescriptions(mapping);
+ }
+ catch (Exception ex) when (!throwOnError)
+ {
+ _logger.Warning(ex.ToString());
+ }
+ }
+
+ ///
+ /// Use game descriptions as names, updating cloneof/romof/sampleof
+ ///
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ private void MachineDescriptionToNameImplDB(bool throwOnError = false)
+ {
+ try
+ {
+ // First we want to get a mapping for all games to description
+ var mapping = CreateMachineToDescriptionMappingDB();
+
+ // Now we loop through every item and update accordingly
+ UpdateMachineNamesFromDescriptionsDB(mapping);
+ }
+ catch (Exception ex) when (!throwOnError)
+ {
+ _logger.Warning(ex.ToString());
+ }
+ }
+
+ ///
+ /// Filter a DAT using 1G1R logic given an ordered set of regions
+ ///
+ /// List of regions in order of priority
+ ///
+ /// In the most technical sense, the way that the region list is being used does not
+ /// confine its values to be just regions. Since it's essentially acting like a
+ /// specialized version of the machine name filter, anything that is usually encapsulated
+ /// in parenthesis would be matched on, including disc numbers, languages, editions,
+ /// and anything else commonly used. Please note that, unlike other existing 1G1R
+ /// solutions, this does not have the ability to contain custom mappings of parent
+ /// to clone sets based on name, nor does it have the ability to match on the
+ /// Release DatItem type.
+ ///
+ private void SetOneGamePerRegionImpl(List regionList)
+ {
+ // If we have null region list, make it empty
+ regionList ??= [];
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
+
+ // Then we want to get a mapping of all machines to parents
+ Dictionary> parents = [];
+ foreach (string key in Items.Keys)
+ {
+ DatItem item = GetItemsForBucket(key)[0];
+
+ // Get machine information
+ Machine? machine = item.GetFieldValue(DatItem.MachineKey);
+ string? machineName = machine?.GetStringFieldValue(Models.Metadata.Machine.NameKey)?.ToLowerInvariant();
+ if (machine == null || machineName == null)
+ continue;
+
+ // Get the string values
+ string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)?.ToLowerInvariant();
+ string? romOf = machine.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)?.ToLowerInvariant();
+
+ // Match on CloneOf first
+ if (!string.IsNullOrEmpty(cloneOf))
+ {
+ if (!parents.ContainsKey(cloneOf!))
+ parents.Add(cloneOf!, []);
+
+ parents[cloneOf!].Add(machineName);
+ }
+
+ // Then by RomOf
+ else if (!string.IsNullOrEmpty(romOf))
+ {
+ if (!parents.ContainsKey(romOf!))
+ parents.Add(romOf!, []);
+
+ parents[romOf!].Add(machineName);
+ }
+
+ // Otherwise, treat it as a parent
+ else
+ {
+ if (!parents.ContainsKey(machineName))
+ parents.Add(machineName, []);
+
+ parents[machineName].Add(machineName);
+ }
+ }
+
+ // Once we have the full list of mappings, filter out games to keep
+ foreach (string key in parents.Keys)
+ {
+ // Find the first machine that matches the regions in order, if possible
+ string? machine = default;
+ foreach (string region in regionList)
+ {
+ machine = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
+ if (machine != default)
+ break;
+ }
+
+ // If we didn't get a match, use the parent
+ if (machine == default)
+ machine = key;
+
+ // Remove the key from the list
+ parents[key].Remove(machine);
+
+ // Remove the rest of the items from this key
+ parents[key].ForEach(k => Remove(k));
+ }
+
+ // Finally, strip out the parent tags
+ RemoveMachineRelationshipTagsImpl();
+ }
+
+ ///
+ /// Filter a DAT using 1G1R logic given an ordered set of regions
+ ///
+ /// List of regions in order of priority
+ ///
+ /// In the most technical sense, the way that the region list is being used does not
+ /// confine its values to be just regions. Since it's essentially acting like a
+ /// specialized version of the machine name filter, anything that is usually encapsulated
+ /// in parenthesis would be matched on, including disc numbers, languages, editions,
+ /// and anything else commonly used. Please note that, unlike other existing 1G1R
+ /// solutions, this does not have the ability to contain custom mappings of parent
+ /// to clone sets based on name, nor does it have the ability to match on the
+ /// Release DatItem type.
+ ///
+ private void SetOneGamePerRegionImplDB(List regionList)
+ {
+ // If we have null region list, make it empty
+ regionList ??= [];
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
+
+ // Then we want to get a mapping of all machines to parents
+ Dictionary> parents = [];
+ foreach (string key in ItemsDB.SortedKeys)
+ {
+ var items = GetItemsForBucketDB(key);
+ if (items == null || items.Count == 0)
+ continue;
+
+ var item = items.First();
+ var machine = ItemsDB.GetMachineForItem(item.Key);
+ if (machine.Value == null)
+ continue;
+
+ // Get machine information
+ Machine? machineObj = machine.Value.GetFieldValue(DatItem.MachineKey);
+ string? machineName = machineObj?.GetStringFieldValue(Models.Metadata.Machine.NameKey)?.ToLowerInvariant();
+ if (machineObj == null || machineName == null)
+ continue;
+
+ // Get the string values
+ string? cloneOf = machineObj.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)?.ToLowerInvariant();
+ string? romOf = machineObj.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)?.ToLowerInvariant();
+
+ // Match on CloneOf first
+ if (!string.IsNullOrEmpty(cloneOf))
+ {
+ if (!parents.ContainsKey(cloneOf!))
+ parents.Add(cloneOf!, []);
+
+ parents[cloneOf!].Add(machineName);
+ }
+
+ // Then by RomOf
+ else if (!string.IsNullOrEmpty(romOf))
+ {
+ if (!parents.ContainsKey(romOf!))
+ parents.Add(romOf!, []);
+
+ parents[romOf!].Add(machineName);
+ }
+
+ // Otherwise, treat it as a parent
+ else
+ {
+ if (!parents.ContainsKey(machineName))
+ parents.Add(machineName, []);
+
+ parents[machineName].Add(machineName);
+ }
+ }
+
+ // Once we have the full list of mappings, filter out games to keep
+ foreach (string key in parents.Keys)
+ {
+ // Find the first machine that matches the regions in order, if possible
+ string? machine = default;
+ foreach (string region in regionList)
+ {
+ machine = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
+ if (machine != default)
+ break;
+ }
+
+ // If we didn't get a match, use the parent
+ if (machine == default)
+ machine = key;
+
+ // Remove the key from the list
+ parents[key].Remove(machine);
+
+ // Remove the rest of the items from this key
+ parents[key].ForEach(k => ItemsDB.RemoveMachine(k));
+ }
+
+ // Finally, strip out the parent tags
+ RemoveMachineRelationshipTagsImplDB();
+ }
+
+ ///
+ /// Ensure that all roms are in their own game (or at least try to ensure)
+ ///
+ private void SetOneRomPerGameImpl()
+ {
+ // For each rom, we want to update the game to be "/"
+#if NET452_OR_GREATER || NETCOREAPP
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+#elif NET40_OR_GREATER
+ Parallel.ForEach(Items.Keys, key =>
+#else
+ foreach (var key in Items.Keys)
+#endif
+ {
+ var items = GetItemsForBucket(key);
+ if (items == null)
+#if NET40_OR_GREATER || NETCOREAPP
+ return;
+#else
+ continue;
+#endif
+
+ for (int i = 0; i < items.Count; i++)
+ {
+ SetOneRomPerGameImpl(items[i]);
+ }
+#if NET40_OR_GREATER || NETCOREAPP
+ });
+#else
+ }
+#endif
+ }
+
+ ///
+ /// Set internal names to match One Rom Per Game (ORPG) logic
+ ///
+ /// DatItem to run logic on
+ private static void SetOneRomPerGameImpl(DatItem datItem)
+ {
+ // If the item name is null
+ string? machineName = datItem.GetName();
+ if (machineName == null)
+ return;
+
+ // Get the current machine
+ var machine = datItem.GetFieldValue(DatItem.MachineKey);
+ if (machine == null)
+ return;
+
+ // Remove extensions from Rom items
+ if (datItem is Rom)
+ {
+ string[] splitname = machineName.Split('.');
+ machineName = machine.GetStringFieldValue(Models.Metadata.Machine.NameKey)
+ + $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
+ }
+
+ // Strip off "Default" prefix only for ORPG
+ if (machineName.StartsWith("Default"))
+ machineName = machineName.Substring("Default".Length + 1);
+
+ datItem.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.NameKey, machineName);
+ datItem.SetName(Path.GetFileName(datItem.GetName()));
+ }
+
+ ///
+ /// Ensure that all roms are in their own game (or at least try to ensure)
+ ///
+ private void SetOneRomPerGameImplDB()
+ {
+ // For each rom, we want to update the game to be "/"
+#if NET452_OR_GREATER || NETCOREAPP
+ Parallel.ForEach(ItemsDB.SortedKeys, Globals.ParallelOptions, key =>
+#elif NET40_OR_GREATER
+ Parallel.ForEach(ItemsDB.SortedKeys, key =>
+#else
+ foreach (var key in ItemsDB.SortedKeys)
+#endif
+ {
+ var items = GetItemsForBucketDB(key);
+ if (items == null)
+#if NET40_OR_GREATER || NETCOREAPP
+ return;
+#else
+ continue;
+#endif
+
+ foreach (var item in items)
+ {
+ SetOneRomPerGameImplDB(item);
+ }
+#if NET40_OR_GREATER || NETCOREAPP
+ });
+#else
+ }
+#endif
+ }
+
+ ///
+ /// Set internal names to match One Rom Per Game (ORPG) logic
+ ///
+ /// DatItem to run logic on
+ private void SetOneRomPerGameImplDB(KeyValuePair datItem)
+ {
+ // If the item name is null
+ string? machineName = datItem.Value.GetName();
+ if (datItem.Key < 0 || machineName == null)
+ return;
+
+ // Get the current machine
+ var machine = ItemsDB.GetMachineForItem(datItem.Key);
+ if (machine.Value == null)
+ return;
+
+ // Remove extensions from Rom items
+ if (datItem.Value is Rom)
+ {
+ string[] splitname = machineName.Split('.');
+ machineName = machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)
+ + $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
+ }
+
+ // Strip off "Default" prefix only for ORPG
+ if (machineName.StartsWith("Default"))
+ machineName = machineName.Substring("Default".Length + 1);
+
+ machine.Value.SetFieldValue(Models.Metadata.Machine.NameKey, machineName);
+ datItem.Value.SetName(Path.GetFileName(datItem.Value.GetName()));
+ }
+
+ ///
+ /// Strip the dates from the beginning of scene-style set names
+ ///
+ private void StripSceneDatesFromItemsImpl()
+ {
+ // Now process all of the roms
+#if NET452_OR_GREATER || NETCOREAPP
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+#elif NET40_OR_GREATER
+ Parallel.ForEach(Items.Keys, key =>
+#else
+ foreach (var key in Items.Keys)
+#endif
+ {
+ var items = GetItemsForBucket(key);
+ if (items == null)
+#if NET40_OR_GREATER || NETCOREAPP
+ return;
+#else
+ continue;
+#endif
+
+ for (int j = 0; j < items.Count; j++)
+ {
+ DatItem item = items[j];
+ if (Regex.IsMatch(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, SceneNamePattern))
+ item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.NameKey, Regex.Replace(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, SceneNamePattern, "$2"));
+
+ if (Regex.IsMatch(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, SceneNamePattern))
+ item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.DescriptionKey, Regex.Replace(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, SceneNamePattern, "$2"));
+
+ items[j] = item;
+ }
+
+ Remove(key);
+ Add(key, items);
+#if NET40_OR_GREATER || NETCOREAPP
+ });
+#else
+ }
+#endif
+ }
+
+ ///
+ /// Strip the dates from the beginning of scene-style set names
+ ///
+ private void StripSceneDatesFromItemsImplDB()
+ {
+ // Now process all of the machines
+#if NET452_OR_GREATER || NETCOREAPP
+ Parallel.ForEach(GetMachinesDB(), Core.Globals.ParallelOptions, machine =>
+#elif NET40_OR_GREATER
+ Parallel.ForEach(GetMachinesDB(), machine =>
+#else
+ foreach (var machine in GetMachinesDB())
+#endif
+ {
+ // Get the current machine
+ if (machine.Value == null)
+#if NET40_OR_GREATER || NETCOREAPP
+ return;
+#else
+ continue;
+#endif
+
+ if (Regex.IsMatch(machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, SceneNamePattern))
+ machine.Value.SetFieldValue(Models.Metadata.Machine.NameKey, Regex.Replace(machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, SceneNamePattern, "$2"));
+
+ if (Regex.IsMatch(machine.Value.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, SceneNamePattern))
+ machine.Value.SetFieldValue(Models.Metadata.Machine.DescriptionKey, Regex.Replace(machine.Value.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, SceneNamePattern, "$2"));
+#if NET40_OR_GREATER || NETCOREAPP
+ });
+#else
+ }
+#endif
+ }
+
+ ///
+ /// Update machine names from descriptions according to mappings
+ ///
+ private void UpdateMachineNamesFromDescriptions(IDictionary mapping)
+ {
+#if NET452_OR_GREATER || NETCOREAPP
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+#elif NET40_OR_GREATER
+ Parallel.ForEach(Items.Keys, key =>
+#else
+ foreach (var key in Items.Keys)
+#endif
+ {
+ var items = GetItemsForBucket(key);
+ if (items == null)
+#if NET40_OR_GREATER || NETCOREAPP
+ return;
+#else
+ continue;
+#endif
+
+ foreach (DatItem item in items)
+ {
+ // Get the current machine
+ var machine = item.GetFieldValue(DatItem.MachineKey);
+ if (machine == null)
+ continue;
+
+ // Get the values to check against
+ string? machineName = machine.GetStringFieldValue(Models.Metadata.Machine.NameKey);
+ string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey);
+ string? romOf = machine.GetStringFieldValue(Models.Metadata.Machine.RomOfKey);
+ string? sampleOf = machine.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey);
+
+ // Update machine name
+ if (machineName != null && mapping.ContainsKey(machineName))
+ machine.SetFieldValue(Models.Metadata.Machine.NameKey, mapping[machineName]);
+
+ // Update cloneof
+ if (cloneOf != null && mapping.ContainsKey(cloneOf))
+ machine.SetFieldValue(Models.Metadata.Machine.CloneOfKey, mapping[cloneOf]);
+
+ // Update romof
+ if (romOf != null && mapping.ContainsKey(romOf))
+ machine.SetFieldValue(Models.Metadata.Machine.RomOfKey, mapping[romOf]);
+
+ // Update sampleof
+ if (sampleOf != null && mapping.ContainsKey(sampleOf))
+ machine.SetFieldValue(Models.Metadata.Machine.SampleOfKey, mapping[sampleOf]);
+ }
+#if NET40_OR_GREATER || NETCOREAPP
+ });
+#else
+ }
+#endif
+ }
+
+ ///
+ /// Update machine names from descriptions according to mappings
+ ///
+ private void UpdateMachineNamesFromDescriptionsDB(Dictionary mapping)
+ {
+ foreach (var machine in GetMachinesDB())
+ {
+ // Get the current machine
+ if (machine.Value == null)
+ continue;
+
+ // Get the values to check against
+ string? machineName = machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey);
+ string? cloneOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey);
+ string? romOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey);
+ string? sampleOf = machine.Value.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey);
+
+ // Update machine name
+ if (machineName != null && mapping.ContainsKey(machineName))
+ machine.Value.SetFieldValue(Models.Metadata.Machine.NameKey, mapping[machineName]);
+
+ // Update cloneof
+ if (cloneOf != null && mapping.ContainsKey(cloneOf))
+ machine.Value.SetFieldValue(Models.Metadata.Machine.CloneOfKey, mapping[cloneOf]);
+
+ // Update romof
+ if (romOf != null && mapping.ContainsKey(romOf))
+ machine.Value.SetFieldValue(Models.Metadata.Machine.RomOfKey, mapping[romOf]);
+
+ // Update sampleof
+ if (sampleOf != null && mapping.ContainsKey(sampleOf))
+ machine.Value.SetFieldValue(Models.Metadata.Machine.SampleOfKey, mapping[sampleOf]);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.DatFiles/DatFile.cs b/SabreTools.DatFiles/DatFile.cs
index 015c87ad..5a6f09d4 100644
--- a/SabreTools.DatFiles/DatFile.cs
+++ b/SabreTools.DatFiles/DatFile.cs
@@ -333,54 +333,6 @@ namespace SabreTools.DatFiles
ItemsDB.ExecuteFilters(filterRunner);
}
- ///
- /// Use game descriptions as names, updating cloneof/romof/sampleof
- ///
- /// True if the error that is thrown should be thrown back to the caller, false otherwise
- public void MachineDescriptionToName(bool throwOnError = false)
- {
- Items.MachineDescriptionToName(throwOnError);
- ItemsDB.MachineDescriptionToName(throwOnError);
- }
-
- ///
- /// Ensure that all roms are in their own game (or at least try to ensure)
- ///
- public void SetOneRomPerGame()
- {
- Items.SetOneRomPerGame();
- ItemsDB.SetOneRomPerGame();
- }
-
- ///
- /// Filter a DAT using 1G1R logic given an ordered set of regions
- ///
- /// List of regions in order of priority
- ///
- /// In the most technical sense, the way that the region list is being used does not
- /// confine its values to be just regions. Since it's essentially acting like a
- /// specialized version of the machine name filter, anything that is usually encapsulated
- /// in parenthesis would be matched on, including disc numbers, languages, editions,
- /// and anything else commonly used. Please note that, unlike other existing 1G1R
- /// solutions, this does not have the ability to contain custom mappings of parent
- /// to clone sets based on name, nor does it have the ability to match on the
- /// Release DatItem type.
- ///
- public void SetOneGamePerRegion(List regionList)
- {
- Items.SetOneGamePerRegion(regionList);
- ItemsDB.SetOneGamePerRegion(regionList);
- }
-
- ///
- /// Strip the dates from the beginning of scene-style set names
- ///
- public void StripSceneDatesFromItems()
- {
- Items.StripSceneDatesFromItems();
- ItemsDB.StripSceneDatesFromItems();
- }
-
#endregion
#region Parsing
diff --git a/SabreTools.DatFiles/ItemDictionary.cs b/SabreTools.DatFiles/ItemDictionary.cs
index c6acc946..a915507e 100644
--- a/SabreTools.DatFiles/ItemDictionary.cs
+++ b/SabreTools.DatFiles/ItemDictionary.cs
@@ -1,11 +1,8 @@
-using System;
-using System.Collections;
+using System.Collections;
#if NET40_OR_GREATER || NETCOREAPP
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
-using System.IO;
-using System.Text.RegularExpressions;
#if NET40_OR_GREATER || NETCOREAPP
using System.Threading.Tasks;
#endif
@@ -874,368 +871,6 @@ namespace SabreTools.DatFiles
#endif
}
- ///
- /// Use game descriptions as names, updating cloneof/romof/sampleof
- ///
- /// True if the error that is thrown should be thrown back to the caller, false otherwise
- internal void MachineDescriptionToName(bool throwOnError = false)
- {
- try
- {
- // First we want to get a mapping for all games to description
- var mapping = CreateMachineToDescriptionMapping();
-
- // Now we loop through every item and update accordingly
- UpdateMachineNamesFromDescriptions(mapping);
- }
- catch (Exception ex) when (!throwOnError)
- {
- _logger.Warning(ex.ToString());
- }
- }
-
- ///
- /// Ensure that all roms are in their own game (or at least try to ensure)
- ///
- internal void SetOneRomPerGame()
- {
- // For each rom, we want to update the game to be "/"
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(Keys, Core.Globals.ParallelOptions, key =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(Keys, key =>
-#else
- foreach (var key in Keys)
-#endif
- {
- var items = this[key];
- if (items == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- for (int i = 0; i < items.Count; i++)
- {
- SetOneRomPerGame(items[i]);
- }
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
- }
-
- ///
- /// Filter a DAT using 1G1R logic given an ordered set of regions
- ///
- /// List of regions in order of priority
- ///
- /// In the most technical sense, the way that the region list is being used does not
- /// confine its values to be just regions. Since it's essentially acting like a
- /// specialized version of the machine name filter, anything that is usually encapsulated
- /// in parenthesis would be matched on, including disc numbers, languages, editions,
- /// and anything else commonly used. Please note that, unlike other existing 1G1R
- /// solutions, this does not have the ability to contain custom mappings of parent
- /// to clone sets based on name, nor does it have the ability to match on the
- /// Release DatItem type.
- ///
- internal void SetOneGamePerRegion(List regionList)
- {
- // If we have null region list, make it empty
- regionList ??= [];
-
- // For sake of ease, the first thing we want to do is bucket by game
- BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
-
- // Then we want to get a mapping of all machines to parents
- Dictionary> parents = [];
- foreach (string key in Keys)
- {
- DatItem item = this[key]![0];
-
- // Get machine information
- Machine? machine = item.GetFieldValue(DatItem.MachineKey);
- string? machineName = machine?.GetStringFieldValue(Models.Metadata.Machine.NameKey)?.ToLowerInvariant();
- if (machine == null || machineName == null)
- continue;
-
- // Get the string values
- string? cloneOf = machine.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)?.ToLowerInvariant();
- string? romOf = machine.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)?.ToLowerInvariant();
-
- // Match on CloneOf first
- if (!string.IsNullOrEmpty(cloneOf))
- {
- if (!parents.ContainsKey(cloneOf!))
- parents.Add(cloneOf!, []);
-
- parents[cloneOf!].Add(machineName);
- }
-
- // Then by RomOf
- else if (!string.IsNullOrEmpty(romOf))
- {
- if (!parents.ContainsKey(romOf!))
- parents.Add(romOf!, []);
-
- parents[romOf!].Add(machineName);
- }
-
- // Otherwise, treat it as a parent
- else
- {
- if (!parents.ContainsKey(machineName))
- parents.Add(machineName, []);
-
- parents[machineName].Add(machineName);
- }
- }
-
- // Once we have the full list of mappings, filter out games to keep
- foreach (string key in parents.Keys)
- {
- // Find the first machine that matches the regions in order, if possible
- string? machine = default;
- foreach (string region in regionList)
- {
- machine = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
- if (machine != default)
- break;
- }
-
- // If we didn't get a match, use the parent
- if (machine == default)
- machine = key;
-
- // Remove the key from the list
- parents[key].Remove(machine);
-
- // Remove the rest of the items from this key
- parents[key].ForEach(k => Remove(k));
- }
-
- // Finally, strip out the parent tags
- RemoveTagsFromChild();
- }
-
- ///
- /// Strip the dates from the beginning of scene-style set names
- ///
- internal void StripSceneDatesFromItems()
- {
- // Set the regex pattern to use
- const string pattern = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";
-
- // Now process all of the roms
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(Keys, Core.Globals.ParallelOptions, key =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(Keys, key =>
-#else
- foreach (var key in Keys)
-#endif
- {
- var items = this[key];
- if (items == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- for (int j = 0; j < items.Count; j++)
- {
- DatItem item = items[j];
- if (Regex.IsMatch(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, pattern))
- item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.NameKey, Regex.Replace(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, pattern, "$2"));
-
- if (Regex.IsMatch(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, pattern))
- item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.DescriptionKey, Regex.Replace(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, pattern, "$2"));
-
- items[j] = item;
- }
-
- Remove(key);
- Add(key, items);
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
- }
-
- ///
- /// Create machine to description mapping dictionary
- ///
- private IDictionary CreateMachineToDescriptionMapping()
- {
-#if NET40_OR_GREATER || NETCOREAPP
- ConcurrentDictionary mapping = new();
-#else
- Dictionary mapping = [];
-#endif
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(Keys, Core.Globals.ParallelOptions, key =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(Keys, key =>
-#else
- foreach (var key in Keys)
-#endif
- {
- var items = this[key];
- if (items == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- foreach (DatItem item in items)
- {
- // Get the current machine
- var machine = item.GetFieldValue(DatItem.MachineKey);
- if (machine == null)
- continue;
-
- // Get the values to check against
- string? machineName = machine.GetStringFieldValue(Models.Metadata.Machine.NameKey);
- string? machineDesc = machine.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey);
- if (machineName == null || machineDesc == null)
- continue;
-
- // If the key mapping doesn't exist, add it
-#if NET40_OR_GREATER || NETCOREAPP
- mapping.TryAdd(machineName, machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -"));
-#else
- mapping[machineName] = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
-#endif
- }
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
-
- return mapping;
- }
-
- ///
- /// Set internal names to match One Rom Per Game (ORPG) logic
- ///
- /// DatItem to run logic on
- private static void SetOneRomPerGame(DatItem datItem)
- {
- // If the item name is null
- string? machineName = datItem.GetName();
- if (machineName == null)
- return;
-
- // Get the current machine
- var machine = datItem.GetFieldValue(DatItem.MachineKey);
- if (machine == null)
- return;
-
- // Remove extensions from Rom items
- if (datItem is Rom)
- {
- string[] splitname = machineName.Split('.');
- machineName = machine.GetStringFieldValue(Models.Metadata.Machine.NameKey)
- + $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
- }
-
- // Strip off "Default" prefix only for ORPG
- if (machineName.StartsWith("Default"))
- machineName = machineName.Substring("Default".Length + 1);
-
- datItem.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.NameKey, machineName);
- datItem.SetName(Path.GetFileName(datItem.GetName()));
- }
-
- ///
- /// Update machine names from descriptions according to mappings
- ///
- private void UpdateMachineNamesFromDescriptions(IDictionary mapping)
- {
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(Keys, Core.Globals.ParallelOptions, key =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(Keys, key =>
-#else
- foreach (var key in Keys)
-#endif
- {
- var items = this[key];
- if (items == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- List newItems = [];
- foreach (DatItem item in items)
- {
- // Update machine name
- if (!string.IsNullOrEmpty(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)) && mapping.ContainsKey(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)!))
- item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.NameKey, mapping[item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)!]);
-
- // Update cloneof
- if (!string.IsNullOrEmpty(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)) && mapping.ContainsKey(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)!))
- item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.CloneOfKey, mapping[item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)!]);
-
- // Update romof
- if (!string.IsNullOrEmpty(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)) && mapping.ContainsKey(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)!))
- item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.RomOfKey, mapping[item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)!]);
-
- // Update sampleof
- if (!string.IsNullOrEmpty(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey)) && mapping.ContainsKey(item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey)!))
- item.GetFieldValue(DatItem.MachineKey)!.SetFieldValue(Models.Metadata.Machine.SampleOfKey, mapping[item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey)!]);
-
- // Add the new item to the output list
- newItems.Add(item);
- }
-
- // Replace the old list of roms with the new one
- Remove(key);
- Add(key, newItems);
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
- }
-
- #endregion
-
- // TODO: All internal, can this be put into a better location?
- #region Splitting
-
- ///
- /// Remove all romof and cloneof tags from all games
- ///
- internal void RemoveTagsFromChild()
- {
- 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;
-
- 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);
- }
- }
- }
-
#endregion
#region Statistics
diff --git a/SabreTools.DatFiles/ItemDictionaryDB.cs b/SabreTools.DatFiles/ItemDictionaryDB.cs
index 6f283583..fba8c85b 100644
--- a/SabreTools.DatFiles/ItemDictionaryDB.cs
+++ b/SabreTools.DatFiles/ItemDictionaryDB.cs
@@ -5,7 +5,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Text.RegularExpressions;
#if NET40_OR_GREATER || NETCOREAPP
using System.Threading.Tasks;
#endif
@@ -1242,247 +1241,6 @@ namespace SabreTools.DatFiles
#endif
}
- ///
- /// Use game descriptions as names, updating cloneof/romof/sampleof
- ///
- /// True if the error that is thrown should be thrown back to the caller, false otherwise
- internal void MachineDescriptionToName(bool throwOnError = false)
- {
- try
- {
- // First we want to get a mapping for all games to description
- var mapping = CreateMachineToDescriptionMapping();
-
- // Now we loop through every item and update accordingly
- UpdateMachineNamesFromDescriptions(mapping);
- }
- catch (Exception ex) when (!throwOnError)
- {
- _logger.Warning(ex.ToString());
- }
- }
-
- ///
- /// Ensure that all roms are in their own game (or at least try to ensure)
- ///
- internal void SetOneRomPerGame()
- {
- // For each rom, we want to update the game to be "/"
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(SortedKeys, Core.Globals.ParallelOptions, key =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(SortedKeys, key =>
-#else
- foreach (var key in SortedKeys)
-#endif
- {
- var items = GetItemsForBucket(key);
- if (items == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- foreach (var item in items)
- {
- SetOneRomPerGame(item);
- }
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
- }
-
- ///
- /// Filter a DAT using 1G1R logic given an ordered set of regions
- ///
- /// List of regions in order of priority
- ///
- /// In the most technical sense, the way that the region list is being used does not
- /// confine its values to be just regions. Since it's essentially acting like a
- /// specialized version of the machine name filter, anything that is usually encapsulated
- /// in parenthesis would be matched on, including disc numbers, languages, editions,
- /// and anything else commonly used. Please note that, unlike other existing 1G1R
- /// solutions, this does not have the ability to contain custom mappings of parent
- /// to clone sets based on name, nor does it have the ability to match on the
- /// Release DatItem type.
- ///
- internal void SetOneGamePerRegion(List regionList)
- {
- // If we have null region list, make it empty
- regionList ??= [];
-
- // For sake of ease, the first thing we want to do is bucket by game
- BucketBy(ItemKey.Machine, DedupeType.None, norename: true);
-
- // Then we want to get a mapping of all machines to parents
- Dictionary> parents = [];
- foreach (string key in SortedKeys)
- {
- var items = GetItemsForBucket(key);
- if (items == null || items.Count == 0)
- continue;
-
- var item = items.First();
- var machine = GetMachineForItem(item.Key);
- if (machine.Value == null)
- continue;
-
- // Get machine information
- Machine? machineObj = machine.Value.GetFieldValue(DatItem.MachineKey);
- string? machineName = machineObj?.GetStringFieldValue(Models.Metadata.Machine.NameKey)?.ToLowerInvariant();
- if (machineObj == null || machineName == null)
- continue;
-
- // Get the string values
- string? cloneOf = machineObj.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)?.ToLowerInvariant();
- string? romOf = machineObj.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)?.ToLowerInvariant();
-
- // Match on CloneOf first
- if (!string.IsNullOrEmpty(cloneOf))
- {
- if (!parents.ContainsKey(cloneOf!))
- parents.Add(cloneOf!, []);
-
- parents[cloneOf!].Add(machineName);
- }
-
- // Then by RomOf
- else if (!string.IsNullOrEmpty(romOf))
- {
- if (!parents.ContainsKey(romOf!))
- parents.Add(romOf!, []);
-
- parents[romOf!].Add(machineName);
- }
-
- // Otherwise, treat it as a parent
- else
- {
- if (!parents.ContainsKey(machineName))
- parents.Add(machineName, []);
-
- parents[machineName].Add(machineName);
- }
- }
-
- // Once we have the full list of mappings, filter out games to keep
- foreach (string key in parents.Keys)
- {
- // Find the first machine that matches the regions in order, if possible
- string? machine = default;
- foreach (string region in regionList)
- {
- machine = parents[key].Find(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
- if (machine != default)
- break;
- }
-
- // If we didn't get a match, use the parent
- if (machine == default)
- machine = key;
-
- // Remove the key from the list
- parents[key].Remove(machine);
-
- // Remove the rest of the items from this key
- parents[key].ForEach(k => RemoveMachine(k));
- }
-
- // Finally, strip out the parent tags
- RemoveTagsFromChild();
- }
-
- ///
- /// Strip the dates from the beginning of scene-style set names
- ///
- internal void StripSceneDatesFromItems()
- {
- // Set the regex pattern to use
- const string pattern = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";
-
- // Now process all of the machines
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(GetMachines(), Core.Globals.ParallelOptions, machine =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(GetMachines(), machine =>
-#else
- foreach (var machine in GetMachines())
-#endif
- {
- // Get the current machine
- if (machine.Value == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- if (Regex.IsMatch(machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, pattern))
- machine.Value.SetFieldValue(Models.Metadata.Machine.NameKey, Regex.Replace(machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)!, pattern, "$2"));
-
- if (Regex.IsMatch(machine.Value.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, pattern))
- machine.Value.SetFieldValue(Models.Metadata.Machine.DescriptionKey, Regex.Replace(machine.Value.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey)!, pattern, "$2"));
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
- }
-
- ///
- /// Create machine to description mapping dictionary
- ///
- private IDictionary CreateMachineToDescriptionMapping()
- {
-#if NET40_OR_GREATER || NETCOREAPP
- ConcurrentDictionary mapping = new();
-#else
- Dictionary mapping = [];
-#endif
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(GetMachines(), Core.Globals.ParallelOptions, machine =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(GetMachines(), machine =>
-#else
- foreach (var machine in GetMachines())
-#endif
- {
- // Get the current machine
- if (machine.Value == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- // Get the values to check against
- string? machineName = machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey);
- string? machineDesc = machine.Value.GetStringFieldValue(Models.Metadata.Machine.DescriptionKey);
- if (machineName == null || machineDesc == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- // If the key mapping doesn't exist, add it
-#if NET40_OR_GREATER || NETCOREAPP
- mapping.TryAdd(machineName, machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -"));
-#else
- mapping[machineName] = machineDesc.Replace('/', '_').Replace("\"", "''").Replace(":", " -");
-#endif
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
-
- return mapping;
- }
-
///
/// Execute all filters in a filter runner on a single bucket
///
@@ -1506,127 +1264,6 @@ namespace SabreTools.DatFiles
_buckets[bucketName] = newItems;
}
- ///
- /// Set internal names to match One Rom Per Game (ORPG) logic
- ///
- /// DatItem to run logic on
- private void SetOneRomPerGame(KeyValuePair datItem)
- {
- // If the item name is null
- string? machineName = datItem.Value.GetName();
- if (datItem.Key < 0 || machineName == null)
- return;
-
- // Get the current machine
- var machine = GetMachineForItem(datItem.Key);
- if (machine.Value == null)
- return;
-
- // Remove extensions from Rom items
- if (datItem.Value is Rom)
- {
- string[] splitname = machineName.Split('.');
- machineName = machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)
- + $"/{string.Join(".", splitname, 0, splitname.Length > 1 ? splitname.Length - 1 : 1)}";
- }
-
- // Strip off "Default" prefix only for ORPG
- if (machineName.StartsWith("Default"))
- machineName = machineName.Substring("Default".Length + 1);
-
- machine.Value.SetFieldValue(Models.Metadata.Machine.NameKey, machineName);
- datItem.Value.SetName(Path.GetFileName(datItem.Value.GetName()));
- }
-
- ///
- /// Update machine names from descriptions according to mappings
- ///
- private void UpdateMachineNamesFromDescriptions(IDictionary mapping)
- {
-#if NET452_OR_GREATER || NETCOREAPP
- Parallel.ForEach(SortedKeys, Core.Globals.ParallelOptions, key =>
-#elif NET40_OR_GREATER
- Parallel.ForEach(SortedKeys, key =>
-#else
- foreach (var key in SortedKeys)
-#endif
- {
- var items = GetItemsForBucket(key);
- if (items == null)
-#if NET40_OR_GREATER || NETCOREAPP
- return;
-#else
- continue;
-#endif
-
- List newItems = [];
- foreach (var item in items)
- {
- // Get the current machine
- var machine = GetMachineForItem(item.Key);
- if (machine.Value == null)
- continue;
-
- // Update machine name
- if (!string.IsNullOrEmpty(machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)) && mapping.ContainsKey(machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)!))
- machine.Value.SetFieldValue(Models.Metadata.Machine.NameKey, mapping[machine.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey)!]);
-
- // Update cloneof
- if (!string.IsNullOrEmpty(machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)) && mapping.ContainsKey(machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)!))
- machine.Value.SetFieldValue(Models.Metadata.Machine.CloneOfKey, mapping[machine.Value.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)!]);
-
- // Update romof
- if (!string.IsNullOrEmpty(machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)) && mapping.ContainsKey(machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)!))
- machine.Value.SetFieldValue(Models.Metadata.Machine.RomOfKey, mapping[machine.Value.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)!]);
-
- // Update sampleof
- if (!string.IsNullOrEmpty(machine.Value.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey)) && mapping.ContainsKey(machine.Value.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey)!))
- machine.Value.SetFieldValue(Models.Metadata.Machine.SampleOfKey, mapping[machine.Value.GetStringFieldValue(Models.Metadata.Machine.SampleOfKey)!]);
-
- // Add the new item to the output list
- newItems.Add(item.Key);
- }
-
- // Replace the old list of roms with the new one
- _buckets[key] = newItems;
-#if NET40_OR_GREATER || NETCOREAPP
- });
-#else
- }
-#endif
- }
-
- #endregion
-
- // TODO: All internal, can this be put into a better location?
- #region Splitting
-
- ///
- /// Remove all romof and cloneof tags from all games
- ///
- internal void RemoveTagsFromChild()
- {
- 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;
-
- foreach (long id in items.Keys)
- {
- var machine = 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
#region Statistics