diff --git a/SabreTools.DatFiles/ItemDictionaryDB.cs b/SabreTools.DatFiles/ItemDictionaryDB.cs
index 67491535..f6a165b4 100644
--- a/SabreTools.DatFiles/ItemDictionaryDB.cs
+++ b/SabreTools.DatFiles/ItemDictionaryDB.cs
@@ -437,6 +437,18 @@ namespace SabreTools.DatFiles
return true;
}
+ ///
+ /// Remove a machine, returning if it could be removed
+ ///
+ public bool RemoveMachine(string machineName)
+ {
+ if (string.IsNullOrEmpty(machineName))
+ return false;
+
+ var machine = _machines.FirstOrDefault(m => m.Value.GetStringFieldValue(Models.Metadata.Machine.NameKey) == machineName);
+ return RemoveMachine(machine.Key);
+ }
+
///
/// Add an item, returning the insert index
///
@@ -955,6 +967,96 @@ namespace SabreTools.DatFiles
}
}
+ ///
+ /// 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)
+ {
+ // 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 = GetDatItemsForBucket(key);
+ if (items == null || items.Length == 0)
+ continue;
+
+ var item = items[0];
+ var machine = GetMachineForItem(item.Item1);
+ if (machine.Item2 == null)
+ continue;
+
+ // Match on CloneOf first
+ if (!string.IsNullOrEmpty(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)))
+ {
+ if (!parents.ContainsKey(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)!.ToLowerInvariant()))
+ parents.Add(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)!.ToLowerInvariant(), []);
+
+ parents[machine.Item2.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey)!.ToLowerInvariant()].Add(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.NameKey)!.ToLowerInvariant());
+ }
+
+ // Then by RomOf
+ else if (!string.IsNullOrEmpty(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)))
+ {
+ if (!parents.ContainsKey(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)!.ToLowerInvariant()))
+ parents.Add(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)!.ToLowerInvariant(), []);
+
+ parents[machine.Item2.GetStringFieldValue(Models.Metadata.Machine.RomOfKey)!.ToLowerInvariant()].Add(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.NameKey)!.ToLowerInvariant());
+ }
+
+ // Otherwise, treat it as a parent
+ else
+ {
+ if (!parents.ContainsKey(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.NameKey)!.ToLowerInvariant()))
+ parents.Add(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.NameKey)!.ToLowerInvariant(), []);
+
+ parents[machine.Item2.GetStringFieldValue(Models.Metadata.Machine.NameKey)!.ToLowerInvariant()].Add(machine.Item2.GetStringFieldValue(Models.Metadata.Machine.NameKey)!.ToLowerInvariant());
+ }
+ }
+
+ // 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].FirstOrDefault(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();
+ }
+
///
/// Ensure that all roms are in their own game (or at least try to ensure)
///
diff --git a/SabreTools.Filtering/Cleaner.cs b/SabreTools.Filtering/Cleaner.cs
index 482da8f7..eded4a29 100644
--- a/SabreTools.Filtering/Cleaner.cs
+++ b/SabreTools.Filtering/Cleaner.cs
@@ -22,7 +22,6 @@ namespace SabreTools.Filtering
///
/// Represents the cleaning operations that need to be performed on a set of items, usually a DAT
///
-
public class Cleaner
{
#region Fields
@@ -133,8 +132,11 @@ namespace SabreTools.Filtering
StripSceneDatesFromItems(datFile);
// Run the one rom per game logic, if required
- if (OneGamePerRegion == true)
+ if (OneGamePerRegion == true && RegionList != null)
+ {
SetOneGamePerRegion(datFile);
+ datFile.ItemsDB.SetOneGamePerRegion(RegionList);
+ }
// Run the one rom per game logic, if required
if (OneRomPerGame == true)